From efbdfbe09e55d475268fd22d58ee726084ddf098 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Mon, 18 Nov 2024 19:43:30 +0000
Subject: [PATCH 001/198] Add simple loop input classes for Net and Win32

---
 .../ConsoleDrivers/V2/ConsoleInput.cs         | 45 +++++++++
 .../ConsoleDrivers/V2/IConsoleInput.cs        | 15 +++
 Terminal.Gui/ConsoleDrivers/V2/NetInput.cs    | 15 +++
 .../ConsoleDrivers/V2/WindowsInput.cs         | 96 +++++++++++++++++++
 4 files changed, 171 insertions(+)
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
new file mode 100644
index 0000000000..0d344ec50b
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
@@ -0,0 +1,45 @@
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui;
+
+abstract class ConsoleInput<T>(ConcurrentQueue<T> inputBuffer) : IConsoleInput
+{
+    /// <inheritdoc />
+    public virtual void Dispose ()
+    {
+
+    }
+
+    /// <inheritdoc />
+    public void Run (CancellationToken token)
+    {
+        do
+        {
+            if (Peek ())
+            {
+                foreach (var r in Read ())
+                {
+                    inputBuffer.Enqueue (r);
+                }
+            }
+
+            Task.Delay (TimeSpan.FromMilliseconds (20), token).Wait (token);
+            token.ThrowIfCancellationRequested ();
+        }
+        while (!token.IsCancellationRequested);
+    }
+
+    /// <summary>
+    /// When implemented in a derived class, returns true if there is data available
+    /// to read from console.
+    /// </summary>
+    /// <returns></returns>
+    protected abstract bool Peek ();
+
+    /// <summary>
+    /// Returns the available data without blocking, called when <see cref="Peek"/>
+    /// returns <see langword="true"/>.
+    /// </summary>
+    /// <returns></returns>
+    protected abstract IEnumerable<T> Read ();
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs b/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs
new file mode 100644
index 0000000000..092e84d3c9
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs
@@ -0,0 +1,15 @@
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui;
+
+internal interface IConsoleInput : IDisposable
+{
+    /// <summary>
+    /// Runs in an infinite input loop.
+    /// </summary>
+    /// <param name="token"></param>
+    /// <exception cref="OperationCanceledException">Raised when token is
+    /// cancelled. This is the only means of exiting the input.</exception>
+    void Run (CancellationToken token);
+
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
new file mode 100644
index 0000000000..01fb6fd8c4
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
@@ -0,0 +1,15 @@
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui;
+
+class NetInput (ConcurrentQueue<ConsoleKeyInfo> inputBuffer) : ConsoleInput<ConsoleKeyInfo> (inputBuffer)
+{
+    /// <inheritdoc />
+    protected override bool Peek () => Console.KeyAvailable;
+
+    /// <inheritdoc />
+    protected override IEnumerable<ConsoleKeyInfo> Read ()
+    {
+        return [Console.ReadKey (true)];
+    }
+}
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
new file mode 100644
index 0000000000..f1ad6ffbf6
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
@@ -0,0 +1,96 @@
+using System.Collections.Concurrent;
+using System.Runtime.InteropServices;
+using static Terminal.Gui.WindowsConsole;
+
+namespace Terminal.Gui;
+
+class WindowsInput : ConsoleInput<InputRecord>
+{
+    private readonly nint _inputHandle;
+
+    [DllImport ("kernel32.dll", EntryPoint = "ReadConsoleInputW", CharSet = CharSet.Unicode)]
+    public static extern bool ReadConsoleInput (
+        nint hConsoleInput,
+        nint lpBuffer,
+        uint nLength,
+        out uint lpNumberOfEventsRead
+    );
+
+    [DllImport ("kernel32.dll", EntryPoint = "PeekConsoleInputW", CharSet = CharSet.Unicode)]
+    public static extern bool PeekConsoleInput (
+        nint hConsoleInput,
+        nint lpBuffer,
+        uint nLength,
+        out uint lpNumberOfEventsRead
+    );
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    private static extern nint GetStdHandle (int nStdHandle);
+
+    public WindowsInput (ConcurrentQueue<InputRecord> inputBuffer) : base (inputBuffer)
+    {
+        _inputHandle = GetStdHandle (STD_INPUT_HANDLE);
+    }
+
+    protected override bool Peek ()
+    {
+        const int bufferSize = 1; // We only need to check if there's at least one event
+        nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<InputRecord> () * bufferSize);
+
+        try
+        {
+            // Use PeekConsoleInput to inspect the input buffer without removing events
+            if (PeekConsoleInput (_inputHandle, pRecord, (uint)bufferSize, out uint numberOfEventsRead))
+            {
+                // Return true if there's at least one event in the buffer
+                return numberOfEventsRead > 0;
+            }
+            else
+            {
+                // Handle the failure of PeekConsoleInput
+                throw new InvalidOperationException ("Failed to peek console input.");
+            }
+        }
+        catch (Exception ex)
+        {
+            // Optionally log the exception
+            Console.WriteLine ($"Error in Peek: {ex.Message}");
+            return false;
+        }
+        finally
+        {
+            // Free the allocated memory
+            Marshal.FreeHGlobal (pRecord);
+        }
+    }
+    protected override IEnumerable<InputRecord> Read ()
+    {
+        const int bufferSize = 1;
+        nint pRecord = Marshal.AllocHGlobal (Marshal.SizeOf<InputRecord> () * bufferSize);
+
+        try
+        {
+            ReadConsoleInput (
+                              _inputHandle,
+                              pRecord,
+                              bufferSize,
+                              out uint numberEventsRead);
+
+            return numberEventsRead == 0
+                       ? []
+                       : new [] { Marshal.PtrToStructure<InputRecord> (pRecord) };
+        }
+        catch (Exception)
+        {
+            return [];
+        }
+        finally
+        {
+            Marshal.FreeHGlobal (pRecord);
+        }
+    }
+    public void Dispose ()
+    {
+        // TODO: Un set any settings e.g. console buffer
+    }
+}

From 8000c8df6b2f3add2fbc42c4d0effa0841e3bbd5 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Mon, 18 Nov 2024 20:28:55 +0000
Subject: [PATCH 002/198] Add scaffolding

---
 .../ConsoleDrivers/V2/ConsoleInput.cs         | 21 +++++-
 .../ConsoleDrivers/V2/IConsoleInput.cs        |  8 ++-
 Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs   | 26 +++++++
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    | 49 +++++++++++++
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  | 47 +++++++++++++
 Terminal.Gui/ConsoleDrivers/V2/NetInput.cs    |  2 +-
 Terminal.Gui/ConsoleDrivers/V2/V2.cd          | 69 +++++++++++++++++++
 .../ConsoleDrivers/V2/WindowsInput.cs         |  2 +-
 8 files changed, 218 insertions(+), 6 deletions(-)
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/V2.cd

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
index 0d344ec50b..da3ce88080 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
@@ -1,25 +1,40 @@
-using System.Collections.Concurrent;
+#nullable enable
+using System.Collections.Concurrent;
+
 
 namespace Terminal.Gui;
 
-abstract class ConsoleInput<T>(ConcurrentQueue<T> inputBuffer) : IConsoleInput
+abstract class ConsoleInput<T> : IConsoleInput<T>
 {
+    private ConcurrentQueue<T>? _inputBuffer;
+
     /// <inheritdoc />
     public virtual void Dispose ()
     {
 
     }
 
+    /// <inheritdoc />
+    public void Initialize (ConcurrentQueue<T> inputBuffer)
+    {
+        _inputBuffer = inputBuffer;
+    }
+
     /// <inheritdoc />
     public void Run (CancellationToken token)
     {
+        if (_inputBuffer == null)
+        {
+            throw new ("Cannot run input before Initialization");
+        }
+
         do
         {
             if (Peek ())
             {
                 foreach (var r in Read ())
                 {
-                    inputBuffer.Enqueue (r);
+                    _inputBuffer.Enqueue (r);
                 }
             }
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs b/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs
index 092e84d3c9..68cf4145fb 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs
@@ -2,8 +2,14 @@
 
 namespace Terminal.Gui;
 
-internal interface IConsoleInput : IDisposable
+internal interface IConsoleInput<T> : IDisposable
 {
+    /// <summary>
+    /// Initializes the input with a buffer into which to put data read
+    /// </summary>
+    /// <param name="inputBuffer"></param>
+    void Initialize (ConcurrentQueue<T> inputBuffer);
+
     /// <summary>
     /// Runs in an infinite input loop.
     /// </summary>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
new file mode 100644
index 0000000000..5747c792c2
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
@@ -0,0 +1,26 @@
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui;
+
+internal interface IMainLoop<T> : IDisposable
+{
+    /// <summary>
+    /// Initializes the loop with a buffer from which data can be read
+    /// </summary>
+    /// <param name="inputBuffer"></param>
+    /// <param name="parser"></param>
+    void Initialize (ConcurrentQueue<T> inputBuffer, AnsiResponseParser<T> parser);
+
+    /// <summary>
+    /// Runs <see cref="Iteration"/> in an infinite loop.
+    /// </summary>
+    /// <param name="token"></param>
+    /// <exception cref="OperationCanceledException">Raised when token is
+    /// cancelled. This is the only means of exiting.</exception>
+    public void Run (CancellationToken token);
+
+    /// <summary>
+    /// Perform a single iteration of the main loop without blocking anywhere.
+    /// </summary>
+    public void Iteration ();
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
new file mode 100644
index 0000000000..d526d5d6eb
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -0,0 +1,49 @@
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui;
+class MainLoop<T> : IMainLoop<T>
+{
+    public ConcurrentQueue<T> InputBuffer { get; private set; } = new ();
+
+    public AnsiResponseParser<T> Parser
+    {
+        get;
+        private set;
+    }
+
+    /// <inheritdoc />
+    public void Initialize (ConcurrentQueue<T> inputBuffer, AnsiResponseParser<T> parser)
+    {
+        InputBuffer = inputBuffer;
+        Parser = parser;
+    }
+
+
+    public void Run (CancellationToken token)
+    {
+        do
+        {
+            var dt = DateTime.Now;
+
+            Iteration ();
+
+            var took = DateTime.Now - dt;
+            var sleepFor = TimeSpan.FromMilliseconds (50) - took;
+
+            if (sleepFor.Milliseconds > 0)
+            {
+                Task.Delay (sleepFor, token).Wait (token);
+            }
+        }
+        while (!token.IsCancellationRequested);
+    }
+    /// <inheritdoc />
+    public void Iteration ()
+    {
+
+    }
+    /// <inheritdoc />
+    public void Dispose ()
+    { // TODO release managed resources here
+    }
+}
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
new file mode 100644
index 0000000000..6765f7d5a6
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -0,0 +1,47 @@
+namespace Terminal.Gui.ConsoleDrivers.V2;
+class MainLoopCoordinator<T>
+{
+    private readonly IConsoleInput<T> _input;
+    private readonly IMainLoop<T> _loop;
+    private CancellationTokenSource tokenSource = new CancellationTokenSource ();
+
+    public MainLoopCoordinator (IConsoleInput<T> input, IMainLoop<T> loop)
+    {
+        _input = input;
+        _loop = loop;
+    }
+    public void Start ()
+    {
+        Task.Run (RunInput);
+        Task.Run (RunLoop);
+    }
+
+    private void RunInput ()
+    {
+        try
+        {
+            _input.Run (tokenSource.Token);
+        }
+        catch (OperationCanceledException)
+        {
+        }
+        _input.Dispose ();
+    }
+
+    private void RunLoop ()
+    {
+        try
+        {
+            _loop.Run (tokenSource.Token);
+        }
+        catch (OperationCanceledException)
+        {
+        }
+        _loop.Dispose ();
+    }
+
+    public void Stop ()
+    {
+        tokenSource.Cancel();
+    }
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
index 01fb6fd8c4..af8d263e1e 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
@@ -2,7 +2,7 @@
 
 namespace Terminal.Gui;
 
-class NetInput (ConcurrentQueue<ConsoleKeyInfo> inputBuffer) : ConsoleInput<ConsoleKeyInfo> (inputBuffer)
+class NetInput : ConsoleInput<ConsoleKeyInfo>
 {
     /// <inheritdoc />
     protected override bool Peek () => Console.KeyAvailable;
diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
new file mode 100644
index 0000000000..7a647fac33
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ClassDiagram MajorVersion="1" MinorVersion="1">
+  <Class Name="Terminal.Gui.WindowsInput" Collapsed="true">
+    <Position X="7.25" Y="8.5" Width="2.25" />
+    <TypeIdentifier>
+      <HashCode>QAAAAAAAACAEAAAAAAAAAAAkAAAAAAAAAgAAAAAAABA=</HashCode>
+      <FileName>ConsoleDrivers\V2\WindowsInput.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="Terminal.Gui.NetInput" Collapsed="true">
+    <Position X="10.25" Y="8.5" Width="2" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAEAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\NetInput.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="Terminal.Gui.ConsoleInput&lt;T&gt;">
+    <Position X="9" Y="6" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAACAEAQAAAAAAAAAgAAAAAAAAAAAAAAAAAAo=</HashCode>
+      <FileName>ConsoleDrivers\V2\ConsoleInput.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Class Name="Terminal.Gui.MainLoop&lt;T&gt;" BaseTypeListCollapsed="true">
+    <Position X="9" Y="1.75" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AQAgAAAAACAAAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAI=</HashCode>
+      <FileName>ConsoleDrivers\V2\MainLoop.cs</FileName>
+    </TypeIdentifier>
+    <ShowAsAssociation>
+      <Property Name="Parser" />
+    </ShowAsAssociation>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Class Name="Terminal.Gui.ConsoleDrivers.V2.MainLoopCoordinator&lt;T&gt;">
+    <Position X="5" Y="1.75" Width="2" />
+    <TypeIdentifier>
+      <HashCode>AAAAJAAAACIAAAAAAAAAAAAAAAAQAAQAIAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\MainLoopCoordinator.cs</FileName>
+    </TypeIdentifier>
+    <ShowAsAssociation>
+      <Field Name="_input" />
+      <Field Name="_loop" />
+    </ShowAsAssociation>
+  </Class>
+  <Class Name="Terminal.Gui.AnsiResponseParser&lt;T&gt;" Collapsed="true">
+    <Position X="12.5" Y="1.75" Width="2" />
+    <TypeIdentifier>
+      <HashCode>AAQAAAAAAAAACIAAAAAAAAAAAAAgAABAAAAAABAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Interface Name="Terminal.Gui.IConsoleInput&lt;T&gt;">
+    <Position X="9" Y="4.25" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAI=</HashCode>
+      <FileName>ConsoleDrivers\V2\IConsoleInput.cs</FileName>
+    </TypeIdentifier>
+  </Interface>
+  <Interface Name="Terminal.Gui.IMainLoop&lt;T&gt;" Collapsed="true">
+    <Position X="9" Y="0.75" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAAAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAI=</HashCode>
+      <FileName>ConsoleDrivers\V2\IMainLoop.cs</FileName>
+    </TypeIdentifier>
+  </Interface>
+  <Font Name="Segoe UI" Size="9" />
+</ClassDiagram>
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
index f1ad6ffbf6..edfd3c7cf4 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
@@ -27,7 +27,7 @@ out uint lpNumberOfEventsRead
     [DllImport ("kernel32.dll", SetLastError = true)]
     private static extern nint GetStdHandle (int nStdHandle);
 
-    public WindowsInput (ConcurrentQueue<InputRecord> inputBuffer) : base (inputBuffer)
+    public WindowsInput ()
     {
         _inputHandle = GetStdHandle (STD_INPUT_HANDLE);
     }

From b0fe7a4654f59911b45bb4d87587d5e67ef0c2fa Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Mon, 18 Nov 2024 20:30:06 +0000
Subject: [PATCH 003/198] Adjust layout

---
 Terminal.Gui/ConsoleDrivers/V2/V2.cd | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index 7a647fac33..c84f1e3b9f 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -1,29 +1,29 @@
 <?xml version="1.0" encoding="utf-8"?>
 <ClassDiagram MajorVersion="1" MinorVersion="1">
   <Class Name="Terminal.Gui.WindowsInput" Collapsed="true">
-    <Position X="7.25" Y="8.5" Width="2.25" />
+    <Position X="7.25" Y="7.5" Width="2.25" />
     <TypeIdentifier>
       <HashCode>QAAAAAAAACAEAAAAAAAAAAAkAAAAAAAAAgAAAAAAABA=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsInput.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.NetInput" Collapsed="true">
-    <Position X="10.25" Y="8.5" Width="2" />
+    <Position X="10.25" Y="7.5" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAEAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\NetInput.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.ConsoleInput&lt;T&gt;">
-    <Position X="9" Y="6" Width="1.5" />
+    <Position X="9" Y="5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAACAEAQAAAAAAAAAgAAAAAAAAAAAAAAAAAAo=</HashCode>
       <FileName>ConsoleDrivers\V2\ConsoleInput.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.MainLoop&lt;T&gt;" BaseTypeListCollapsed="true">
-    <Position X="9" Y="1.75" Width="1.5" />
+  <Class Name="Terminal.Gui.MainLoop&lt;T&gt;" Collapsed="true" BaseTypeListCollapsed="true">
+    <Position X="9" Y="2.5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AQAgAAAAACAAAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAI=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoop.cs</FileName>
@@ -45,21 +45,21 @@
     </ShowAsAssociation>
   </Class>
   <Class Name="Terminal.Gui.AnsiResponseParser&lt;T&gt;" Collapsed="true">
-    <Position X="12.5" Y="1.75" Width="2" />
+    <Position X="11.5" Y="2.5" Width="2" />
     <TypeIdentifier>
       <HashCode>AAQAAAAAAAAACIAAAAAAAAAAAAAgAABAAAAAABAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Interface Name="Terminal.Gui.IConsoleInput&lt;T&gt;">
-    <Position X="9" Y="4.25" Width="1.5" />
+    <Position X="9" Y="3.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAI=</HashCode>
       <FileName>ConsoleDrivers\V2\IConsoleInput.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.IMainLoop&lt;T&gt;" Collapsed="true">
-    <Position X="9" Y="0.75" Width="1.5" />
+  <Interface Name="Terminal.Gui.IMainLoop&lt;T&gt;">
+    <Position X="9" Y="0.5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAI=</HashCode>
       <FileName>ConsoleDrivers\V2\IMainLoop.cs</FileName>

From 43418b52f7ad7272921979fae48c7d9ec35e766b Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Tue, 19 Nov 2024 21:46:07 +0000
Subject: [PATCH 004/198] Add Drivers2 test project and make everything public.
  Also add output

---
 Drivers2/Drivers2.csproj                      | 14 +++
 Drivers2/Program.cs                           | 68 +++++++++++++
 .../AnsiResponseExpectation.cs                |  2 +-
 .../AnsiResponseParser/AnsiResponseParser.cs  |  4 +-
 .../AnsiResponseParser/IHeld.cs               |  2 +-
 .../ConsoleDrivers/V2/ConsoleInput.cs         |  2 +-
 .../ConsoleDrivers/V2/IConsoleInput.cs        |  2 +-
 .../ConsoleDrivers/V2/IConsoleOutput.cs       | 12 +++
 Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs   |  5 +-
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    |  8 +-
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  |  7 +-
 Terminal.Gui/ConsoleDrivers/V2/NetInput.cs    |  2 +-
 Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs   | 22 +++++
 Terminal.Gui/ConsoleDrivers/V2/V2.cd          | 16 +++-
 .../ConsoleDrivers/V2/WindowsInput.cs         |  2 +-
 .../ConsoleDrivers/V2/WindowsOutput.cs        | 95 +++++++++++++++++++
 Terminal.Gui/ConsoleDrivers/WindowsDriver.cs  |  2 +-
 Terminal.sln                                  |  6 ++
 18 files changed, 251 insertions(+), 20 deletions(-)
 create mode 100644 Drivers2/Drivers2.csproj
 create mode 100644 Drivers2/Program.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs

diff --git a/Drivers2/Drivers2.csproj b/Drivers2/Drivers2.csproj
new file mode 100644
index 0000000000..d36e1d7ac0
--- /dev/null
+++ b/Drivers2/Drivers2.csproj
@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
+  </ItemGroup>
+
+</Project>
diff --git a/Drivers2/Program.cs b/Drivers2/Program.cs
new file mode 100644
index 0000000000..543fd08c9f
--- /dev/null
+++ b/Drivers2/Program.cs
@@ -0,0 +1,68 @@
+using System.Collections.Concurrent;
+using Terminal.Gui;
+using static Terminal.Gui.WindowsConsole;
+
+namespace Drivers2;
+
+class Program
+{
+    static void Main (string [] args)
+    {
+        bool win = false;
+
+        if (args.Length > 0)
+        {
+            if (args [0] == "net")
+            {
+                // default
+            }
+            else if(args [0] == "win")
+            {
+                win = true;
+            }
+            else
+            {
+                Console.WriteLine("Arg must be 'win' or 'net' or blank to use default");
+            }
+        }
+
+        if (win)
+        {
+            using var input = new WindowsInput ();
+            using var output = new WindowsOutput ();
+            var parser = new AnsiResponseParser<InputRecord> ();
+            var buffer = new ConcurrentQueue<InputRecord> ();
+
+            var loop = new MainLoop<InputRecord> ();
+            loop.Initialize (buffer,parser,output);
+
+            var coordinator = new MainLoopCoordinator<InputRecord> (input,loop);
+
+            coordinator.Start ();
+
+            output.Write ("Hello World");
+
+            coordinator.Stop ();
+        }
+        else
+        {
+            using var input = new NetInput ();
+            using var output = new NetOutput () ;
+            var parser = new AnsiResponseParser<ConsoleKeyInfo> ();
+            var buffer = new ConcurrentQueue<ConsoleKeyInfo> ();
+
+            var loop = new MainLoop<ConsoleKeyInfo> ();
+            loop.Initialize (buffer, parser, output);
+
+            var coordinator = new MainLoopCoordinator<ConsoleKeyInfo> (input, loop);
+
+            coordinator.Start ();
+
+            output.Write ("Hello World");
+
+
+            coordinator.Stop ();
+        }
+
+    }
+}
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseExpectation.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseExpectation.cs
index 00aa1a9512..c313c21690 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseExpectation.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseExpectation.cs
@@ -1,7 +1,7 @@
 #nullable enable
 namespace Terminal.Gui;
 
-internal record AnsiResponseExpectation (string Terminator, Action<IHeld> Response, Action? Abandoned)
+public record AnsiResponseExpectation (string Terminator, Action<IHeld> Response, Action? Abandoned)
 {
     public bool Matches (string cur) { return cur.EndsWith (Terminator); }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
index a2bc4d0ceb..16a9c533ef 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
@@ -2,7 +2,7 @@
 
 namespace Terminal.Gui;
 
-internal abstract class AnsiResponseParserBase : IAnsiResponseParser
+public abstract class AnsiResponseParserBase : IAnsiResponseParser
 {
     protected object lockExpectedResponses = new ();
 
@@ -333,7 +333,7 @@ public void StopExpecting (string terminator, bool persistent)
     }
 }
 
-internal class AnsiResponseParser<T> : AnsiResponseParserBase
+public class AnsiResponseParser<T> : AnsiResponseParserBase
 {
     public AnsiResponseParser () : base (new GenericHeld<T> ()) { }
 
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/IHeld.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/IHeld.cs
index ab23f477fd..efcf025038 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/IHeld.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/IHeld.cs
@@ -5,7 +5,7 @@ namespace Terminal.Gui;
 ///     Describes a sequence of chars (and optionally T metadata) accumulated
 ///     by an <see cref="IAnsiResponseParser"/>
 /// </summary>
-internal interface IHeld
+public interface IHeld
 {
     /// <summary>
     ///     Clears all held objects
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
index da3ce88080..d2993b3d57 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
@@ -4,7 +4,7 @@
 
 namespace Terminal.Gui;
 
-abstract class ConsoleInput<T> : IConsoleInput<T>
+public abstract class ConsoleInput<T> : IConsoleInput<T>
 {
     private ConcurrentQueue<T>? _inputBuffer;
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs b/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs
index 68cf4145fb..7c90a07f1a 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs
@@ -2,7 +2,7 @@
 
 namespace Terminal.Gui;
 
-internal interface IConsoleInput<T> : IDisposable
+public interface IConsoleInput<T> : IDisposable
 {
     /// <summary>
     /// Initializes the input with a buffer into which to put data read
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
new file mode 100644
index 0000000000..996ba25c3a
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Terminal.Gui;
+public interface IConsoleOutput : IDisposable
+{
+    void Write(string text);
+
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
index 5747c792c2..2a690f9433 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
@@ -2,14 +2,15 @@
 
 namespace Terminal.Gui;
 
-internal interface IMainLoop<T> : IDisposable
+public interface IMainLoop<T> : IDisposable
 {
     /// <summary>
     /// Initializes the loop with a buffer from which data can be read
     /// </summary>
     /// <param name="inputBuffer"></param>
     /// <param name="parser"></param>
-    void Initialize (ConcurrentQueue<T> inputBuffer, AnsiResponseParser<T> parser);
+    /// <param name="consoleOutput"></param>
+    void Initialize (ConcurrentQueue<T> inputBuffer, AnsiResponseParser<T> parser, IConsoleOutput consoleOutput);
 
     /// <summary>
     /// Runs <see cref="Iteration"/> in an infinite loop.
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index d526d5d6eb..3acc92157b 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -1,7 +1,8 @@
 using System.Collections.Concurrent;
 
 namespace Terminal.Gui;
-class MainLoop<T> : IMainLoop<T>
+
+public class MainLoop<T> : IMainLoop<T>
 {
     public ConcurrentQueue<T> InputBuffer { get; private set; } = new ();
 
@@ -11,11 +12,14 @@ public AnsiResponseParser<T> Parser
         private set;
     }
 
+    public IConsoleOutput Out { get;private set; }
+
     /// <inheritdoc />
-    public void Initialize (ConcurrentQueue<T> inputBuffer, AnsiResponseParser<T> parser)
+    public void Initialize (ConcurrentQueue<T> inputBuffer, AnsiResponseParser<T> parser, IConsoleOutput consoleOutput)
     {
         InputBuffer = inputBuffer;
         Parser = parser;
+        Out = consoleOutput;
     }
 
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index 6765f7d5a6..0559849cde 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -1,9 +1,10 @@
-namespace Terminal.Gui.ConsoleDrivers.V2;
-class MainLoopCoordinator<T>
+namespace Terminal.Gui;
+
+public class MainLoopCoordinator<T>
 {
     private readonly IConsoleInput<T> _input;
     private readonly IMainLoop<T> _loop;
-    private CancellationTokenSource tokenSource = new CancellationTokenSource ();
+    private CancellationTokenSource tokenSource = new ();
 
     public MainLoopCoordinator (IConsoleInput<T> input, IMainLoop<T> loop)
     {
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
index af8d263e1e..f7ec9e09d7 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
@@ -2,7 +2,7 @@
 
 namespace Terminal.Gui;
 
-class NetInput : ConsoleInput<ConsoleKeyInfo>
+public class NetInput : ConsoleInput<ConsoleKeyInfo>
 {
     /// <inheritdoc />
     protected override bool Peek () => Console.KeyAvailable;
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
new file mode 100644
index 0000000000..1745785131
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Terminal.Gui;
+public class NetOutput : IConsoleOutput
+{
+    /// <inheritdoc />
+    public void Write (string text)
+    {
+        Console.WriteLine (text);
+    }
+
+    /// <inheritdoc />
+    public void Dispose ()
+    {
+        Console.Clear ();
+    }
+
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index c84f1e3b9f..d263798613 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -25,16 +25,17 @@
   <Class Name="Terminal.Gui.MainLoop&lt;T&gt;" Collapsed="true" BaseTypeListCollapsed="true">
     <Position X="9" Y="2.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AQAgAAAAACAAAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAI=</HashCode>
+      <HashCode>QQAgAAAAACAAAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAI=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoop.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
       <Property Name="Parser" />
+      <Property Name="Out" />
     </ShowAsAssociation>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.ConsoleDrivers.V2.MainLoopCoordinator&lt;T&gt;">
-    <Position X="5" Y="1.75" Width="2" />
+  <Class Name="Terminal.Gui.MainLoopCoordinator&lt;T&gt;">
+    <Position X="5.75" Y="1.75" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAJAAAACIAAAAAAAAAAAAAAAAQAAQAIAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoopCoordinator.cs</FileName>
@@ -45,7 +46,7 @@
     </ShowAsAssociation>
   </Class>
   <Class Name="Terminal.Gui.AnsiResponseParser&lt;T&gt;" Collapsed="true">
-    <Position X="11.5" Y="2.5" Width="2" />
+    <Position X="11.75" Y="1.75" Width="2" />
     <TypeIdentifier>
       <HashCode>AAQAAAAAAAAACIAAAAAAAAAAAAAgAABAAAAAABAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
@@ -65,5 +66,12 @@
       <FileName>ConsoleDrivers\V2\IMainLoop.cs</FileName>
     </TypeIdentifier>
   </Interface>
+  <Interface Name="Terminal.Gui.IConsoleOutput" Collapsed="true">
+    <Position X="12.25" Y="3.25" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\IConsoleOutput.cs</FileName>
+    </TypeIdentifier>
+  </Interface>
   <Font Name="Segoe UI" Size="9" />
 </ClassDiagram>
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
index edfd3c7cf4..d2ce100747 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
@@ -4,7 +4,7 @@
 
 namespace Terminal.Gui;
 
-class WindowsInput : ConsoleInput<InputRecord>
+public class WindowsInput : ConsoleInput<InputRecord>
 {
     private readonly nint _inputHandle;
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
new file mode 100644
index 0000000000..fde07e25c1
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Terminal.Gui;
+public class WindowsOutput : IConsoleOutput
+{
+
+    [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)]
+    private static extern bool CloseHandle (nint handle);
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    private static extern nint CreateConsoleScreenBuffer (
+        DesiredAccess dwDesiredAccess,
+        ShareMode dwShareMode,
+        nint secutiryAttributes,
+        uint flags,
+        nint screenBufferData
+    );
+
+    [Flags]
+    private enum ShareMode : uint
+    {
+        FileShareRead = 1,
+        FileShareWrite = 2
+    }
+
+    [Flags]
+    private enum DesiredAccess : uint
+    {
+        GenericRead = 2147483648,
+        GenericWrite = 1073741824
+    }
+
+    internal static nint INVALID_HANDLE_VALUE = new (-1);
+    
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    private static extern bool SetConsoleActiveScreenBuffer (nint Handle);
+
+    private nint _screenBuffer;
+
+    public WindowsOutput ()
+    {
+        _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);
+            }
+        }
+
+        if (!SetConsoleActiveScreenBuffer (_screenBuffer))
+        {
+            throw new Win32Exception (Marshal.GetLastWin32Error ());
+        }
+
+    }
+    public void Write (string str)
+    {
+        WriteConsole (_screenBuffer, str, (uint)str.Length, out uint _, nint.Zero);
+
+    }
+
+    /// <inheritdoc />
+    public void Dispose ()
+    {
+        if (_screenBuffer != nint.Zero)
+        {
+            CloseHandle (_screenBuffer);
+        }
+    }
+}
diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
index c5a4d6d9a0..7589adcea3 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
@@ -26,7 +26,7 @@
 
 namespace Terminal.Gui;
 
-internal class WindowsConsole
+public class WindowsConsole
 {
     public const int STD_OUTPUT_HANDLE = -11;
     public const int STD_INPUT_HANDLE = -10;
diff --git a/Terminal.sln b/Terminal.sln
index 5f2eda9e8a..405d3a507b 100644
--- a/Terminal.sln
+++ b/Terminal.sln
@@ -48,6 +48,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SelfContained", "SelfContai
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeAot", "NativeAot\NativeAot.csproj", "{E6D716C6-AC94-4150-B10A-44AE13F79344}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drivers2", "Drivers2\Drivers2.csproj", "{3221C618-E35C-4F38-A90F-A220CD4DD8B0}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -86,6 +88,10 @@ Global
 		{E6D716C6-AC94-4150-B10A-44AE13F79344}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{E6D716C6-AC94-4150-B10A-44AE13F79344}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{E6D716C6-AC94-4150-B10A-44AE13F79344}.Release|Any CPU.Build.0 = Release|Any CPU
+		{3221C618-E35C-4F38-A90F-A220CD4DD8B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{3221C618-E35C-4F38-A90F-A220CD4DD8B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{3221C618-E35C-4F38-A90F-A220CD4DD8B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{3221C618-E35C-4F38-A90F-A220CD4DD8B0}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

From 77febd872b09739eb1b7d7dce29ee4d9a85d84ae Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Tue, 19 Nov 2024 21:49:03 +0000
Subject: [PATCH 005/198] Launch with "win"

---
 Drivers2/Properties/launchSettings.json | 8 ++++++++
 1 file changed, 8 insertions(+)
 create mode 100644 Drivers2/Properties/launchSettings.json

diff --git a/Drivers2/Properties/launchSettings.json b/Drivers2/Properties/launchSettings.json
new file mode 100644
index 0000000000..d8f3b5caa3
--- /dev/null
+++ b/Drivers2/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+  "profiles": {
+    "Drivers2": {
+      "commandName": "Project",
+      "commandLineArgs": "win"
+    }
+  }
+}
\ No newline at end of file

From e24d06fb83f653ebd3ea46661186ff1d3ba554cd Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Wed, 20 Nov 2024 21:09:04 +0000
Subject: [PATCH 006/198] Deal with strange thread requirements of Win32 apis

---
 Drivers2/Program.cs                           | 45 +++++--------
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    |  2 +-
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  | 63 ++++++++++++++++---
 Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs   |  2 +-
 .../ConsoleDrivers/V2/WindowsOutput.cs        |  6 +-
 5 files changed, 76 insertions(+), 42 deletions(-)

diff --git a/Drivers2/Program.cs b/Drivers2/Program.cs
index 543fd08c9f..0f7785aae2 100644
--- a/Drivers2/Program.cs
+++ b/Drivers2/Program.cs
@@ -26,43 +26,30 @@ static void Main (string [] args)
             }
         }
 
+        IMainLoopCoordinator coordinator;
         if (win)
         {
-            using var input = new WindowsInput ();
-            using var output = new WindowsOutput ();
-            var parser = new AnsiResponseParser<InputRecord> ();
-            var buffer = new ConcurrentQueue<InputRecord> ();
-
             var loop = new MainLoop<InputRecord> ();
-            loop.Initialize (buffer,parser,output);
-
-            var coordinator = new MainLoopCoordinator<InputRecord> (input,loop);
-
-            coordinator.Start ();
-
-            output.Write ("Hello World");
-
-            coordinator.Stop ();
+            coordinator = new MainLoopCoordinator<InputRecord> (
+                                                                ()=>new WindowsInput (),
+                                                                ()=>new WindowsOutput (),
+                                                                loop);
         }
         else
         {
-            using var input = new NetInput ();
-            using var output = new NetOutput () ;
-            var parser = new AnsiResponseParser<ConsoleKeyInfo> ();
-            var buffer = new ConcurrentQueue<ConsoleKeyInfo> ();
-
             var loop = new MainLoop<ConsoleKeyInfo> ();
-            loop.Initialize (buffer, parser, output);
-
-            var coordinator = new MainLoopCoordinator<ConsoleKeyInfo> (input, loop);
-
-            coordinator.Start ();
-
-            output.Write ("Hello World");
-
-
-            coordinator.Stop ();
+            coordinator = new MainLoopCoordinator<ConsoleKeyInfo> (()=>new NetInput (),
+                                                                   ()=>new NetOutput (),
+                                                                   loop);
         }
 
+        // Register the event handler for Ctrl+C
+        Console.CancelKeyPress += (s,e)=>
+                                  {
+                                      e.Cancel = true;
+                                      coordinator.Stop ();
+                                  };
+
+        coordinator.StartBlocking ();
     }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 3acc92157b..8eae1ea0b5 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -44,7 +44,7 @@ public void Run (CancellationToken token)
     /// <inheritdoc />
     public void Iteration ()
     {
-
+        Out.Write ("h");
     }
     /// <inheritdoc />
     public void Dispose ()
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index 0559849cde..3f5a0c940a 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -1,36 +1,73 @@
-namespace Terminal.Gui;
+using System.Collections.Concurrent;
 
-public class MainLoopCoordinator<T>
+namespace Terminal.Gui;
+
+public class MainLoopCoordinator<T> : IMainLoopCoordinator
 {
-    private readonly IConsoleInput<T> _input;
+    private readonly Func<IConsoleInput<T>> _inputFactory;
     private readonly IMainLoop<T> _loop;
     private CancellationTokenSource tokenSource = new ();
+    private readonly Func<IConsoleOutput> _outputFactory;
 
-    public MainLoopCoordinator (IConsoleInput<T> input, IMainLoop<T> loop)
+    /// <summary>
+    /// Creates a new coordinator
+    /// </summary>
+    /// <param name="inputFactory">Function to create a new input. This must call <see langword="new"/>
+    /// explicitly and cannot return an existing instance. This requirement arises because Windows
+    /// console screen buffer APIs are thread-specific for certain operations.</param>
+    /// <param name="outputFactory">Function to create a new output. This must call <see langword="new"/>
+    /// explicitly and cannot return an existing instance. This requirement arises because Windows
+    /// console screen buffer APIs are thread-specific for certain operations.</param>
+    /// <param name="loop"></param>
+    public MainLoopCoordinator (Func<IConsoleInput<T>> inputFactory, Func<IConsoleOutput> outputFactory, IMainLoop<T> loop)
     {
-        _input = input;
+        _inputFactory = inputFactory;
+        _outputFactory = outputFactory;
         _loop = loop;
     }
-    public void Start ()
+
+    /// <summary>
+    /// Starts the main and input loop threads in separate tasks (returning immediately).
+    /// </summary>
+    public void StartAsync ()
     {
         Task.Run (RunInput);
         Task.Run (RunLoop);
     }
-
+    /// <summary>
+    /// Starts the input thread and then enters the main loop in the current thread
+    /// (method only exits when application ends).
+    /// </summary>
+    public void StartBlocking ()
+    {
+        Task.Run (RunInput);
+        RunLoop();
+    }
     private void RunInput ()
     {
+        // Instance must be constructed on the thread in which it is used.
+        IConsoleInput<T> input = _inputFactory.Invoke ();
+
         try
         {
-            _input.Run (tokenSource.Token);
+            input.Run (tokenSource.Token);
         }
         catch (OperationCanceledException)
         {
         }
-        _input.Dispose ();
+        input.Dispose ();
     }
 
     private void RunLoop ()
     {
+        // Instance must be constructed on the thread in which it is used.
+        IConsoleOutput output = _outputFactory.Invoke ();
+
+        var parser = new AnsiResponseParser<T> ();
+        var buffer = new ConcurrentQueue<T> ();
+
+        _loop.Initialize (buffer, parser, output);
+
         try
         {
             _loop.Run (tokenSource.Token);
@@ -46,3 +83,11 @@ public void Stop ()
         tokenSource.Cancel();
     }
 }
+
+public interface IMainLoopCoordinator
+{
+    public void StartAsync ();
+    public void StartBlocking ();
+
+    public void Stop ();
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
index 1745785131..900abecc2f 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
@@ -10,7 +10,7 @@ public class NetOutput : IConsoleOutput
     /// <inheritdoc />
     public void Write (string text)
     {
-        Console.WriteLine (text);
+        Console.Write (text);
     }
 
     /// <inheritdoc />
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
index fde07e25c1..556753ce72 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
@@ -80,8 +80,10 @@ public WindowsOutput ()
     }
     public void Write (string str)
     {
-        WriteConsole (_screenBuffer, str, (uint)str.Length, out uint _, nint.Zero);
-
+        if (!WriteConsole (_screenBuffer, str, (uint)str.Length, out uint _, nint.Zero))
+        {
+            throw new Win32Exception (Marshal.GetLastWin32Error (), "Failed to write to console screen buffer.");
+        }
     }
 
     /// <inheritdoc />

From 4cd73c7fc369ee290ebac19f421b22be2abb1bce Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 21 Nov 2024 20:25:46 +0000
Subject: [PATCH 007/198] WIP output buffer

---
 .../ConsoleDrivers/V2/IConsoleOutput.cs       |   2 +-
 .../ConsoleDrivers/V2/IOutputBuffer.cs        |  20 +
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    |   2 +
 Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs   | 198 +++++++++
 .../ConsoleDrivers/V2/OutputBuffer.cs         | 405 ++++++++++++++++++
 Terminal.Gui/ConsoleDrivers/V2/V2.cd          |  40 +-
 .../ConsoleDrivers/V2/WindowsOutput.cs        |  97 ++++-
 7 files changed, 751 insertions(+), 13 deletions(-)
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs

diff --git a/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
index 996ba25c3a..2932cdac19 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
@@ -8,5 +8,5 @@ namespace Terminal.Gui;
 public interface IConsoleOutput : IDisposable
 {
     void Write(string text);
-
+    void Write (IOutputBuffer buffer);
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
new file mode 100644
index 0000000000..424b9dc5cb
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
@@ -0,0 +1,20 @@
+namespace Terminal.Gui;
+
+public interface IOutputBuffer
+{
+    int Rows { get;}
+    int Cols { get; }
+
+    /// <summary>
+    /// 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.
+    /// </summary>
+    public bool [] DirtyLines { get; }
+
+    /// <summary>
+    ///     The contents of the application output. The driver outputs this buffer to the terminal when
+    ///     UpdateScreen is called.
+    ///     <remarks>The format of the array is rows, columns. The first index is the row, the second index is the column.</remarks>
+    /// </summary>
+    public Cell [,] Contents { get;}
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 8eae1ea0b5..68ab97902f 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -6,6 +6,8 @@ public class MainLoop<T> : IMainLoop<T>
 {
     public ConcurrentQueue<T> InputBuffer { get; private set; } = new ();
 
+    public IOutputBuffer OutputBuffer { get; private set; } = new OutputBuffer();
+
     public AnsiResponseParser<T> Parser
     {
         get;
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
index 900abecc2f..8dbb745a88 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
@@ -3,16 +3,214 @@
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
+using Microsoft.VisualBasic;
 
 namespace Terminal.Gui;
 public class NetOutput : IConsoleOutput
 {
+    public bool IsWinPlatform { get; }
+
+    private CursorVisibility? _cachedCursorVisibility;
+    public NetOutput ()
+    {
+        PlatformID p = Environment.OSVersion.Platform;
+
+        if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
+        {
+            IsWinPlatform = true;
+        }
+    }
+
     /// <inheritdoc />
     public void Write (string text)
     {
         Console.Write (text);
     }
 
+    /// <inheritdoc />
+    public void Write (IOutputBuffer buffer)
+    {
+        bool updated = false;
+        if ( Console.WindowHeight < 1
+            || buffer.Contents.Length != buffer.Rows * buffer.Cols
+            || buffer.Rows != Console.WindowHeight)
+        {
+            return;
+        }
+
+        var top = 0;
+        var left = 0;
+        int rows = buffer.Rows;
+        int cols = buffer.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 (!buffer.DirtyLines [row])
+            {
+                continue;
+            }
+
+            if (!SetCursorPosition (0, row))
+            {
+                return;
+            }
+
+            buffer.DirtyLines [row] = false;
+            output.Clear ();
+
+            for (int col = left; col < cols; col++)
+            {
+                lastCol = -1;
+                var outputWidth = 0;
+
+                for (; col < cols; col++)
+                {
+                    if (!buffer.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 = buffer.Contents [row, col].Attribute.Value;
+
+                    // Performance: Only send the escape sequence if the attribute has changed.
+                    if (attr != redrawAttr)
+                    {
+                        redrawAttr = attr;
+
+                        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 = buffer.Contents [row, col].Rune;
+                    output.Append (rune);
+
+                    if (buffer.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);
+                    }
+
+                    buffer.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;
+    }
+    public bool SetCursorVisibility (CursorVisibility visibility)
+    {
+        _cachedCursorVisibility = visibility;
+
+        Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
+
+        return visibility == CursorVisibility.Default;
+    }
+    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;
+    }
+
     /// <inheritdoc />
     public void Dispose ()
     {
diff --git a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
new file mode 100644
index 0000000000..63c6662187
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
@@ -0,0 +1,405 @@
+using System.Diagnostics;
+
+namespace Terminal.Gui;
+
+/// <summary>
+/// Stores the desired output state for the whole application. This is updated during
+/// draw operations before being flushed to the console as part of <see cref="MainLoop{T}"/>
+/// operation
+/// </summary>
+public class OutputBuffer : IOutputBuffer
+{
+    /// <summary>
+    ///     The contents of the application output. The driver outputs this buffer to the terminal when
+    ///     UpdateScreen is called.
+    ///     <remarks>The format of the array is rows, columns. The first index is the row, the second index is the column.</remarks>
+    /// </summary>
+    public Cell [,] Contents { get; set; }
+
+    private Attribute _currentAttribute;
+    private int _cols;
+    private int _rows;
+
+    /// <summary>
+    ///     The <see cref="Attribute"/> that will be used for the next <see cref="AddRune(Rune)"/> or <see cref="AddStr"/>
+    ///     call.
+    /// </summary>
+    public Attribute CurrentAttribute
+    {
+        get => _currentAttribute;
+        set
+        {
+            // 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);
+
+                return;
+            }
+
+            _currentAttribute = value;
+        }
+    }
+
+    /// <summary>The leftmost column in the terminal.</summary>
+    internal virtual int Left { get; set; } = 0;
+
+    /// <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.
+    /// </summary>
+    internal int Row { get; private set; }
+
+
+    /// <summary>
+    ///     Gets the column 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.
+    /// </summary>
+    internal int Col { get; private set; }
+
+    /// <summary>The number of rows visible in the terminal.</summary>
+    public int Rows
+    {
+        get => _rows;
+        set
+        {
+            _rows = value;
+            ClearContents ();
+        }
+    }
+
+    /// <summary>The number of columns visible in the terminal.</summary>
+    public int Cols
+    {
+        get => _cols;
+        set
+        {
+            _cols = value;
+            ClearContents ();
+        }
+    }
+
+
+    /// <summary>The topmost row in the terminal.</summary>
+    internal virtual int Top { get; set; } = 0;
+
+
+    // 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.
+    public bool []? DirtyLines { get; set; }
+
+    // QUESTION: When non-full screen apps are supported, will this represent the app size, or will that be in Application?
+    /// <summary>Gets the location and size of the terminal screen.</summary>
+    internal Rectangle Screen => new (0, 0, Cols, Rows);
+
+    private Region? _clip = null;
+
+    /// <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>
+    internal Region? Clip
+    {
+        get => _clip;
+        set
+        {
+            if (_clip == value)
+            {
+                return;
+            }
+
+            _clip = value;
+
+            // Don't ever let Clip be bigger than Screen
+            if (_clip is { })
+            {
+                _clip.Intersect (Screen);
+            }
+        }
+    }
+
+    /// <summary>Adds the specified rune to the display at the current cursor position.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         When the method returns, <see cref="Col"/> will be incremented by the number of columns
+    ///         <paramref name="rune"/> required, even if the new column value is outside of the <see cref="Clip"/> or screen
+    ///         dimensions defined by <see cref="Cols"/>.
+    ///     </para>
+    ///     <para>
+    ///         If <paramref name="rune"/> requires more than one column, and <see cref="Col"/> plus the number of columns
+    ///         needed exceeds the <see cref="Clip"/> or screen dimensions, the default Unicode replacement character (U+FFFD)
+    ///         will be added instead.
+    ///     </para>
+    /// </remarks>
+    /// <param name="rune">Rune to add.</param>
+    internal void AddRune (Rune rune)
+    {
+        int runeWidth = -1;
+        bool validLocation = IsValidLocation (rune, Col, Row);
+
+        if (Contents is null)
+        {
+            return;
+        }
+
+        Rectangle clipRect = Clip!.GetBounds ();
+
+        if (validLocation)
+        {
+            rune = rune.MakePrintable ();
+            runeWidth = rune.GetColumns ();
+
+            lock (Contents)
+            {
+                if (runeWidth == 0 && rune.IsCombiningMark ())
+                {
+                    // 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 `[é  ]`.
+                    // 
+                    // Until this is addressed (see Issue #), we do our best by 
+                    // a) Attempting to normalize any CM with the base char to it's left
+                    // b) Ignoring any CMs that don't normalize
+                    if (Col > 0)
+                    {
+                        if (Contents [Row, Col - 1].CombiningMarks.Count > 0)
+                        {
+                            // Just add this mark to the list
+                            Contents [Row, Col - 1].CombiningMarks.Add (rune);
+
+                            // Ignore. Don't move to next column (let the driver figure out what to do).
+                        }
+                        else
+                        {
+                            // Attempt to normalize the cell to our left combined with this mark
+                            string combined = Contents [Row, Col - 1].Rune + rune.ToString ();
+
+                            // Normalize to Form C (Canonical Composition)
+                            string normalized = combined.Normalize (NormalizationForm.FormC);
+
+                            if (normalized.Length == 1)
+                            {
+                                // It normalized! We can just set the Cell to the left with the
+                                // normalized codepoint 
+                                Contents [Row, Col - 1].Rune = (Rune)normalized [0];
+
+                                // Ignore. Don't move to next column because we're already there
+                            }
+                            else
+                            {
+                                // It didn't normalize. Add it to the Cell to left's CM list
+                                Contents [Row, Col - 1].CombiningMarks.Add (rune);
+
+                                // Ignore. Don't move to next column (let the driver figure out what to do).
+                            }
+                        }
+
+                        Contents [Row, Col - 1].Attribute = CurrentAttribute;
+                        Contents [Row, Col - 1].IsDirty = true;
+                    }
+                    else
+                    {
+                        // Most drivers will render a combining mark at col 0 as the mark
+                        Contents [Row, Col].Rune = rune;
+                        Contents [Row, Col].Attribute = CurrentAttribute;
+                        Contents [Row, Col].IsDirty = true;
+                        Col++;
+                    }
+                }
+                else
+                {
+                    Contents [Row, Col].Attribute = CurrentAttribute;
+                    Contents [Row, Col].IsDirty = true;
+
+                    if (Col > 0)
+                    {
+                        // Check if cell to left has a wide glyph
+                        if (Contents [Row, Col - 1].Rune.GetColumns () > 1)
+                        {
+                            // Invalidate cell to left
+                            Contents [Row, Col - 1].Rune = Rune.ReplacementChar;
+                            Contents [Row, Col - 1].IsDirty = true;
+                        }
+                    }
+
+                    if (runeWidth < 1)
+                    {
+                        Contents [Row, Col].Rune = Rune.ReplacementChar;
+                    }
+                    else if (runeWidth == 1)
+                    {
+                        Contents [Row, Col].Rune = rune;
+
+                        if (Col < clipRect.Right - 1)
+                        {
+                            Contents [Row, Col + 1].IsDirty = true;
+                        }
+                    }
+                    else if (runeWidth == 2)
+                    {
+                        if (!Clip.Contains (Col + 1, Row))
+                        {
+                            // We're at the right edge of the clip, so we can't display a wide character.
+                            // TODO: Figure out if it is better to show a replacement character or ' '
+                            Contents [Row, Col].Rune = Rune.ReplacementChar;
+                        }
+                        else if (!Clip.Contains (Col, Row))
+                        {
+                            // Our 1st column is outside the clip, so we can't display a wide character.
+                            Contents [Row, Col + 1].Rune = Rune.ReplacementChar;
+                        }
+                        else
+                        {
+                            Contents [Row, Col].Rune = rune;
+
+                            if (Col < clipRect.Right - 1)
+                            {
+                                // Invalidate cell to right so that it doesn't get drawn
+                                // TODO: Figure out if it is better to show a replacement character or ' '
+                                Contents [Row, Col + 1].Rune = Rune.ReplacementChar;
+                                Contents [Row, Col + 1].IsDirty = true;
+                            }
+                        }
+                    }
+                    else
+                    {
+                        // This is a non-spacing character, so we don't need to do anything
+                        Contents [Row, Col].Rune = (Rune)' ';
+                        Contents [Row, Col].IsDirty = false;
+                    }
+
+                    DirtyLines! [Row] = true;
+                }
+            }
+        }
+
+        if (runeWidth is < 0 or > 0)
+        {
+            Col++;
+        }
+
+        if (runeWidth > 1)
+        {
+            Debug.Assert (runeWidth <= 2);
+
+            if (validLocation && Col < clipRect.Right)
+            {
+                lock (Contents!)
+                {
+                    // This is a double-width character, and we are not at the end of the line.
+                    // Col now points to the second column of the character. Ensure it doesn't
+                    // Get rendered.
+                    Contents [Row, Col].IsDirty = false;
+                    Contents [Row, Col].Attribute = CurrentAttribute;
+
+                    // TODO: Determine if we should wipe this out (for now now)
+                    //Contents [Row, Col].Rune = (Rune)' ';
+                }
+            }
+
+            Col++;
+        }
+    }
+
+    /// <summary>
+    ///     Adds the specified <see langword="char"/> to the display at the current cursor position. This method is a
+    ///     convenience method that calls <see cref="AddRune(Rune)"/> with the <see cref="Rune"/> constructor.
+    /// </summary>
+    /// <param name="c">Character to add.</param>
+    internal void AddRune (char c) { AddRune (new Rune (c)); }
+
+    /// <summary>Adds the <paramref name="str"/> to the display at the cursor position.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         When the method returns, <see cref="Col"/> will be incremented by the number of columns
+    ///         <paramref name="str"/> required, unless the new column value is outside of the <see cref="Clip"/> or screen
+    ///         dimensions defined by <see cref="Cols"/>.
+    ///     </para>
+    ///     <para>If <paramref name="str"/> requires more columns than are available, the output will be clipped.</para>
+    /// </remarks>
+    /// <param name="str">String.</param>
+    internal void AddStr (string str)
+    {
+        List<Rune> runes = str.EnumerateRunes ().ToList ();
+
+        for (var i = 0; i < runes.Count; i++)
+        {
+            AddRune (runes [i]);
+        }
+    }
+
+    /// <summary>Clears the <see cref="Contents"/> of the driver.</summary>
+    internal 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 = new (Screen);
+
+        DirtyLines = new bool [Rows];
+
+        lock (Contents)
+        {
+            for (var row = 0; row < Rows; row++)
+            {
+                for (var c = 0; c < Cols; c++)
+                {
+                    Contents [row, c] = new Cell
+                    {
+                        Rune = (Rune)' ',
+                        Attribute = new Attribute (Color.White, Color.Black),
+                        IsDirty = true
+                    };
+                }
+                DirtyLines [row] = true;
+            }
+        }
+
+        // TODO: Who uses this and why? I am removing for now - this class is a state class not an events class
+        //ClearedContents?.Invoke (this, EventArgs.Empty);
+    }
+    /// <summary>Tests whether the specified coordinate are valid for drawing the specified Rune.</summary>
+    /// <param name="rune">Used to determine if one or two columns are required.</param>
+    /// <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>
+    internal bool IsValidLocation (Rune rune, int col, int row)
+    {
+        if (rune.GetColumns () < 2)
+        {
+            return col >= 0 && row >= 0 && col < Cols && row < Rows && Clip!.Contains (col, row);
+        }
+        else
+        {
+
+            return Clip!.Contains (col, row) || Clip!.Contains (col + 1, row);
+        }
+    }
+
+    // TODO: Make internal once Menu is upgraded
+    /// <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;
+    }
+}
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index d263798613..51df3a4081 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -24,11 +24,26 @@
   </Class>
   <Class Name="Terminal.Gui.MainLoop&lt;T&gt;" Collapsed="true" BaseTypeListCollapsed="true">
     <Position X="9" Y="2.5" Width="1.5" />
+    <AssociationLine Name="Parser" Type="Terminal.Gui.AnsiResponseParser&lt;T&gt;" FixedFromPoint="true" FixedToPoint="true">
+      <Path>
+        <Point X="10.5" Y="2.688" />
+        <Point X="11.875" Y="2.688" />
+        <Point X="11.875" Y="1.691" />
+      </Path>
+    </AssociationLine>
+    <AssociationLine Name="Out" Type="Terminal.Gui.IConsoleOutput" FixedFromPoint="true" FixedToPoint="true">
+      <Path>
+        <Point X="10.5" Y="2.75" />
+        <Point X="14.25" Y="2.75" />
+        <Point X="14.25" Y="3.25" />
+      </Path>
+    </AssociationLine>
     <TypeIdentifier>
-      <HashCode>QQAgAAAAACAAAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAI=</HashCode>
+      <HashCode>QQAgAAAAACAAAQQAAAAAAAAAAAAAAAACAAAAAAAAAAI=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoop.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
+      <Property Name="OutputBuffer" />
       <Property Name="Parser" />
       <Property Name="Out" />
     </ShowAsAssociation>
@@ -37,21 +52,29 @@
   <Class Name="Terminal.Gui.MainLoopCoordinator&lt;T&gt;">
     <Position X="5.75" Y="1.75" Width="2" />
     <TypeIdentifier>
-      <HashCode>AAAAJAAAACIAAAAAAAAAAAAAAAAQAAQAIAAAAAAAAAA=</HashCode>
+      <HashCode>AAAAJAEAAAAAAAAAABAAAAAAAAAQIAQAIQAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoopCoordinator.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
-      <Field Name="_input" />
       <Field Name="_loop" />
     </ShowAsAssociation>
+    <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.AnsiResponseParser&lt;T&gt;" Collapsed="true">
-    <Position X="11.75" Y="1.75" Width="2" />
+    <Position X="11.25" Y="1" Width="2" />
     <TypeIdentifier>
       <HashCode>AAQAAAAAAAAACIAAAAAAAAAAAAAgAABAAAAAABAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
     </TypeIdentifier>
   </Class>
+  <Class Name="Terminal.Gui.OutputBuffer" Collapsed="true">
+    <Position X="11.25" Y="4.25" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AwAAAAAAAIAAAECIBgAEAIAAAAEIRgAACAAAKABAgAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\OutputBuffer.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
   <Interface Name="Terminal.Gui.IConsoleInput&lt;T&gt;">
     <Position X="9" Y="3.25" Width="1.5" />
     <TypeIdentifier>
@@ -67,11 +90,18 @@
     </TypeIdentifier>
   </Interface>
   <Interface Name="Terminal.Gui.IConsoleOutput" Collapsed="true">
-    <Position X="12.25" Y="3.25" Width="1.5" />
+    <Position X="13.5" Y="3.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IConsoleOutput.cs</FileName>
     </TypeIdentifier>
   </Interface>
+  <Interface Name="Terminal.Gui.IOutputBuffer" Collapsed="true">
+    <Position X="11.25" Y="3.25" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAAAEAAAAAAAIAAAAAABAAAAAAAAABAAAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\IOutputBuffer.cs</FileName>
+    </TypeIdentifier>
+  </Interface>
   <Font Name="Segoe UI" Size="9" />
 </ClassDiagram>
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
index 556753ce72..1e854e2688 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
@@ -1,10 +1,6 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
+using System.ComponentModel;
 using System.Runtime.InteropServices;
-using System.Text;
-using System.Threading.Tasks;
+using static Terminal.Gui.WindowsConsole;
 
 namespace Terminal.Gui;
 public class WindowsOutput : IConsoleOutput
@@ -52,6 +48,8 @@ private enum DesiredAccess : uint
 
     private nint _screenBuffer;
 
+    public WindowsConsole WinConsole { get; private set; }
+
     public WindowsOutput ()
     {
         _screenBuffer = CreateConsoleScreenBuffer (
@@ -76,7 +74,6 @@ public WindowsOutput ()
         {
             throw new Win32Exception (Marshal.GetLastWin32Error ());
         }
-
     }
     public void Write (string str)
     {
@@ -86,6 +83,92 @@ public void Write (string str)
         }
     }
 
+
+    public void Write (IOutputBuffer buffer)
+    {
+
+        var outputBuffer = new WindowsConsole.ExtendedCharInfo [buffer.Rows * buffer.Cols];
+
+        Size windowSize = WinConsole?.GetConsoleBufferWindow (out Point _) ?? new Size (buffer.Cols, buffer.Rows);
+
+        if (!windowSize.IsEmpty && (windowSize.Width != buffer.Cols || windowSize.Height != buffer.Rows))
+        {
+            return;
+        }
+
+        var bufferCoords = new WindowsConsole.Coord
+        {
+            X = (short)buffer.Cols, //Clip.Width,
+            Y = (short)buffer.Rows, //Clip.Height
+        };
+
+        for (var row = 0; row < buffer.Rows; row++)
+        {
+            if (!buffer.DirtyLines [row])
+            {
+                continue;
+            }
+
+            buffer.DirtyLines [row] = false;
+
+            for (var col = 0; col < buffer.Cols; col++)
+            {
+                int position = row * buffer.Cols + col;
+                outputBuffer [position].Attribute = buffer.Contents [row, col].Attribute.GetValueOrDefault ();
+
+                if (buffer.Contents [row, col].IsDirty == false)
+                {
+                    outputBuffer [position].Empty = true;
+                    outputBuffer [position].Char = (char)Rune.ReplacementChar.Value;
+
+                    continue;
+                }
+
+                outputBuffer [position].Empty = false;
+
+                if (buffer.Contents [row, col].Rune.IsBmp)
+                {
+                    outputBuffer [position].Char = (char)buffer.Contents [row, col].Rune.Value;
+                }
+                else
+                {
+                    //outputBuffer [position].Empty = true;
+                    outputBuffer [position].Char = (char)Rune.ReplacementChar.Value;
+
+                    if (buffer.Contents [row, col].Rune.GetColumns () > 1 && col + 1 < buffer.Cols)
+                    {
+                        // TODO: This is a hack to deal with non-BMP and wide characters.
+                        col++;
+                        position = row * buffer.Cols + col;
+                        outputBuffer [position].Empty = false;
+                        outputBuffer [position].Char = ' ';
+                    }
+                }
+            }
+        }
+
+        var damageRegion = new WindowsConsole.SmallRect
+        {
+            Top = 0,
+            Left = 0,
+            Bottom = (short)buffer.Rows,
+            Right = (short)buffer.Cols
+        };
+
+        if (WinConsole != null
+            && !WinConsole.WriteToConsole (new (buffer.Cols, buffer.Rows), outputBuffer, bufferCoords, damageRegion, false))
+        {
+            int err = Marshal.GetLastWin32Error ();
+
+            if (err != 0)
+            {
+                throw new Win32Exception (err);
+            }
+        }
+
+        WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
+    }
+
     /// <inheritdoc />
     public void Dispose ()
     {

From 0655f0da5f601738b491557ea6334482360dc6ca Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Fri, 22 Nov 2024 18:15:12 +0000
Subject: [PATCH 008/198] WIP try to exercise new buffer for hello world

---
 Drivers2/Program.cs                           |  3 +
 .../ConsoleDrivers/V2/IOutputBuffer.cs        | 62 ++++++++++++++++---
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    |  7 ++-
 .../ConsoleDrivers/V2/OutputBuffer.cs         | 18 ++++--
 Terminal.Gui/ConsoleDrivers/V2/V2.cd          | 26 +++++++-
 5 files changed, 100 insertions(+), 16 deletions(-)

diff --git a/Drivers2/Program.cs b/Drivers2/Program.cs
index 0f7785aae2..82f992ab4f 100644
--- a/Drivers2/Program.cs
+++ b/Drivers2/Program.cs
@@ -26,6 +26,9 @@ static void Main (string [] args)
             }
         }
 
+        // Required to set up colors etc?
+        Application.Init ();
+
         IMainLoopCoordinator coordinator;
         if (win)
         {
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
index 424b9dc5cb..23442dd14d 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
@@ -2,9 +2,6 @@
 
 public interface IOutputBuffer
 {
-    int Rows { get;}
-    int Cols { get; }
-
     /// <summary>
     /// 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.
@@ -12,9 +9,60 @@ public interface IOutputBuffer
     public bool [] DirtyLines { get; }
 
     /// <summary>
-    ///     The contents of the application output. The driver outputs this buffer to the terminal when
-    ///     UpdateScreen is called.
-    ///     <remarks>The format of the array is rows, columns. The first index is the row, the second index is the column.</remarks>
+    /// The contents of the application output. The driver outputs this buffer to the terminal when UpdateScreen is called.
+    /// </summary>
+    Cell [,] Contents { get; set; }
+
+    /// <summary>
+    /// The <see cref="Attribute"/> that will be used for the next AddRune or AddStr call.
+    /// </summary>
+    Attribute CurrentAttribute { get; set; }
+
+    /// <summary>The number of rows visible in the terminal.</summary>
+    int Rows { get; set; }
+
+    /// <summary>The number of columns visible in the terminal.</summary>
+    int Cols { get; set; }
+
+    /// <summary>
+    /// Updates the column and row to the specified location in the buffer.
+    /// </summary>
+    /// <param name="col">The column to move to.</param>
+    /// <param name="row">The row to move to.</param>
+    void Move (int col, int row);
+
+    /// <summary>Adds the specified rune to the display at the current cursor position.</summary>
+    /// <param name="rune">Rune to add.</param>
+    void AddRune (Rune rune);
+
+    /// <summary>
+    /// Adds the specified character to the display at the current cursor position. This is a convenience method for AddRune.
+    /// </summary>
+    /// <param name="c">Character to add.</param>
+    void AddRune (char c);
+
+    /// <summary>Adds the string to the display at the current cursor position.</summary>
+    /// <param name="str">String to add.</param>
+    void AddStr (string str);
+
+    /// <summary>Clears the contents of the buffer.</summary>
+    void ClearContents ();
+
+    /// <summary>
+    /// Tests whether the specified coordinate is valid for drawing the specified Rune.
+    /// </summary>
+    /// <param name="rune">Used to determine if one or two columns are required.</param>
+    /// <param name="col">The column.</param>
+    /// <param name="row">The row.</param>
+    /// <returns>
+    /// True if the coordinate is valid for the Rune; false otherwise.
+    /// </returns>
+    bool IsValidLocation (Rune rune, int col, int row);
+
+    /// <summary>
+    /// Changes the size of the buffer to the given size
     /// </summary>
-    public Cell [,] Contents { get;}
+    /// <param name="cols"></param>
+    /// <param name="rows"></param>
+    void SetWindowSize (int cols, int rows);
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 68ab97902f..1bf9a2b9c4 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -46,7 +46,12 @@ public void Run (CancellationToken token)
     /// <inheritdoc />
     public void Iteration ()
     {
-        Out.Write ("h");
+        OutputBuffer.SetWindowSize (20, 10);
+
+        OutputBuffer.CurrentAttribute = new Attribute (Color.White, Color.Black);
+        OutputBuffer.Move (5, 3);
+        OutputBuffer.AddStr ("Hello World!");
+        Out.Write (OutputBuffer);
     }
     /// <inheritdoc />
     public void Dispose ()
diff --git a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
index 63c6662187..90c8998383 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
@@ -133,7 +133,7 @@ internal Region? Clip
     ///     </para>
     /// </remarks>
     /// <param name="rune">Rune to add.</param>
-    internal void AddRune (Rune rune)
+    public void AddRune (Rune rune)
     {
         int runeWidth = -1;
         bool validLocation = IsValidLocation (rune, Col, Row);
@@ -308,7 +308,7 @@ internal void AddRune (Rune rune)
     ///     convenience method that calls <see cref="AddRune(Rune)"/> with the <see cref="Rune"/> constructor.
     /// </summary>
     /// <param name="c">Character to add.</param>
-    internal void AddRune (char c) { AddRune (new Rune (c)); }
+    public void AddRune (char c) { AddRune (new Rune (c)); }
 
     /// <summary>Adds the <paramref name="str"/> to the display at the cursor position.</summary>
     /// <remarks>
@@ -320,7 +320,7 @@ internal void AddRune (Rune rune)
     ///     <para>If <paramref name="str"/> requires more columns than are available, the output will be clipped.</para>
     /// </remarks>
     /// <param name="str">String.</param>
-    internal void AddStr (string str)
+    public void AddStr (string str)
     {
         List<Rune> runes = str.EnumerateRunes ().ToList ();
 
@@ -331,7 +331,7 @@ internal void AddStr (string str)
     }
 
     /// <summary>Clears the <see cref="Contents"/> of the driver.</summary>
-    internal void ClearContents ()
+    public void ClearContents ()
     {
         Contents = new Cell [Rows, Cols];
 
@@ -369,7 +369,7 @@ internal void ClearContents ()
     ///     <see langword="false"/> if the coordinate is outside the screen bounds or outside of <see cref="Clip"/>.
     ///     <see langword="true"/> otherwise.
     /// </returns>
-    internal bool IsValidLocation (Rune rune, int col, int row)
+    public bool IsValidLocation (Rune rune, int col, int row)
     {
         if (rune.GetColumns () < 2)
         {
@@ -382,6 +382,14 @@ internal bool IsValidLocation (Rune rune, int col, int row)
         }
     }
 
+    /// <inheritdoc />
+    public void SetWindowSize (int cols, int rows)
+    {
+        Cols = cols;
+        Rows = rows;
+        ClearContents ();
+    }
+
     // TODO: Make internal once Menu is upgraded
     /// <summary>
     ///     Updates <see cref="Col"/> and <see cref="Row"/> to the specified column and row in <see cref="Contents"/>.
diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index 51df3a4081..4f3718c4d5 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -1,14 +1,14 @@
 <?xml version="1.0" encoding="utf-8"?>
 <ClassDiagram MajorVersion="1" MinorVersion="1">
   <Class Name="Terminal.Gui.WindowsInput" Collapsed="true">
-    <Position X="7.25" Y="7.5" Width="2.25" />
+    <Position X="7.25" Y="8.25" Width="2.25" />
     <TypeIdentifier>
       <HashCode>QAAAAAAAACAEAAAAAAAAAAAkAAAAAAAAAgAAAAAAABA=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsInput.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.NetInput" Collapsed="true">
-    <Position X="10.25" Y="7.5" Width="2" />
+    <Position X="10.25" Y="8.25" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAEAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\NetInput.cs</FileName>
@@ -67,14 +67,34 @@
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.OutputBuffer" Collapsed="true">
+  <Class Name="Terminal.Gui.OutputBuffer">
     <Position X="11.25" Y="4.25" Width="1.5" />
+    <Compartments>
+      <Compartment Name="Fields" Collapsed="true" />
+      <Compartment Name="Methods" Collapsed="true" />
+    </Compartments>
     <TypeIdentifier>
       <HashCode>AwAAAAAAAIAAAECIBgAEAIAAAAEIRgAACAAAKABAgAA=</HashCode>
       <FileName>ConsoleDrivers\V2\OutputBuffer.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
+  <Class Name="Terminal.Gui.NetOutput" Collapsed="true">
+    <Position X="14.75" Y="4.25" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AEAAAAAAACAAAAAAAAAAAAAAAQAAAAAAMACAAAEAAAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\NetOutput.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Class Name="Terminal.Gui.WindowsOutput" Collapsed="true">
+    <Position X="13.25" Y="4.25" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAABAAACAAhAAACAAAACAAAAAAAAAIAAAAAAEAAAQ=</HashCode>
+      <FileName>ConsoleDrivers\V2\WindowsOutput.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
   <Interface Name="Terminal.Gui.IConsoleInput&lt;T&gt;">
     <Position X="9" Y="3.25" Width="1.5" />
     <TypeIdentifier>

From aaef87d5609d73e77258f21f9dd917e774bd6fdc Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Fri, 22 Nov 2024 18:37:00 +0000
Subject: [PATCH 009/198] Hello world appeared!

---
 Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
index 1e854e2688..4621fc166f 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
@@ -48,7 +48,7 @@ private enum DesiredAccess : uint
 
     private nint _screenBuffer;
 
-    public WindowsConsole WinConsole { get; private set; }
+    public WindowsConsole WinConsole { get; private set; } = new WindowsConsole ();
 
     public WindowsOutput ()
     {

From ea3468ebe2f1b6102d33bddce8395c4e43cecccd Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Fri, 22 Nov 2024 18:57:00 +0000
Subject: [PATCH 010/198] Rainbows!

---
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs | 51 +++++++++++++++++++++-
 1 file changed, 50 insertions(+), 1 deletion(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 1bf9a2b9c4..b2d0f54b5f 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -50,7 +50,56 @@ public void Iteration ()
 
         OutputBuffer.CurrentAttribute = new Attribute (Color.White, Color.Black);
         OutputBuffer.Move (5, 3);
-        OutputBuffer.AddStr ("Hello World!");
+
+        // Red
+        OutputBuffer.CurrentAttribute = new Attribute (new Color (255, 0, 0), Color.Black);
+        OutputBuffer.AddRune ('H');
+
+        // Orange
+        OutputBuffer.CurrentAttribute = new Attribute (new Color (255, 165, 0), Color.Black);
+        OutputBuffer.AddRune ('e');
+
+        // Yellow
+        OutputBuffer.CurrentAttribute = new Attribute (new Color (255, 255, 0), Color.Black);
+        OutputBuffer.AddRune ('l');
+
+        // Green
+        OutputBuffer.CurrentAttribute = new Attribute (new Color (0, 255, 0), Color.Black);
+        OutputBuffer.AddRune ('l');
+
+        // Blue
+        OutputBuffer.CurrentAttribute = new Attribute (new Color (100, 100, 255), Color.Black);
+        OutputBuffer.AddRune ('o');
+
+        // Indigo
+        OutputBuffer.CurrentAttribute = new Attribute (new Color (75, 0, 130), Color.Black);
+        OutputBuffer.AddRune (' ');
+
+        // Violet
+        OutputBuffer.CurrentAttribute = new Attribute (new Color (238, 130, 238), Color.Black);
+        OutputBuffer.AddRune ('W');
+
+        // Red
+        OutputBuffer.CurrentAttribute = new Attribute (new Color (255, 0, 0), Color.Black);
+        OutputBuffer.AddRune ('o');
+
+        // Orange
+        OutputBuffer.CurrentAttribute = new Attribute (new Color (255, 165, 0), Color.Black);
+        OutputBuffer.AddRune ('r');
+
+        // Yellow
+        OutputBuffer.CurrentAttribute = new Attribute (new Color (255, 255, 0), Color.Black);
+        OutputBuffer.AddRune ('l');
+
+        // Green
+        OutputBuffer.CurrentAttribute = new Attribute (new Color (0, 255, 0), Color.Black);
+        OutputBuffer.AddRune ('d');
+
+        // Blue
+        OutputBuffer.CurrentAttribute = new Attribute (new Color (100, 100, 255), Color.Black);
+        OutputBuffer.AddRune ('!');
+
+
         Out.Write (OutputBuffer);
     }
     /// <inheritdoc />

From d98a841847f2ea312ddbb05e7c1e997449558116 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Fri, 22 Nov 2024 18:59:49 +0000
Subject: [PATCH 011/198] suppress suspicious check

---
 Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
index 8dbb745a88..133465c997 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
@@ -35,7 +35,7 @@ public void Write (IOutputBuffer buffer)
             || buffer.Contents.Length != buffer.Rows * buffer.Cols
             || buffer.Rows != Console.WindowHeight)
         {
-            return;
+       //     return;
         }
 
         var top = 0;

From 584a2abef8c3fb2144923d67d81d168c53f0f0d7 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Fri, 22 Nov 2024 19:11:49 +0000
Subject: [PATCH 012/198] Add props and random render for fun

---
 Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs | 12 ++++++++++++
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs      |  3 ++-
 Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs  |  4 ++--
 3 files changed, 16 insertions(+), 3 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
index 23442dd14d..7318815c0f 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
@@ -24,6 +24,18 @@ public interface IOutputBuffer
     /// <summary>The number of columns visible in the terminal.</summary>
     int Cols { get; set; }
 
+    /// <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.
+    /// </summary>
+    public int Row { get;}
+
+
+    /// <summary>
+    ///     Gets the column 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.
+    /// </summary>
+    public int Col { get; }
     /// <summary>
     /// Updates the column and row to the specified location in the buffer.
     /// </summary>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index b2d0f54b5f..34090a60a8 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -46,10 +46,11 @@ public void Run (CancellationToken token)
     /// <inheritdoc />
     public void Iteration ()
     {
+        Random r = new Random ();
         OutputBuffer.SetWindowSize (20, 10);
 
         OutputBuffer.CurrentAttribute = new Attribute (Color.White, Color.Black);
-        OutputBuffer.Move (5, 3);
+        OutputBuffer.Move (r.Next(10), r.Next (10));
 
         // Red
         OutputBuffer.CurrentAttribute = new Attribute (new Color (255, 0, 0), Color.Black);
diff --git a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
index 90c8998383..4d792b3269 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
@@ -48,14 +48,14 @@ public Attribute CurrentAttribute
     ///     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.
     /// </summary>
-    internal int Row { get; private set; }
+    public int Row { get; private set; }
 
 
     /// <summary>
     ///     Gets the column 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.
     /// </summary>
-    internal int Col { get; private set; }
+    public int Col { get; private set; }
 
     /// <summary>The number of rows visible in the terminal.</summary>
     public int Rows

From c6b9c28077652132d8b36208f3d11e607a19c6df Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Fri, 22 Nov 2024 21:01:29 +0000
Subject: [PATCH 013/198] Starting on input processing

---
 Drivers2/Properties/launchSettings.json       |   3 +-
 .../ConsoleDrivers/ConsoleKeyMapping.cs       | 131 ++++++++++++++++++
 Terminal.Gui/ConsoleDrivers/NetDriver.cs      | 100 -------------
 .../ConsoleDrivers/V2/IInputProcessor.cs      |  46 ++++++
 Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs   |   7 +-
 .../ConsoleDrivers/V2/InputProcessor.cs       |  73 ++++++++++
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    |  82 ++++-------
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  |   9 +-
 Terminal.Gui/ConsoleDrivers/V2/NetInput.cs    |  36 ++++-
 Terminal.Gui/ConsoleDrivers/V2/V2.cd          |  89 +++++++-----
 10 files changed, 367 insertions(+), 209 deletions(-)
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs

diff --git a/Drivers2/Properties/launchSettings.json b/Drivers2/Properties/launchSettings.json
index d8f3b5caa3..c8c054cdba 100644
--- a/Drivers2/Properties/launchSettings.json
+++ b/Drivers2/Properties/launchSettings.json
@@ -1,8 +1,7 @@
 {
   "profiles": {
     "Drivers2": {
-      "commandName": "Project",
-      "commandLineArgs": "win"
+      "commandName": "Project"
     }
   }
 }
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs
index c3497f3acd..b32e7b2eac 100644
--- a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs
+++ b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs
@@ -711,6 +711,137 @@ internal static uint MapKeyCodeToConsoleKey (KeyCode keyValue, out bool isConsol
         return (uint)keyValue;
     }
 
+    public static KeyCode MapKey (ConsoleKeyInfo keyInfo)
+    {
+        // TODO: I Copied this from NetDriver and made it static because it is about ConsoleKeyMapping so belongs here
+        //       However there is a separate version in FakeDriver ?! Is that just an older version of the same code?!
+
+        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));
+    }
+
+    public static Key ToKey<T> (T result)
+    {
+        // TODO: Requires testing
+        return result switch
+               {
+                   ConsoleKeyInfo keyInfo =>MapKey (keyInfo),
+
+                   // TODO: how?
+                   // WindowsConsole.InputRecord inputRecord => inputRecord.KeyEvent.UnicodeChar,
+                   _ => throw new ArgumentException ($"Unsupported type {typeof (T).Name}")
+               };
+    }
+
+    public static char ToChar<T> (T result)
+    {
+        // TODO: Requires testing
+        return result switch
+               {
+                   ConsoleKeyInfo keyInfo => keyInfo.KeyChar,
+                   Key key => (char)key,
+
+                   // TODO: probably not that simple
+                   WindowsConsole.InputRecord inputRecord => inputRecord.KeyEvent.UnicodeChar,
+                   _ => throw new ArgumentException ($"Unsupported type {typeof (T).Name}")
+               };
+    }
+
     /// <summary>Maps a <see cref="ConsoleKeyInfo"/> to a <see cref="KeyCode"/>.</summary>
     /// <param name="consoleKeyInfo">The console key.</param>
     /// <returns>The <see cref="KeyCode"/> or the <paramref name="consoleKeyInfo"/>.</returns>
diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs
index 6f54bac1bd..8413b1d9f0 100644
--- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs
@@ -1528,106 +1528,6 @@ private ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyI
         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
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
new file mode 100644
index 0000000000..2f28f2068f
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
@@ -0,0 +1,46 @@
+
+namespace Terminal.Gui;
+
+
+public interface IInputProcessor
+{
+    /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="KeyUp"/>.</summary>
+    event EventHandler<Key>? KeyDown;
+
+    /// <summary>Event fired when a key is released.</summary>
+    /// <remarks>
+    ///     Drivers that do not support key release events will fire this event after <see cref="KeyDown"/> processing is complete.
+    /// </remarks>
+    event EventHandler<Key>? KeyUp;
+
+    /// <summary>Event fired when a mouse event occurs.</summary>
+    event EventHandler<MouseEventArgs>? MouseEvent;
+
+    /// <summary>
+    ///     Called when a key is pressed down. Fires the <see cref="KeyDown"/> event. This is a precursor to
+    ///     <see cref="OnKeyUp"/>.
+    /// </summary>
+    /// <param name="key">The key event data.</param>
+    void OnKeyDown (Key key);
+
+    /// <summary>
+    ///     Called when a key is released. Fires the <see cref="KeyUp"/> event.
+    /// </summary>
+    /// <remarks>
+    ///     Drivers that do not support key release events will call this method after <see cref="OnKeyDown"/> processing
+    ///     is complete.
+    /// </remarks>
+    /// <param name="key">The key event data.</param>
+    void OnKeyUp (Key key);
+
+    /// <summary>
+    ///     Called when a mouse event occurs. Fires the <see cref="MouseEvent"/> event.
+    /// </summary>
+    /// <param name="mouseEventArgs">The mouse event data.</param>
+    void OnMouseEvent (MouseEventArgs mouseEventArgs);
+
+    /// <summary>
+    /// Drains the input buffer, processing all available keystrokes
+    /// </summary>
+    void ProcessQueue ();
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
index 2a690f9433..4411c17ecf 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
@@ -1,16 +1,19 @@
 using System.Collections.Concurrent;
+using Terminal.Gui.ConsoleDrivers.V2;
 
 namespace Terminal.Gui;
 
 public interface IMainLoop<T> : IDisposable
 {
+
+    public IInputProcessor InputProcessor { get; }
+
     /// <summary>
     /// Initializes the loop with a buffer from which data can be read
     /// </summary>
     /// <param name="inputBuffer"></param>
-    /// <param name="parser"></param>
     /// <param name="consoleOutput"></param>
-    void Initialize (ConcurrentQueue<T> inputBuffer, AnsiResponseParser<T> parser, IConsoleOutput consoleOutput);
+    void Initialize (ConcurrentQueue<T> inputBuffer, IConsoleOutput consoleOutput);
 
     /// <summary>
     /// Runs <see cref="Iteration"/> in an infinite loop.
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
new file mode 100644
index 0000000000..3f0ae2ee9c
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -0,0 +1,73 @@
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui.ConsoleDrivers.V2;
+
+public class InputProcessor<T> : IInputProcessor
+{
+    public AnsiResponseParser<T> Parser { get; } = new ();
+    public ConcurrentQueue<T> InputBuffer { get; }
+
+    /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="KeyUp"/>.</summary>
+    public event EventHandler<Key>? KeyDown;
+
+    /// <summary>
+    ///     Called when a key is pressed down. Fires the <see cref="KeyDown"/> event. This is a precursor to
+    ///     <see cref="OnKeyUp"/>.
+    /// </summary>
+    /// <param name="a"></param>
+    public void OnKeyDown (Key a) { KeyDown?.Invoke (this, a); }
+
+    /// <summary>Event fired when a key is released.</summary>
+    /// <remarks>
+    ///     Drivers that do not support key release events will fire this event after <see cref="KeyDown"/> processing is
+    ///     complete.
+    /// </remarks>
+    public event EventHandler<Key>? KeyUp;
+
+    /// <summary>Called when a key is released. Fires the <see cref="KeyUp"/> event.</summary>
+    /// <remarks>
+    ///     Drivers that do not support key release events will call this method after <see cref="OnKeyDown"/> processing
+    ///     is complete.
+    /// </remarks>
+    /// <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);
+    }
+
+    public InputProcessor ( ConcurrentQueue<T> inputBuffer)
+    {
+        InputBuffer = inputBuffer;
+
+        // TODO: For now handle all escape codes with ignore - later add mouse etc
+        Parser.UnexpectedResponseHandler = (str) => { return true; };
+    }
+
+    /// <summary>
+    /// Drains the <see cref="InputBuffer"/> buffer, processing all available keystrokes
+    /// </summary>
+    public void ProcessQueue ()
+    {
+        // TODO: Esc timeout etc
+
+        while (InputBuffer.TryDequeue (out var input))
+        {
+            foreach (var released in Parser.ProcessInput (Tuple.Create (ConsoleKeyMapping.ToChar (input), input)))
+            {
+                Key key = ConsoleKeyMapping.ToKey (released.Item2);
+                OnKeyDown (key);
+                OnKeyUp (key);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 34090a60a8..899525734b 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -1,4 +1,7 @@
 using System.Collections.Concurrent;
+using Terminal.Gui.ConsoleDrivers;
+using Terminal.Gui.ConsoleDrivers.V2;
+using static Terminal.Gui.WindowsConsole;
 
 namespace Terminal.Gui;
 
@@ -6,22 +9,24 @@ public class MainLoop<T> : IMainLoop<T>
 {
     public ConcurrentQueue<T> InputBuffer { get; private set; } = new ();
 
-    public IOutputBuffer OutputBuffer { get; private set; } = new OutputBuffer();
+    public IInputProcessor InputProcessor { get; private set; }
 
-    public AnsiResponseParser<T> Parser
-    {
-        get;
-        private set;
-    }
+    public IOutputBuffer OutputBuffer { get; private set; } = new OutputBuffer();
 
     public IConsoleOutput Out { get;private set; }
 
-    /// <inheritdoc />
-    public void Initialize (ConcurrentQueue<T> inputBuffer, AnsiResponseParser<T> parser, IConsoleOutput consoleOutput)
+
+    // TODO: Remove later
+    StringBuilder sb = new StringBuilder ();
+
+    public void Initialize (ConcurrentQueue<T> inputBuffer, IConsoleOutput consoleOutput)
     {
         InputBuffer = inputBuffer;
-        Parser = parser;
         Out = consoleOutput;
+        InputProcessor = new InputProcessor<T> (inputBuffer);
+
+        // TODO: Remove later
+        InputProcessor.KeyDown += (s,k) => sb.Append (ConsoleKeyMapping.ToChar (k));
     }
 
 
@@ -46,63 +51,24 @@ public void Run (CancellationToken token)
     /// <inheritdoc />
     public void Iteration ()
     {
+        InputProcessor.ProcessQueue ();
+
+
         Random r = new Random ();
         OutputBuffer.SetWindowSize (20, 10);
 
         OutputBuffer.CurrentAttribute = new Attribute (Color.White, Color.Black);
-        OutputBuffer.Move (r.Next(10), r.Next (10));
-
-        // Red
-        OutputBuffer.CurrentAttribute = new Attribute (new Color (255, 0, 0), Color.Black);
-        OutputBuffer.AddRune ('H');
-
-        // Orange
-        OutputBuffer.CurrentAttribute = new Attribute (new Color (255, 165, 0), Color.Black);
-        OutputBuffer.AddRune ('e');
-
-        // Yellow
-        OutputBuffer.CurrentAttribute = new Attribute (new Color (255, 255, 0), Color.Black);
-        OutputBuffer.AddRune ('l');
-
-        // Green
-        OutputBuffer.CurrentAttribute = new Attribute (new Color (0, 255, 0), Color.Black);
-        OutputBuffer.AddRune ('l');
-
-        // Blue
-        OutputBuffer.CurrentAttribute = new Attribute (new Color (100, 100, 255), Color.Black);
-        OutputBuffer.AddRune ('o');
-
-        // Indigo
-        OutputBuffer.CurrentAttribute = new Attribute (new Color (75, 0, 130), Color.Black);
-        OutputBuffer.AddRune (' ');
-
-        // Violet
-        OutputBuffer.CurrentAttribute = new Attribute (new Color (238, 130, 238), Color.Black);
-        OutputBuffer.AddRune ('W');
-
-        // Red
-        OutputBuffer.CurrentAttribute = new Attribute (new Color (255, 0, 0), Color.Black);
-        OutputBuffer.AddRune ('o');
-
-        // Orange
-        OutputBuffer.CurrentAttribute = new Attribute (new Color (255, 165, 0), Color.Black);
-        OutputBuffer.AddRune ('r');
-
-        // Yellow
-        OutputBuffer.CurrentAttribute = new Attribute (new Color (255, 255, 0), Color.Black);
-        OutputBuffer.AddRune ('l');
-
-        // Green
-        OutputBuffer.CurrentAttribute = new Attribute (new Color (0, 255, 0), Color.Black);
-        OutputBuffer.AddRune ('d');
-
-        // Blue
-        OutputBuffer.CurrentAttribute = new Attribute (new Color (100, 100, 255), Color.Black);
-        OutputBuffer.AddRune ('!');
+        OutputBuffer.Move (0,0);
 
+        foreach (var ch in sb.ToString())
+        {
+            OutputBuffer.CurrentAttribute = new Attribute (new Color (r.Next (255), r.Next (255), r.Next (255)), Color.Black);
+            OutputBuffer.AddRune (ch);
+        }
 
         Out.Write (OutputBuffer);
     }
+
     /// <inheritdoc />
     public void Dispose ()
     { // TODO release managed resources here
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index 3f5a0c940a..5d18ffd4af 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -9,6 +9,8 @@ public class MainLoopCoordinator<T> : IMainLoopCoordinator
     private CancellationTokenSource tokenSource = new ();
     private readonly Func<IConsoleOutput> _outputFactory;
 
+    ConcurrentQueue<T> _inputBuffer = new ();
+
     /// <summary>
     /// Creates a new coordinator
     /// </summary>
@@ -47,7 +49,7 @@ private void RunInput ()
     {
         // Instance must be constructed on the thread in which it is used.
         IConsoleInput<T> input = _inputFactory.Invoke ();
-
+        input.Initialize (_inputBuffer);
         try
         {
             input.Run (tokenSource.Token);
@@ -63,10 +65,7 @@ private void RunLoop ()
         // Instance must be constructed on the thread in which it is used.
         IConsoleOutput output = _outputFactory.Invoke ();
 
-        var parser = new AnsiResponseParser<T> ();
-        var buffer = new ConcurrentQueue<T> ();
-
-        _loop.Initialize (buffer, parser, output);
+        _loop.Initialize (_inputBuffer, output);
 
         try
         {
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
index f7ec9e09d7..ed39fc866b 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
@@ -1,15 +1,43 @@
-using System.Collections.Concurrent;
-
-namespace Terminal.Gui;
+namespace Terminal.Gui;
 
 public class NetInput : ConsoleInput<ConsoleKeyInfo>
 {
+    private NetWinVTConsole _adjustConsole;
+
+    public NetInput ()
+    {
+        var p = Environment.OSVersion.Platform;
+        if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
+        {
+            try
+            {
+                _adjustConsole = new NetWinVTConsole ();
+            }
+            catch (ApplicationException)
+            {
+                // Likely running as a unit test, or in a non-interactive session.
+            }
+        }
+
+        // Doesn't seem to work
+        Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
+    }
     /// <inheritdoc />
     protected override bool Peek () => Console.KeyAvailable;
 
     /// <inheritdoc />
     protected override IEnumerable<ConsoleKeyInfo> Read ()
     {
-        return [Console.ReadKey (true)];
+        while (Console.KeyAvailable)
+        {
+            yield return Console.ReadKey (true);
+        }
+    }
+
+    /// <inheritdoc />
+    public override void Dispose ()
+    {
+        base.Dispose ();
+        _adjustConsole?.Cleanup ();
     }
 }
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index 4f3718c4d5..d758bc6168 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -1,21 +1,30 @@
 <?xml version="1.0" encoding="utf-8"?>
 <ClassDiagram MajorVersion="1" MinorVersion="1">
+  <Comment CommentText="Thread 1 - Input thread, populates input buffer.  This thread is hidden, nobody gets to interact directly with these classes)">
+    <Position X="10" Y="0.5" Height="0.5" Width="5.325" />
+  </Comment>
+  <Comment CommentText="Thread 2 - Main Loop which does everything else including output.  Deals with input exclusively through the input buffer. Is accessible externally e.g. to Application">
+    <Position X="10.083" Y="3.813" Height="0.479" Width="5.325" />
+  </Comment>
+  <Comment CommentText="Orchestrates the 2 main threads in Terminal.Gui">
+    <Position X="5" Y="0.5" Height="0.291" Width="2.929" />
+  </Comment>
   <Class Name="Terminal.Gui.WindowsInput" Collapsed="true">
-    <Position X="7.25" Y="8.25" Width="2.25" />
+    <Position X="10.5" Y="3" Width="1.75" />
     <TypeIdentifier>
       <HashCode>QAAAAAAAACAEAAAAAAAAAAAkAAAAAAAAAgAAAAAAABA=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsInput.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.NetInput" Collapsed="true">
-    <Position X="10.25" Y="8.25" Width="2" />
+    <Position X="12.25" Y="3" Width="2" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAEAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <HashCode>AAAAAAAAACAEAAAAQAAAAAAgAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\NetInput.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.ConsoleInput&lt;T&gt;">
-    <Position X="9" Y="5" Width="1.5" />
+  <Class Name="Terminal.Gui.ConsoleInput&lt;T&gt;" Collapsed="true">
+    <Position X="11.5" Y="2" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAACAEAQAAAAAAAAAgAAAAAAAAAAAAAAAAAAo=</HashCode>
       <FileName>ConsoleDrivers\V2\ConsoleInput.cs</FileName>
@@ -23,36 +32,22 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.MainLoop&lt;T&gt;" Collapsed="true" BaseTypeListCollapsed="true">
-    <Position X="9" Y="2.5" Width="1.5" />
-    <AssociationLine Name="Parser" Type="Terminal.Gui.AnsiResponseParser&lt;T&gt;" FixedFromPoint="true" FixedToPoint="true">
-      <Path>
-        <Point X="10.5" Y="2.688" />
-        <Point X="11.875" Y="2.688" />
-        <Point X="11.875" Y="1.691" />
-      </Path>
-    </AssociationLine>
-    <AssociationLine Name="Out" Type="Terminal.Gui.IConsoleOutput" FixedFromPoint="true" FixedToPoint="true">
-      <Path>
-        <Point X="10.5" Y="2.75" />
-        <Point X="14.25" Y="2.75" />
-        <Point X="14.25" Y="3.25" />
-      </Path>
-    </AssociationLine>
+    <Position X="10" Y="4.75" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>QQAgAAAAACAAAQQAAAAAAAAAAAAAAAACAAAAAAAAAAI=</HashCode>
+      <HashCode>QQQAAAAAACAAAQQCAAAAAAAAAAAAAAACAAAAAAAAAAI=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoop.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
       <Property Name="OutputBuffer" />
-      <Property Name="Parser" />
       <Property Name="Out" />
+      <Property Name="InputProcessor" />
     </ShowAsAssociation>
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.MainLoopCoordinator&lt;T&gt;">
-    <Position X="5.75" Y="1.75" Width="2" />
+    <Position X="5" Y="1.25" Width="2" />
     <TypeIdentifier>
-      <HashCode>AAAAJAEAAAAAAAAAABAAAAAAAAAQIAQAIQAAAAAAAAA=</HashCode>
+      <HashCode>AAAAJAEAAAAAAAAAABAAAAAAAAAQIAQAIQAAAAAAAAg=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoopCoordinator.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -61,26 +56,26 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.AnsiResponseParser&lt;T&gt;" Collapsed="true">
-    <Position X="11.25" Y="1" Width="2" />
+    <Position X="16" Y="5.75" Width="2" />
     <TypeIdentifier>
       <HashCode>AAQAAAAAAAAACIAAAAAAAAAAAAAgAABAAAAAABAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.OutputBuffer">
-    <Position X="11.25" Y="4.25" Width="1.5" />
+  <Class Name="Terminal.Gui.OutputBuffer" Collapsed="true">
+    <Position X="13" Y="7.5" Width="1.5" />
     <Compartments>
       <Compartment Name="Fields" Collapsed="true" />
       <Compartment Name="Methods" Collapsed="true" />
     </Compartments>
     <TypeIdentifier>
-      <HashCode>AwAAAAAAAIAAAECIBgAEAIAAAAEIRgAACAAAKABAgAA=</HashCode>
+      <HashCode>AwAAAAAAAIAAAECIBgAEAIAAAAEMRgAACAAAKABAgAA=</HashCode>
       <FileName>ConsoleDrivers\V2\OutputBuffer.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.NetOutput" Collapsed="true">
-    <Position X="14.75" Y="4.25" Width="1.5" />
+    <Position X="13.75" Y="9.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AEAAAAAAACAAAAAAAAAAAAAAAQAAAAAAMACAAAEAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\NetOutput.cs</FileName>
@@ -88,40 +83,58 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.WindowsOutput" Collapsed="true">
-    <Position X="13.25" Y="4.25" Width="1.5" />
+    <Position X="12.25" Y="9.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAABAAACAAhAAACAAAACAAAAAAAAAIAAAAAAEAAAQ=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsOutput.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Interface Name="Terminal.Gui.IConsoleInput&lt;T&gt;">
-    <Position X="9" Y="3.25" Width="1.5" />
+  <Class Name="Terminal.Gui.ConsoleDrivers.V2.InputProcessor&lt;T&gt;" Collapsed="true">
+    <Position X="13" Y="5.75" Width="2" />
+    <TypeIdentifier>
+      <HashCode>AQAkAAAAAACAgAAAAAgAAAAABAIAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\InputProcessor.cs</FileName>
+    </TypeIdentifier>
+    <ShowAsAssociation>
+      <Property Name="Parser" />
+    </ShowAsAssociation>
+    <Lollipop Position="0.1" />
+  </Class>
+  <Interface Name="Terminal.Gui.IConsoleInput&lt;T&gt;" Collapsed="true">
+    <Position X="11.5" Y="1" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAI=</HashCode>
       <FileName>ConsoleDrivers\V2\IConsoleInput.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.IMainLoop&lt;T&gt;">
-    <Position X="9" Y="0.5" Width="1.5" />
+  <Interface Name="Terminal.Gui.IMainLoop&lt;T&gt;" Collapsed="true">
+    <Position X="8.25" Y="3.75" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAAAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAI=</HashCode>
+      <HashCode>AAQAAAAAAAAAAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAI=</HashCode>
       <FileName>ConsoleDrivers\V2\IMainLoop.cs</FileName>
     </TypeIdentifier>
   </Interface>
   <Interface Name="Terminal.Gui.IConsoleOutput" Collapsed="true">
-    <Position X="13.5" Y="3.25" Width="1.5" />
+    <Position X="13" Y="8.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IConsoleOutput.cs</FileName>
     </TypeIdentifier>
   </Interface>
   <Interface Name="Terminal.Gui.IOutputBuffer" Collapsed="true">
-    <Position X="11.25" Y="3.25" Width="1.5" />
+    <Position X="13" Y="6.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAAAEAAAAAAAIAAAAAABAAAAAAAAABAAAA=</HashCode>
+      <HashCode>AQAAAAAAAIAAAEAIAAAAAIAAAAEERgAAAAAACABAgAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IOutputBuffer.cs</FileName>
     </TypeIdentifier>
   </Interface>
+  <Interface Name="Terminal.Gui.IInputProcessor" Collapsed="true">
+    <Position X="13" Y="4.75" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAkAAAAAACAgAAAAAgAAAAABAIAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\IInputProcessor.cs</FileName>
+    </TypeIdentifier>
+  </Interface>
   <Font Name="Segoe UI" Size="9" />
 </ClassDiagram>
\ No newline at end of file

From 6124deb5ed2666a352db60af5157c286934fdf59 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 23 Nov 2024 13:39:21 +0000
Subject: [PATCH 014/198] WIP : mouse support

---
 .../AnsiResponseParser/AnsiResponseParser.cs  |  36 +++
 .../AnsiResponseParser/MouseParser.cs         | 247 ++++++++++++++++++
 .../ConsoleDrivers/V2/InputProcessor.cs       |  20 +-
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    |   8 +-
 UnitTests/ConsoleDrivers/MouseParserTests.cs  |  42 +++
 5 files changed, 340 insertions(+), 13 deletions(-)
 create mode 100644 Terminal.Gui/ConsoleDrivers/AnsiResponseParser/MouseParser.cs
 create mode 100644 UnitTests/ConsoleDrivers/MouseParserTests.cs

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
index 16a9c533ef..f6e6351967 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
@@ -4,10 +4,22 @@ namespace Terminal.Gui;
 
 public abstract class AnsiResponseParserBase : IAnsiResponseParser
 {
+    private readonly MouseParser _mouseParser = new  ();
     protected object lockExpectedResponses = new ();
 
     protected object lockState = new ();
 
+    /// <summary>
+    /// Event raised when mouse events are detected - requires setting <see cref="HandleMouse"/> to true
+    /// </summary>
+    public event EventHandler<MouseEventArgs> Mouse;
+
+    /// <summary>
+    /// True to explicitly handle mouse escape sequences by passing them to <see cref="Mouse"/> event.
+    /// Defaults to <see langword="false"/>
+    /// </summary>
+    public bool HandleMouse { get; set; } = false;
+
     /// <summary>
     ///     Responses we are expecting to come in.
     /// </summary>
@@ -180,6 +192,13 @@ protected bool ShouldReleaseHeldContent ()
     {
         string cur = heldContent.HeldToString ();
 
+        if (HandleMouse && IsMouse (cur))
+        {
+            RaiseMouseEvent (cur);
+            heldContent.ClearHeld ();
+            return false;
+        }
+
         lock (lockExpectedResponses)
         {
             // Look for an expected response for what is accumulated so far (since Esc)
@@ -238,6 +257,23 @@ protected bool ShouldReleaseHeldContent ()
         return false; // Continue accumulating
     }
 
+    private void RaiseMouseEvent (string cur)
+    {
+        var ev = _mouseParser.ProcessMouseInput (cur);
+
+        if (ev != null)
+        {
+            Mouse?.Invoke (this,ev);
+        }
+    }
+
+    private bool IsMouse (string cur)
+    {
+        // Typically in this format
+        // ESC [ < {button_code};{x_pos};{y_pos}{final_byte}
+        return cur.EndsWith ('M') || cur.EndsWith ('m');
+    }
+
     /// <summary>
     ///     <para>
     ///         When overriden in a derived class, indicates whether the unexpected response
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/MouseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/MouseParser.cs
new file mode 100644
index 0000000000..97146b939f
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/MouseParser.cs
@@ -0,0 +1,247 @@
+#nullable enable
+using System.Text.RegularExpressions;
+
+namespace Terminal.Gui;
+
+/// <summary>
+///     Parses mouse ansi escape sequences into <see cref="MouseEventArgs"/>
+///     including support for pressed, released and mouse wheel.
+/// </summary>
+public class MouseParser
+{
+    // Regex patterns for button press/release, wheel scroll, and mouse position reporting
+    private readonly Regex _mouseEventPattern = new (@"\u001b\[<(\d+);(\d+);(\d+)(M|m)", RegexOptions.Compiled);
+
+    /// <summary>
+    ///     Parses a mouse ansi escape sequence into a mouse event. Returns null if input
+    ///     is not a mouse event or its syntax is not understood.
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    public MouseEventArgs? ProcessMouseInput (string input)
+    {
+        // Match mouse wheel events first
+        Match match = _mouseEventPattern.Match (input);
+
+        if (match.Success)
+        {
+            int buttonCode = int.Parse (match.Groups [1].Value);
+            int x = int.Parse (match.Groups [2].Value);
+            int y = int.Parse (match.Groups [3].Value);
+            char terminator = match.Groups [4].Value.Single ();
+
+            return new()
+            {
+                Position = new (x, y),
+                Flags = GetFlags (buttonCode, terminator)
+            };
+        }
+
+        // its some kind of odd mouse event that doesn't follow expected format?
+        return null;
+    }
+
+    private static MouseFlags GetFlags (int buttonCode, char terminator)
+    {
+        MouseFlags buttonState = 0;
+
+        switch (buttonCode)
+        {
+            case 0:
+            case 8:
+            case 16:
+            case 24:
+            case 32:
+            case 36:
+            case 40:
+            case 48:
+            case 56:
+                buttonState = terminator == 'M'
+                                  ? MouseFlags.Button1Pressed
+                                  : MouseFlags.Button1Released;
+
+                break;
+            case 1:
+            case 9:
+            case 17:
+            case 25:
+            case 33:
+            case 37:
+            case 41:
+            case 45:
+            case 49:
+            case 53:
+            case 57:
+            case 61:
+                buttonState = terminator == 'M'
+                                  ? MouseFlags.Button2Pressed
+                                  : MouseFlags.Button2Released;
+
+                break;
+            case 2:
+            case 10:
+            case 14:
+            case 18:
+            case 22:
+            case 26:
+            case 30:
+            case 34:
+            case 42:
+            case 46:
+            case 50:
+            case 54:
+            case 58:
+            case 62:
+                buttonState = terminator == 'M'
+                                  ? MouseFlags.Button3Pressed
+                                  : MouseFlags.Button3Released;
+
+                break;
+            case 35:
+            //// Needed for Windows OS
+            //if (isButtonPressed && c == 'm'
+            //	&& (lastMouseEvent.ButtonState == MouseFlags.Button1Pressed
+            //	|| lastMouseEvent.ButtonState == MouseFlags.Button2Pressed
+            //	|| lastMouseEvent.ButtonState == MouseFlags.Button3Pressed)) {
+
+            //	switch (lastMouseEvent.ButtonState) {
+            //	case MouseFlags.Button1Pressed:
+            //		buttonState = MouseFlags.Button1Released;
+            //		break;
+            //	case MouseFlags.Button2Pressed:
+            //		buttonState = MouseFlags.Button2Released;
+            //		break;
+            //	case MouseFlags.Button3Pressed:
+            //		buttonState = MouseFlags.Button3Released;
+            //		break;
+            //	}
+            //} else {
+            //	buttonState = MouseFlags.ReportMousePosition;
+            //}
+            //break;
+            case 39:
+            case 43:
+            case 47:
+            case 51:
+            case 55:
+            case 59:
+            case 63:
+                buttonState = MouseFlags.ReportMousePosition;
+
+                break;
+            case 64:
+                buttonState = MouseFlags.WheeledUp;
+
+                break;
+            case 65:
+                buttonState = MouseFlags.WheeledDown;
+
+                break;
+            case 68:
+            case 72:
+            case 80:
+                buttonState = MouseFlags.WheeledLeft; // Shift/Ctrl+WheeledUp
+
+                break;
+            case 69:
+            case 73:
+            case 81:
+                buttonState = MouseFlags.WheeledRight; // Shift/Ctrl+WheeledDown
+
+                break;
+        }
+
+        // Modifiers.
+        switch (buttonCode)
+        {
+            case 8:
+            case 9:
+            case 10:
+            case 43:
+                buttonState |= MouseFlags.ButtonAlt;
+
+                break;
+            case 14:
+            case 47:
+                buttonState |= MouseFlags.ButtonAlt | MouseFlags.ButtonShift;
+
+                break;
+            case 16:
+            case 17:
+            case 18:
+            case 51:
+                buttonState |= MouseFlags.ButtonCtrl;
+
+                break;
+            case 22:
+            case 55:
+                buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonShift;
+
+                break;
+            case 24:
+            case 25:
+            case 26:
+            case 59:
+                buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonAlt;
+
+                break;
+            case 30:
+            case 63:
+                buttonState |= MouseFlags.ButtonCtrl | MouseFlags.ButtonShift | MouseFlags.ButtonAlt;
+
+                break;
+            case 32:
+            case 33:
+            case 34:
+                buttonState |= MouseFlags.ReportMousePosition;
+
+                break;
+            case 36:
+            case 37:
+                buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonShift;
+
+                break;
+            case 39:
+            case 68:
+            case 69:
+                buttonState |= MouseFlags.ButtonShift;
+
+                break;
+            case 40:
+            case 41:
+            case 42:
+                buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonAlt;
+
+                break;
+            case 45:
+            case 46:
+                buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonAlt | MouseFlags.ButtonShift;
+
+                break;
+            case 48:
+            case 49:
+            case 50:
+                buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl;
+
+                break;
+            case 53:
+            case 54:
+                buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonShift;
+
+                break;
+            case 56:
+            case 57:
+            case 58:
+                buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonAlt;
+
+                break;
+            case 61:
+            case 62:
+                buttonState |= MouseFlags.ReportMousePosition | MouseFlags.ButtonCtrl | MouseFlags.ButtonShift | MouseFlags.ButtonAlt;
+
+                break;
+        }
+
+        return buttonState;
+    }
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index 3f0ae2ee9c..48151a3341 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -1,4 +1,5 @@
 using System.Collections.Concurrent;
+using System.Diagnostics.CodeAnalysis;
 
 namespace Terminal.Gui.ConsoleDrivers.V2;
 
@@ -45,29 +46,30 @@ public void OnMouseEvent (MouseEventArgs a)
         MouseEvent?.Invoke (this, a);
     }
 
-    public InputProcessor ( ConcurrentQueue<T> inputBuffer)
+    public InputProcessor (ConcurrentQueue<T> inputBuffer)
     {
         InputBuffer = inputBuffer;
-
-        // TODO: For now handle all escape codes with ignore - later add mouse etc
-        Parser.UnexpectedResponseHandler = (str) => { return true; };
+        Parser.HandleMouse = true;
+        Parser.Mouse += (s, e) => OnMouseEvent (e);
+        // TODO: For now handle all other escape codes with ignore
+        Parser.UnexpectedResponseHandler = str => { return true; };
     }
 
     /// <summary>
-    /// Drains the <see cref="InputBuffer"/> buffer, processing all available keystrokes
+    ///     Drains the <see cref="InputBuffer"/> buffer, processing all available keystrokes
     /// </summary>
     public void ProcessQueue ()
     {
         // TODO: Esc timeout etc
 
-        while (InputBuffer.TryDequeue (out var input))
+        while (InputBuffer.TryDequeue (out T input))
         {
-            foreach (var released in Parser.ProcessInput (Tuple.Create (ConsoleKeyMapping.ToChar (input), input)))
+            foreach (Tuple<char, T> released in Parser.ProcessInput (Tuple.Create (ConsoleKeyMapping.ToChar (input), input)))
             {
-                Key key = ConsoleKeyMapping.ToKey (released.Item2);
+                var key = ConsoleKeyMapping.ToKey (released.Item2);
                 OnKeyDown (key);
                 OnKeyUp (key);
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 899525734b..d3fa136f2d 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -1,7 +1,6 @@
 using System.Collections.Concurrent;
 using Terminal.Gui.ConsoleDrivers;
 using Terminal.Gui.ConsoleDrivers.V2;
-using static Terminal.Gui.WindowsConsole;
 
 namespace Terminal.Gui;
 
@@ -17,7 +16,8 @@ public class MainLoop<T> : IMainLoop<T>
 
 
     // TODO: Remove later
-    StringBuilder sb = new StringBuilder ();
+    StringBuilder sb = new StringBuilder ("*");
+    private Point _lastMousePos = new Point(0,0);
 
     public void Initialize (ConcurrentQueue<T> inputBuffer, IConsoleOutput consoleOutput)
     {
@@ -27,6 +27,7 @@ public void Initialize (ConcurrentQueue<T> inputBuffer, IConsoleOutput consoleOu
 
         // TODO: Remove later
         InputProcessor.KeyDown += (s,k) => sb.Append (ConsoleKeyMapping.ToChar (k));
+        InputProcessor.MouseEvent += (s, e) => { _lastMousePos = e.Position; };
     }
 
 
@@ -53,12 +54,11 @@ public void Iteration ()
     {
         InputProcessor.ProcessQueue ();
 
-
         Random r = new Random ();
         OutputBuffer.SetWindowSize (20, 10);
 
         OutputBuffer.CurrentAttribute = new Attribute (Color.White, Color.Black);
-        OutputBuffer.Move (0,0);
+        OutputBuffer.Move (_lastMousePos.X,_lastMousePos.Y);
 
         foreach (var ch in sb.ToString())
         {
diff --git a/UnitTests/ConsoleDrivers/MouseParserTests.cs b/UnitTests/ConsoleDrivers/MouseParserTests.cs
new file mode 100644
index 0000000000..567948ecdf
--- /dev/null
+++ b/UnitTests/ConsoleDrivers/MouseParserTests.cs
@@ -0,0 +1,42 @@
+namespace UnitTests.ConsoleDrivers;
+
+public class MouseParserTests
+{
+    private readonly MouseParser _parser;
+
+    public MouseParserTests () { _parser = new (); }
+
+    // Consolidated test for all mouse events: button press/release, wheel scroll, position, modifiers
+    [Theory]
+    [InlineData ("\u001b[<0;100;200M", 100, 200, MouseFlags.Button1Pressed)] // Button 1 Pressed
+    [InlineData ("\u001b[<0;150;250m", 150, 250, MouseFlags.Button1Released)] // Button 1 Released
+    [InlineData ("\u001b[<1;120;220M", 120, 220, MouseFlags.Button2Pressed)] // Button 2 Pressed
+    [InlineData ("\u001b[<1;180;280m", 180, 280, MouseFlags.Button2Released)] // Button 2 Released
+    [InlineData ("\u001b[<2;200;300M", 200, 300, MouseFlags.Button3Pressed)] // Button 3 Pressed
+    [InlineData ("\u001b[<2;250;350m", 250, 350, MouseFlags.Button3Released)] // Button 3 Released
+    [InlineData ("\u001b[<64;100;200M", 100, 200, MouseFlags.WheeledUp)] // Wheel Scroll Up
+    [InlineData ("\u001b[<65;150;250m", 150, 250, MouseFlags.WheeledDown)] // Wheel Scroll Down
+    [InlineData ("\u001b[<39;100;200m", 100, 200, MouseFlags.ButtonShift | MouseFlags.ReportMousePosition)] // Mouse Position (No Button)
+    [InlineData ("\u001b[<43;120;240m", 120, 240, MouseFlags.ButtonAlt | MouseFlags.ReportMousePosition)] // Mouse Position (No Button)
+    [InlineData ("\u001b[<8;100;200M", 100, 200, MouseFlags.Button1Pressed | MouseFlags.ButtonAlt)] // Button 1 Pressed + Alt
+    [InlineData ("\u001b[<invalid;100;200M", 0, 0, MouseFlags.None)] // Invalid Input (Expecting null)
+    [InlineData ("\u001b[<100;200;300Z", 0, 0, MouseFlags.None)] // Invalid Input (Expecting null)
+    [InlineData ("\u001b[<invalidInput>", 0, 0, MouseFlags.None)] // Invalid Input (Expecting null)
+    public void ProcessMouseInput_ReturnsCorrectFlags (string input, int expectedX, int expectedY, MouseFlags expectedFlags)
+    {
+        // Act
+        MouseEventArgs result = _parser.ProcessMouseInput (input);
+
+        // Assert
+        if (expectedFlags == MouseFlags.None)
+        {
+            Assert.Null (result); // Expect null for invalid inputs
+        }
+        else
+        {
+            Assert.NotNull (result); // Expect non-null result for valid inputs
+            Assert.Equal (new (expectedX, expectedY), result!.Position); // Verify position
+            Assert.Equal (expectedFlags, result.Flags); // Verify flags
+        }
+    }
+}

From 9f6e23a6cd980528a6be39bf5a0b747b5331c011 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 23 Nov 2024 14:53:18 +0000
Subject: [PATCH 015/198] Fix not reseting state after releasing mouse events

---
 .../ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
index f6e6351967..4a57b0ad3a 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
@@ -195,7 +195,7 @@ protected bool ShouldReleaseHeldContent ()
         if (HandleMouse && IsMouse (cur))
         {
             RaiseMouseEvent (cur);
-            heldContent.ClearHeld ();
+            ResetState ();
             return false;
         }
 

From 24c2a5a702ccd73ee5d2901e92057b739512acbf Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 23 Nov 2024 15:14:37 +0000
Subject: [PATCH 016/198] Refactor InputProcessor to be abstract with typed
 inheritors

---
 Drivers2/Program.cs                           | 11 ++++-
 .../ConsoleDrivers/ConsoleKeyMapping.cs       | 13 ------
 Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs   |  3 +-
 .../ConsoleDrivers/V2/InputProcessor.cs       | 12 +++--
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    |  6 +--
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  | 20 +++++----
 .../ConsoleDrivers/V2/NetInputProcessor.cs    | 28 ++++++++++++
 Terminal.Gui/ConsoleDrivers/V2/V2.cd          | 45 ++++++++++++++-----
 .../V2/WindowsInputProcessor.cs               | 18 ++++++++
 9 files changed, 111 insertions(+), 45 deletions(-)
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs

diff --git a/Drivers2/Program.cs b/Drivers2/Program.cs
index 82f992ab4f..f2ca4b6f74 100644
--- a/Drivers2/Program.cs
+++ b/Drivers2/Program.cs
@@ -1,5 +1,6 @@
 using System.Collections.Concurrent;
 using Terminal.Gui;
+using Terminal.Gui.ConsoleDrivers.V2;
 using static Terminal.Gui.WindowsConsole;
 
 namespace Drivers2;
@@ -28,20 +29,28 @@ static void Main (string [] args)
 
         // Required to set up colors etc?
         Application.Init ();
-
         IMainLoopCoordinator coordinator;
         if (win)
         {
+            // TODO: We will need a nice factory for this constructor, it's getting a bit epic
+
+            var inputBuffer = new ConcurrentQueue<InputRecord> ();
             var loop = new MainLoop<InputRecord> ();
             coordinator = new MainLoopCoordinator<InputRecord> (
                                                                 ()=>new WindowsInput (),
+                                                                inputBuffer,
+                                                                new WindowsInputProcessor (inputBuffer),
                                                                 ()=>new WindowsOutput (),
                                                                 loop);
         }
         else
         {
+
+            var inputBuffer = new ConcurrentQueue<ConsoleKeyInfo> ();
             var loop = new MainLoop<ConsoleKeyInfo> ();
             coordinator = new MainLoopCoordinator<ConsoleKeyInfo> (()=>new NetInput (),
+                                                                   inputBuffer,
+                                                                   new NetInputProcessor (inputBuffer),
                                                                    ()=>new NetOutput (),
                                                                    loop);
         }
diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs
index b32e7b2eac..3fb87466d4 100644
--- a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs
+++ b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs
@@ -828,19 +828,6 @@ public static Key ToKey<T> (T result)
                };
     }
 
-    public static char ToChar<T> (T result)
-    {
-        // TODO: Requires testing
-        return result switch
-               {
-                   ConsoleKeyInfo keyInfo => keyInfo.KeyChar,
-                   Key key => (char)key,
-
-                   // TODO: probably not that simple
-                   WindowsConsole.InputRecord inputRecord => inputRecord.KeyEvent.UnicodeChar,
-                   _ => throw new ArgumentException ($"Unsupported type {typeof (T).Name}")
-               };
-    }
 
     /// <summary>Maps a <see cref="ConsoleKeyInfo"/> to a <see cref="KeyCode"/>.</summary>
     /// <param name="consoleKeyInfo">The console key.</param>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
index 4411c17ecf..accd18578d 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
@@ -12,8 +12,9 @@ public interface IMainLoop<T> : IDisposable
     /// Initializes the loop with a buffer from which data can be read
     /// </summary>
     /// <param name="inputBuffer"></param>
+    /// <param name="inputProcessor"></param>
     /// <param name="consoleOutput"></param>
-    void Initialize (ConcurrentQueue<T> inputBuffer, IConsoleOutput consoleOutput);
+    void Initialize (ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput);
 
     /// <summary>
     /// Runs <see cref="Iteration"/> in an infinite loop.
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index 48151a3341..d8e895b77b 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -1,9 +1,10 @@
 using System.Collections.Concurrent;
+using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 
 namespace Terminal.Gui.ConsoleDrivers.V2;
 
-public class InputProcessor<T> : IInputProcessor
+public abstract class InputProcessor<T> : IInputProcessor
 {
     public AnsiResponseParser<T> Parser { get; } = new ();
     public ConcurrentQueue<T> InputBuffer { get; }
@@ -64,12 +65,9 @@ public void ProcessQueue ()
 
         while (InputBuffer.TryDequeue (out T input))
         {
-            foreach (Tuple<char, T> released in Parser.ProcessInput (Tuple.Create (ConsoleKeyMapping.ToChar (input), input)))
-            {
-                var key = ConsoleKeyMapping.ToKey (released.Item2);
-                OnKeyDown (key);
-                OnKeyUp (key);
-            }
+            this.Process (input);
         }
     }
+
+    protected abstract void Process (T result);
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index d3fa136f2d..b64c6b8d6f 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -19,14 +19,14 @@ public class MainLoop<T> : IMainLoop<T>
     StringBuilder sb = new StringBuilder ("*");
     private Point _lastMousePos = new Point(0,0);
 
-    public void Initialize (ConcurrentQueue<T> inputBuffer, IConsoleOutput consoleOutput)
+    public void Initialize (ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput)
     {
         InputBuffer = inputBuffer;
         Out = consoleOutput;
-        InputProcessor = new InputProcessor<T> (inputBuffer);
+        InputProcessor = inputProcessor;
 
         // TODO: Remove later
-        InputProcessor.KeyDown += (s,k) => sb.Append (ConsoleKeyMapping.ToChar (k));
+        InputProcessor.KeyDown += (s,k) => sb.Append ((char)k);
         InputProcessor.MouseEvent += (s, e) => { _lastMousePos = e.Position; };
     }
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index 5d18ffd4af..51ee8addbb 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -5,25 +5,29 @@ namespace Terminal.Gui;
 public class MainLoopCoordinator<T> : IMainLoopCoordinator
 {
     private readonly Func<IConsoleInput<T>> _inputFactory;
+    private readonly ConcurrentQueue<T> _inputBuffer;
+    private readonly IInputProcessor _inputProcessor;
     private readonly IMainLoop<T> _loop;
     private CancellationTokenSource tokenSource = new ();
     private readonly Func<IConsoleOutput> _outputFactory;
 
-    ConcurrentQueue<T> _inputBuffer = new ();
-
     /// <summary>
     /// Creates a new coordinator
     /// </summary>
     /// <param name="inputFactory">Function to create a new input. This must call <see langword="new"/>
-    /// explicitly and cannot return an existing instance. This requirement arises because Windows
-    /// console screen buffer APIs are thread-specific for certain operations.</param>
+    ///     explicitly and cannot return an existing instance. This requirement arises because Windows
+    ///     console screen buffer APIs are thread-specific for certain operations.</param>
+    /// <param name="inputBuffer"></param>
+    /// <param name="inputProcessor"></param>
     /// <param name="outputFactory">Function to create a new output. This must call <see langword="new"/>
-    /// explicitly and cannot return an existing instance. This requirement arises because Windows
-    /// console screen buffer APIs are thread-specific for certain operations.</param>
+    ///     explicitly and cannot return an existing instance. This requirement arises because Windows
+    ///     console screen buffer APIs are thread-specific for certain operations.</param>
     /// <param name="loop"></param>
-    public MainLoopCoordinator (Func<IConsoleInput<T>> inputFactory, Func<IConsoleOutput> outputFactory, IMainLoop<T> loop)
+    public MainLoopCoordinator (Func<IConsoleInput<T>> inputFactory, ConcurrentQueue<T> inputBuffer,IInputProcessor inputProcessor, Func<IConsoleOutput> outputFactory, IMainLoop<T> loop)
     {
         _inputFactory = inputFactory;
+        _inputBuffer = inputBuffer;
+        _inputProcessor = inputProcessor;
         _outputFactory = outputFactory;
         _loop = loop;
     }
@@ -65,7 +69,7 @@ private void RunLoop ()
         // Instance must be constructed on the thread in which it is used.
         IConsoleOutput output = _outputFactory.Invoke ();
 
-        _loop.Initialize (_inputBuffer, output);
+        _loop.Initialize (_inputBuffer, _inputProcessor, output);
 
         try
         {
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
new file mode 100644
index 0000000000..b187dd58bb
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Terminal.Gui.ConsoleDrivers.V2;
+
+/// <summary>
+/// Input processor for <see cref="NetInput"/>, deals in <see cref="ConsoleKeyInfo"/> stream
+/// </summary>
+public class NetInputProcessor : InputProcessor<ConsoleKeyInfo>
+{
+    /// <inheritdoc />
+    public NetInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer) : base (inputBuffer) { }
+
+    /// <inheritdoc />
+    protected override void Process (ConsoleKeyInfo consoleKeyInfo)
+    {
+        foreach (Tuple<char, ConsoleKeyInfo> released in Parser.ProcessInput (Tuple.Create (consoleKeyInfo.KeyChar, consoleKeyInfo)))
+        {
+            var key = ConsoleKeyMapping.ToKey (released.Item2);
+            OnKeyDown (key);
+            OnKeyUp (key);
+        }
+    }
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index d758bc6168..7d76cae23e 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -34,13 +34,13 @@
   <Class Name="Terminal.Gui.MainLoop&lt;T&gt;" Collapsed="true" BaseTypeListCollapsed="true">
     <Position X="10" Y="4.75" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>QQQAAAAAACAAAQQCAAAAAAAAAAAAAAACAAAAAAAAAAI=</HashCode>
+      <HashCode>QQQAAAAAACAAAQQCAAAAAAAAAAAAAAACAAAAAAABAAI=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoop.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
+      <Property Name="InputProcessor" />
       <Property Name="OutputBuffer" />
       <Property Name="Out" />
-      <Property Name="InputProcessor" />
     </ShowAsAssociation>
     <Lollipop Position="0.2" />
   </Class>
@@ -56,14 +56,14 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.AnsiResponseParser&lt;T&gt;" Collapsed="true">
-    <Position X="16" Y="5.75" Width="2" />
+    <Position X="18.75" Y="4.75" Width="2" />
     <TypeIdentifier>
       <HashCode>AAQAAAAAAAAACIAAAAAAAAAAAAAgAABAAAAAABAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.OutputBuffer" Collapsed="true">
-    <Position X="13" Y="7.5" Width="1.5" />
+    <Position X="10" Y="8.25" Width="1.5" />
     <Compartments>
       <Compartment Name="Fields" Collapsed="true" />
       <Compartment Name="Methods" Collapsed="true" />
@@ -75,7 +75,7 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.NetOutput" Collapsed="true">
-    <Position X="13.75" Y="9.25" Width="1.5" />
+    <Position X="13.5" Y="8.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AEAAAAAAACAAAAAAAAAAAAAAAQAAAAAAMACAAAEAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\NetOutput.cs</FileName>
@@ -83,7 +83,7 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.WindowsOutput" Collapsed="true">
-    <Position X="12.25" Y="9.25" Width="1.5" />
+    <Position X="12" Y="8.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAABAAACAAhAAACAAAACAAAAAAAAAIAAAAAAEAAAQ=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsOutput.cs</FileName>
@@ -91,9 +91,9 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.ConsoleDrivers.V2.InputProcessor&lt;T&gt;" Collapsed="true">
-    <Position X="13" Y="5.75" Width="2" />
+    <Position X="15.75" Y="4.75" Width="2" />
     <TypeIdentifier>
-      <HashCode>AQAkAAAAAACAgAAAAAgAAAAABAIAAAAAAAAAAAAAAAA=</HashCode>
+      <HashCode>AQAkAAAAAACAgAAAAggAAAAABAIAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\InputProcessor.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -101,6 +101,27 @@
     </ShowAsAssociation>
     <Lollipop Position="0.1" />
   </Class>
+  <Class Name="Terminal.Gui.ConsoleDrivers.V2.NetInputProcessor" Collapsed="true">
+    <Position X="16.75" Y="5.75" Width="2" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\NetInputProcessor.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="Terminal.Gui.ConsoleDrivers.V2.WindowsInputProcessor" Collapsed="true">
+    <Position X="14.75" Y="5.75" Width="2" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\WindowsInputProcessor.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="Terminal.Gui.MouseParser" Collapsed="true">
+    <Position X="20.75" Y="4.75" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>BAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\AnsiResponseParser\MouseParser.cs</FileName>
+    </TypeIdentifier>
+  </Class>
   <Interface Name="Terminal.Gui.IConsoleInput&lt;T&gt;" Collapsed="true">
     <Position X="11.5" Y="1" Width="1.5" />
     <TypeIdentifier>
@@ -116,21 +137,21 @@
     </TypeIdentifier>
   </Interface>
   <Interface Name="Terminal.Gui.IConsoleOutput" Collapsed="true">
-    <Position X="13" Y="8.25" Width="1.5" />
+    <Position X="12.75" Y="7.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IConsoleOutput.cs</FileName>
     </TypeIdentifier>
   </Interface>
   <Interface Name="Terminal.Gui.IOutputBuffer" Collapsed="true">
-    <Position X="13" Y="6.5" Width="1.5" />
+    <Position X="10" Y="7.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AQAAAAAAAIAAAEAIAAAAAIAAAAEERgAAAAAACABAgAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IOutputBuffer.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.IInputProcessor" Collapsed="true">
-    <Position X="13" Y="4.75" Width="1.5" />
+  <Interface Name="Terminal.Gui.IInputProcessor">
+    <Position X="13" Y="4.5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAkAAAAAACAgAAAAAgAAAAABAIAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IInputProcessor.cs</FileName>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
new file mode 100644
index 0000000000..b2f4bc1f6c
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
@@ -0,0 +1,18 @@
+using System.Collections.Concurrent;
+
+namespace Terminal.Gui.ConsoleDrivers.V2;
+
+/// <summary>
+/// Input processor for <see cref="WindowsInput"/>, deals in <see cref="WindowsConsole.InputRecord"/> stream.
+/// </summary>
+public class WindowsInputProcessor : InputProcessor<WindowsConsole.InputRecord>
+{
+    /// <inheritdoc />
+    public WindowsInputProcessor (ConcurrentQueue<WindowsConsole.InputRecord> inputBuffer) : base (inputBuffer) { }
+
+    /// <inheritdoc />
+    protected override void Process (WindowsConsole.InputRecord result)
+    {
+
+    }
+}

From 4c632978c4fe163a487bbf4d1cff8bbe1bea559b Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 23 Nov 2024 15:31:23 +0000
Subject: [PATCH 017/198] WindowsOutput and InputProcessor working ish

---
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    |  2 +
 .../V2/WindowsInputProcessor.cs               | 60 ++++++++++++++++++-
 .../ConsoleDrivers/V2/WindowsOutput.cs        |  6 +-
 3 files changed, 65 insertions(+), 3 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index b64c6b8d6f..7ddb880ce7 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -49,12 +49,14 @@ public void Run (CancellationToken token)
         }
         while (!token.IsCancellationRequested);
     }
+
     /// <inheritdoc />
     public void Iteration ()
     {
         InputProcessor.ProcessQueue ();
 
         Random r = new Random ();
+
         OutputBuffer.SetWindowSize (20, 10);
 
         OutputBuffer.CurrentAttribute = new Attribute (Color.White, Color.Black);
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
index b2f4bc1f6c..d205a34982 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
@@ -1,4 +1,5 @@
 using System.Collections.Concurrent;
+using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
 
 namespace Terminal.Gui.ConsoleDrivers.V2;
 
@@ -11,8 +12,65 @@ public class WindowsInputProcessor : InputProcessor<WindowsConsole.InputRecord>
     public WindowsInputProcessor (ConcurrentQueue<WindowsConsole.InputRecord> inputBuffer) : base (inputBuffer) { }
 
     /// <inheritdoc />
-    protected override void Process (WindowsConsole.InputRecord result)
+    protected override void Process (WindowsConsole.InputRecord inputEvent)
     {
+        switch (inputEvent.EventType)
+        {
 
+            case WindowsConsole.EventType.Key:
+
+                var mapped = (Key)inputEvent.KeyEvent.UnicodeChar;
+                /*
+                if (inputEvent.KeyEvent.wVirtualKeyCode == (VK)ConsoleKey.Packet)
+                {
+                    // Used to pass Unicode characters as if they were keystrokes.
+                    // The VK_PACKET key is the low word of a 32-bit
+                    // Virtual Key value used for non-keyboard input methods.
+                    inputEvent.KeyEvent = FromVKPacketToKeyEventRecord (inputEvent.KeyEvent);
+                }
+
+                WindowsConsole.ConsoleKeyInfoEx keyInfo = ToConsoleKeyInfoEx (inputEvent.KeyEvent);
+
+                //Debug.WriteLine ($"event: KBD: {GetKeyboardLayoutName()} {inputEvent.ToString ()} {keyInfo.ToString (keyInfo)}");
+
+                KeyCode map = MapKey (keyInfo);
+
+                if (map == KeyCode.Null)
+                {
+                    break;
+                }
+                */
+                // This follows convention in NetDriver
+
+                if (inputEvent.KeyEvent.bKeyDown)
+                {
+                    OnKeyDown (mapped);
+                }
+                else
+                {
+                    OnKeyUp (mapped);
+                }
+
+                break;
+
+            case WindowsConsole.EventType.Mouse:
+                MouseEventArgs me = ToDriverMouse (inputEvent.MouseEvent);
+
+                OnMouseEvent (me);
+
+                break;
+        }
+    }
+
+    private MouseEventArgs ToDriverMouse (WindowsConsole.MouseEventRecord e)
+    {
+        var result = new MouseEventArgs ()
+        {
+            Position = new (e.MousePosition.X, e.MousePosition.Y)
+        };
+
+        // TODO: Return keys too
+
+        return result;
     }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
index 4621fc166f..2f7717ae50 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
@@ -90,11 +90,13 @@ public void Write (IOutputBuffer buffer)
         var outputBuffer = new WindowsConsole.ExtendedCharInfo [buffer.Rows * buffer.Cols];
 
         Size windowSize = WinConsole?.GetConsoleBufferWindow (out Point _) ?? new Size (buffer.Cols, buffer.Rows);
-
+        
+        // TODO: probably do need this right?
+        /*
         if (!windowSize.IsEmpty && (windowSize.Width != buffer.Cols || windowSize.Height != buffer.Rows))
         {
             return;
-        }
+        }*/
 
         var bufferCoords = new WindowsConsole.Coord
         {

From 485be292e433f75c8a52c3d37556bb73520e9043 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 23 Nov 2024 16:44:36 +0000
Subject: [PATCH 018/198] Add window size support

---
 .../ConsoleDrivers/V2/IConsoleOutput.cs       |  1 +
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    |  5 ++++-
 Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs   |  7 +++++++
 .../ConsoleDrivers/V2/WindowsOutput.cs        | 21 +++++++++++++++++++
 4 files changed, 33 insertions(+), 1 deletion(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
index 2932cdac19..2242d5010e 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
@@ -9,4 +9,5 @@ public interface IConsoleOutput : IDisposable
 {
     void Write(string text);
     void Write (IOutputBuffer buffer);
+    public Size GetWindowSize ();
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 7ddb880ce7..34e4daca52 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -57,7 +57,10 @@ public void Iteration ()
 
         Random r = new Random ();
 
-        OutputBuffer.SetWindowSize (20, 10);
+        // TODO: throttle this
+        var size = Out.GetWindowSize ();
+
+        OutputBuffer.SetWindowSize (size.Width, size.Height);
 
         OutputBuffer.CurrentAttribute = new Attribute (Color.White, Color.Black);
         OutputBuffer.Move (_lastMousePos.X,_lastMousePos.Y);
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
index 133465c997..038a355983 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
@@ -171,6 +171,13 @@ public void Write (IOutputBuffer buffer)
 
         _cachedCursorVisibility = savedVisibility;
     }
+
+    /// <inheritdoc />
+    public Size GetWindowSize ()
+    {
+        return new Size (Console.BufferWidth, Console.BufferHeight);
+    }
+
     void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
     {
         SetCursorPosition (lastCol, row);
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
index 2f7717ae50..6fa2efcdcc 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
@@ -27,6 +27,10 @@ private static extern nint CreateConsoleScreenBuffer (
         nint screenBufferData
     );
 
+
+    [DllImport ("kernel32.dll", SetLastError = true)]
+    private static extern bool GetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX csbi);
+
     [Flags]
     private enum ShareMode : uint
     {
@@ -171,6 +175,23 @@ public void Write (IOutputBuffer buffer)
         WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
     }
 
+    public Size GetWindowSize ()
+    {
+        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 ());
+            return Size.Empty;
+        }
+
+        Size sz = new (
+                       csbi.srWindow.Right - csbi.srWindow.Left + 1,
+                       csbi.srWindow.Bottom - csbi.srWindow.Top + 1);
+        return sz;
+    }
+
     /// <inheritdoc />
     public void Dispose ()
     {

From d081b4de29c8dfee7151b494f5171fb2dc20217f Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 23 Nov 2024 16:58:59 +0000
Subject: [PATCH 019/198] Fix WindowsInputProcessor to handle mouse properly

---
 Drivers2/Program.cs                           |  1 -
 .../ConsoleDrivers/ConsoleKeyMapping.cs       | 14 -------
 .../ConsoleDrivers/V2/ConsoleInput.cs         |  1 -
 .../ConsoleDrivers/V2/IConsoleOutput.cs       |  8 +---
 Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs   |  1 -
 .../ConsoleDrivers/V2/InputProcessor.cs       |  6 +--
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    |  2 -
 .../ConsoleDrivers/V2/NetInputProcessor.cs    | 12 ++----
 Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs   |  9 +---
 .../ConsoleDrivers/V2/OutputBuffer.cs         |  7 +---
 Terminal.Gui/ConsoleDrivers/V2/V2.cd          | 16 ++++----
 .../ConsoleDrivers/V2/WindowsInput.cs         |  5 +--
 .../V2/WindowsInputProcessor.cs               | 41 +++++++++++--------
 .../ConsoleDrivers/V2/WindowsOutput.cs        |  8 ++--
 14 files changed, 47 insertions(+), 84 deletions(-)

diff --git a/Drivers2/Program.cs b/Drivers2/Program.cs
index f2ca4b6f74..11b18be28c 100644
--- a/Drivers2/Program.cs
+++ b/Drivers2/Program.cs
@@ -1,6 +1,5 @@
 using System.Collections.Concurrent;
 using Terminal.Gui;
-using Terminal.Gui.ConsoleDrivers.V2;
 using static Terminal.Gui.WindowsConsole;
 
 namespace Drivers2;
diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs
index 3fb87466d4..73bb35cc17 100644
--- a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs
+++ b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs
@@ -815,20 +815,6 @@ public static KeyCode MapKey (ConsoleKeyInfo keyInfo)
         return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.KeyChar));
     }
 
-    public static Key ToKey<T> (T result)
-    {
-        // TODO: Requires testing
-        return result switch
-               {
-                   ConsoleKeyInfo keyInfo =>MapKey (keyInfo),
-
-                   // TODO: how?
-                   // WindowsConsole.InputRecord inputRecord => inputRecord.KeyEvent.UnicodeChar,
-                   _ => throw new ArgumentException ($"Unsupported type {typeof (T).Name}")
-               };
-    }
-
-
     /// <summary>Maps a <see cref="ConsoleKeyInfo"/> to a <see cref="KeyCode"/>.</summary>
     /// <param name="consoleKeyInfo">The console key.</param>
     /// <returns>The <see cref="KeyCode"/> or the <paramref name="consoleKeyInfo"/>.</returns>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
index d2993b3d57..9a0269be19 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
@@ -1,7 +1,6 @@
 #nullable enable
 using System.Collections.Concurrent;
 
-
 namespace Terminal.Gui;
 
 public abstract class ConsoleInput<T> : IConsoleInput<T>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
index 2242d5010e..cd2fb62cc4 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
@@ -1,10 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Terminal.Gui;
+namespace Terminal.Gui;
 public interface IConsoleOutput : IDisposable
 {
     void Write(string text);
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
index accd18578d..67208eb6a7 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
@@ -1,5 +1,4 @@
 using System.Collections.Concurrent;
-using Terminal.Gui.ConsoleDrivers.V2;
 
 namespace Terminal.Gui;
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index d8e895b77b..52fe0b3a96 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -1,8 +1,6 @@
 using System.Collections.Concurrent;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
 
-namespace Terminal.Gui.ConsoleDrivers.V2;
+namespace Terminal.Gui;
 
 public abstract class InputProcessor<T> : IInputProcessor
 {
@@ -65,7 +63,7 @@ public void ProcessQueue ()
 
         while (InputBuffer.TryDequeue (out T input))
         {
-            this.Process (input);
+            Process (input);
         }
     }
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 34e4daca52..6b15bedc02 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -1,6 +1,4 @@
 using System.Collections.Concurrent;
-using Terminal.Gui.ConsoleDrivers;
-using Terminal.Gui.ConsoleDrivers.V2;
 
 namespace Terminal.Gui;
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
index b187dd58bb..5085ba5c2d 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
@@ -1,11 +1,7 @@
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.Collections.Concurrent;
+using Terminal.Gui.ConsoleDrivers;
 
-namespace Terminal.Gui.ConsoleDrivers.V2;
+namespace Terminal.Gui;
 
 /// <summary>
 /// Input processor for <see cref="NetInput"/>, deals in <see cref="ConsoleKeyInfo"/> stream
@@ -20,7 +16,7 @@ protected override void Process (ConsoleKeyInfo consoleKeyInfo)
     {
         foreach (Tuple<char, ConsoleKeyInfo> released in Parser.ProcessInput (Tuple.Create (consoleKeyInfo.KeyChar, consoleKeyInfo)))
         {
-            var key = ConsoleKeyMapping.ToKey (released.Item2);
+            var key = ConsoleKeyMapping.MapKey (released.Item2);
             OnKeyDown (key);
             OnKeyUp (key);
         }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
index 038a355983..dbaad89be6 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
@@ -1,11 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Microsoft.VisualBasic;
-
-namespace Terminal.Gui;
+namespace Terminal.Gui;
 public class NetOutput : IConsoleOutput
 {
     public bool IsWinPlatform { get; }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
index 4d792b3269..20cd5e112d 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
@@ -92,7 +92,7 @@ public int Cols
     /// <summary>Gets the location and size of the terminal screen.</summary>
     internal Rectangle Screen => new (0, 0, Cols, Rows);
 
-    private Region? _clip = null;
+    private Region? _clip;
 
     /// <summary>
     ///     Gets or sets the clip rectangle that <see cref="AddRune(Rune)"/> and <see cref="AddStr(string)"/> are subject
@@ -375,11 +375,8 @@ public bool IsValidLocation (Rune rune, int col, int row)
         {
             return col >= 0 && row >= 0 && col < Cols && row < Rows && Clip!.Contains (col, row);
         }
-        else
-        {
 
-            return Clip!.Contains (col, row) || Clip!.Contains (col + 1, row);
-        }
+        return Clip!.Contains (col, row) || Clip!.Contains (col + 1, row);
     }
 
     /// <inheritdoc />
diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index 7d76cae23e..f7923526ed 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -47,7 +47,7 @@
   <Class Name="Terminal.Gui.MainLoopCoordinator&lt;T&gt;">
     <Position X="5" Y="1.25" Width="2" />
     <TypeIdentifier>
-      <HashCode>AAAAJAEAAAAAAAAAABAAAAAAAAAQIAQAIQAAAAAAAAg=</HashCode>
+      <HashCode>AAAAJAEAAAAAAAAAABAAAAAAAAAQIAQAIQAAAAAAAgg=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoopCoordinator.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -77,7 +77,7 @@
   <Class Name="Terminal.Gui.NetOutput" Collapsed="true">
     <Position X="13.5" Y="8.25" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AEAAAAAAACAAAAAAAAAAAAAAAQAAAAAAMACAAAEAAAA=</HashCode>
+      <HashCode>AEAAAAAAACAAAAAAAAAAAAAAAQAAQAAAMACAAAEAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\NetOutput.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
@@ -85,12 +85,12 @@
   <Class Name="Terminal.Gui.WindowsOutput" Collapsed="true">
     <Position X="12" Y="8.25" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAAAABAAACAAhAAACAAAACAAAAAAAAAIAAAAAAEAAAQ=</HashCode>
+      <HashCode>AAAAABAAACAAhAAACAAAACAAAAgAQAAIAAAAAAEAAAQ=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsOutput.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.ConsoleDrivers.V2.InputProcessor&lt;T&gt;" Collapsed="true">
+  <Class Name="Terminal.Gui.InputProcessor&lt;T&gt;" Collapsed="true">
     <Position X="15.75" Y="4.75" Width="2" />
     <TypeIdentifier>
       <HashCode>AQAkAAAAAACAgAAAAggAAAAABAIAAAAAAAAAAAAAAAA=</HashCode>
@@ -101,17 +101,17 @@
     </ShowAsAssociation>
     <Lollipop Position="0.1" />
   </Class>
-  <Class Name="Terminal.Gui.ConsoleDrivers.V2.NetInputProcessor" Collapsed="true">
+  <Class Name="Terminal.Gui.NetInputProcessor" Collapsed="true">
     <Position X="16.75" Y="5.75" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\NetInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.ConsoleDrivers.V2.WindowsInputProcessor" Collapsed="true">
+  <Class Name="Terminal.Gui.WindowsInputProcessor" Collapsed="true">
     <Position X="14.75" Y="5.75" Width="2" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <HashCode>AQAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Class>
@@ -139,7 +139,7 @@
   <Interface Name="Terminal.Gui.IConsoleOutput" Collapsed="true">
     <Position X="12.75" Y="7.25" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAA=</HashCode>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IConsoleOutput.cs</FileName>
     </TypeIdentifier>
   </Interface>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
index d2ce100747..7293a344eb 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
@@ -1,5 +1,4 @@
-using System.Collections.Concurrent;
-using System.Runtime.InteropServices;
+using System.Runtime.InteropServices;
 using static Terminal.Gui.WindowsConsole;
 
 namespace Terminal.Gui;
@@ -40,7 +39,7 @@ protected override bool Peek ()
         try
         {
             // Use PeekConsoleInput to inspect the input buffer without removing events
-            if (PeekConsoleInput (_inputHandle, pRecord, (uint)bufferSize, out uint numberOfEventsRead))
+            if (PeekConsoleInput (_inputHandle, pRecord, bufferSize, out uint numberOfEventsRead))
             {
                 // Return true if there's at least one event in the buffer
                 return numberOfEventsRead > 0;
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
index d205a34982..b810d53509 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
@@ -1,25 +1,39 @@
 using System.Collections.Concurrent;
 using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
+using static Terminal.Gui.WindowsConsole;
 
-namespace Terminal.Gui.ConsoleDrivers.V2;
+namespace Terminal.Gui;
+using InputRecord = InputRecord;
 
 /// <summary>
 /// Input processor for <see cref="WindowsInput"/>, deals in <see cref="WindowsConsole.InputRecord"/> stream.
 /// </summary>
-public class WindowsInputProcessor : InputProcessor<WindowsConsole.InputRecord>
+public class WindowsInputProcessor : InputProcessor<InputRecord>
 {
     /// <inheritdoc />
-    public WindowsInputProcessor (ConcurrentQueue<WindowsConsole.InputRecord> inputBuffer) : base (inputBuffer) { }
+    public WindowsInputProcessor (ConcurrentQueue<InputRecord> inputBuffer) : base (inputBuffer) { }
 
     /// <inheritdoc />
-    protected override void Process (WindowsConsole.InputRecord inputEvent)
+    protected override void Process (InputRecord inputEvent)
     {
         switch (inputEvent.EventType)
         {
 
-            case WindowsConsole.EventType.Key:
+            case EventType.Key:
+
+                // TODO: For now ignore keyup because ANSI comes in as down+up which is confusing to try and parse/pair these things up
+                if (!inputEvent.KeyEvent.bKeyDown)
+                {
+                    return;
+                }
+
+                foreach (Tuple<char, InputRecord> released in Parser.ProcessInput (Tuple.Create (inputEvent.KeyEvent.UnicodeChar, inputEvent)))
+                {
+                    var key = (Key) (released.Item2.KeyEvent.UnicodeChar);
+                    OnKeyDown (key);
+                    OnKeyUp (key);
+                }
 
-                var mapped = (Key)inputEvent.KeyEvent.UnicodeChar;
                 /*
                 if (inputEvent.KeyEvent.wVirtualKeyCode == (VK)ConsoleKey.Packet)
                 {
@@ -42,18 +56,9 @@ protected override void Process (WindowsConsole.InputRecord inputEvent)
                 */
                 // This follows convention in NetDriver
 
-                if (inputEvent.KeyEvent.bKeyDown)
-                {
-                    OnKeyDown (mapped);
-                }
-                else
-                {
-                    OnKeyUp (mapped);
-                }
-
                 break;
 
-            case WindowsConsole.EventType.Mouse:
+            case EventType.Mouse:
                 MouseEventArgs me = ToDriverMouse (inputEvent.MouseEvent);
 
                 OnMouseEvent (me);
@@ -62,9 +67,9 @@ protected override void Process (WindowsConsole.InputRecord inputEvent)
         }
     }
 
-    private MouseEventArgs ToDriverMouse (WindowsConsole.MouseEventRecord e)
+    private MouseEventArgs ToDriverMouse (MouseEventRecord e)
     {
-        var result = new MouseEventArgs ()
+        var result = new MouseEventArgs
         {
             Position = new (e.MousePosition.X, e.MousePosition.Y)
         };
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
index 6fa2efcdcc..c23b9b16e6 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
@@ -91,7 +91,7 @@ public void Write (string str)
     public void Write (IOutputBuffer buffer)
     {
 
-        var outputBuffer = new WindowsConsole.ExtendedCharInfo [buffer.Rows * buffer.Cols];
+        var outputBuffer = new ExtendedCharInfo [buffer.Rows * buffer.Cols];
 
         Size windowSize = WinConsole?.GetConsoleBufferWindow (out Point _) ?? new Size (buffer.Cols, buffer.Rows);
         
@@ -102,7 +102,7 @@ public void Write (IOutputBuffer buffer)
             return;
         }*/
 
-        var bufferCoords = new WindowsConsole.Coord
+        var bufferCoords = new Coord
         {
             X = (short)buffer.Cols, //Clip.Width,
             Y = (short)buffer.Rows, //Clip.Height
@@ -153,7 +153,7 @@ public void Write (IOutputBuffer buffer)
             }
         }
 
-        var damageRegion = new WindowsConsole.SmallRect
+        var damageRegion = new SmallRect
         {
             Top = 0,
             Left = 0,
@@ -172,7 +172,7 @@ public void Write (IOutputBuffer buffer)
             }
         }
 
-        WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
+        SmallRect.MakeEmpty (ref damageRegion);
     }
 
     public Size GetWindowSize ()

From 5e202b037473818c909454032a05c764598d3f61 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 24 Nov 2024 19:08:41 +0000
Subject: [PATCH 020/198] Add IConsoleDriver

---
 NativeAot/Program.cs                          |   4 +-
 SelfContained/Program.cs                      |   2 +-
 .../Application/Application.Driver.cs         |   8 +-
 .../Application/Application.Initialization.cs |  20 +-
 .../Application/Application.Keyboard.cs       |   4 +-
 Terminal.Gui/Application/Application.Run.cs   |   8 +-
 .../Application/Application.Screen.cs         |   4 +-
 Terminal.Gui/Application/Application.cs       |   6 +-
 Terminal.Gui/Application/MainLoop.cs          |   4 +-
 Terminal.Gui/Clipboard/Clipboard.cs           |   2 +-
 Terminal.Gui/Clipboard/ClipboardBase.cs       |   4 +-
 .../AnsiEscapeSequenceRequest.cs              |   2 +-
 .../AnsiRequestScheduler.cs                   |   2 +-
 Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs  |  76 ++---
 .../CursesDriver/CursesDriver.cs              |  12 +-
 .../CursesDriver/UnixMainLoop.cs              |   2 +-
 .../ConsoleDrivers/FakeDriver/FakeDriver.cs   |  12 +-
 .../ConsoleDrivers/FakeDriver/FakeMainLoop.cs |   2 +-
 Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs | 295 ++++++++++++++++++
 Terminal.Gui/ConsoleDrivers/NetDriver.cs      |  14 +-
 .../ConsoleDrivers/V2/OutputBuffer.cs         |   2 +-
 Terminal.Gui/ConsoleDrivers/WindowsDriver.cs  |  20 +-
 Terminal.Gui/Drawing/Attribute.cs             |   2 +-
 Terminal.Gui/Drawing/Cell.cs                  |   2 +-
 Terminal.Gui/Drawing/LineCanvas.cs            |   6 +-
 Terminal.Gui/Drawing/SixelToRender.cs         |   2 +-
 Terminal.Gui/README.md                        |   4 +-
 Terminal.Gui/Text/TextFormatter.cs            |   4 +-
 Terminal.Gui/View/View.cs                     |   2 +-
 Terminal.Gui/Views/ColorPicker.Prompt.cs      |   2 +-
 Terminal.Gui/Views/GraphView/Axis.cs          |   2 +-
 Terminal.Gui/Views/Menu/MenuBar.cs            |   2 +-
 Terminal.Gui/Views/TableView/TableStyle.cs    |   2 +-
 Terminal.Gui/Views/TableView/TableView.cs     |   4 +-
 Terminal.Gui/Views/TreeView/Branch.cs         |  10 +-
 UICatalog/Scenarios/GraphViewExample.cs       |   2 +-
 UICatalog/UICatalog.cs                        |   2 +-
 UnitTests/Application/ApplicationTests.cs     |   2 +-
 UnitTests/ConsoleDrivers/AddRuneTests.cs      |   2 +-
 UnitTests/ConsoleDrivers/ClipRegionTests.cs   |   6 +-
 .../ConsoleDrivers/ConsoleDriverTests.cs      |  12 +-
 UnitTests/ConsoleDrivers/ContentsTests.cs     |   6 +-
 UnitTests/ConsoleDrivers/DriverColorTests.cs  |   6 +-
 .../ConsoleDrivers/MainLoopDriverTests.cs     |  24 +-
 UnitTests/TestHelpers.cs                      |  24 +-
 45 files changed, 465 insertions(+), 170 deletions(-)
 create mode 100644 Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs

diff --git a/NativeAot/Program.cs b/NativeAot/Program.cs
index bb0f38bc0d..e9c8cf77f9 100644
--- a/NativeAot/Program.cs
+++ b/NativeAot/Program.cs
@@ -9,8 +9,8 @@ namespace NativeAot;
 
 public static class Program
 {
-    [RequiresUnreferencedCode ("Calls Terminal.Gui.Application.Init(ConsoleDriver, String)")]
-    [RequiresDynamicCode ("Calls Terminal.Gui.Application.Init(ConsoleDriver, String)")]
+    [RequiresUnreferencedCode ("Calls Terminal.Gui.Application.Init(IConsoleDriver, String)")]
+    [RequiresDynamicCode ("Calls Terminal.Gui.Application.Init(IConsoleDriver, String)")]
     private static void Main (string [] args)
     {
         Application.Init ();
diff --git a/SelfContained/Program.cs b/SelfContained/Program.cs
index 29b7f5cd9c..c57f39e589 100644
--- a/SelfContained/Program.cs
+++ b/SelfContained/Program.cs
@@ -9,7 +9,7 @@ namespace SelfContained;
 
 public static class Program
 {
-    [RequiresUnreferencedCode ("Calls Terminal.Gui.Application.Run<T>(Func<Exception, Boolean>, ConsoleDriver)")]
+    [RequiresUnreferencedCode ("Calls Terminal.Gui.Application.Run<T>(Func<Exception, Boolean>, IConsoleDriver)")]
     private static void Main (string [] args)
     {
         Application.Init ();
diff --git a/Terminal.Gui/Application/Application.Driver.cs b/Terminal.Gui/Application/Application.Driver.cs
index c2f6431db7..7b3340cbdb 100644
--- a/Terminal.Gui/Application/Application.Driver.cs
+++ b/Terminal.Gui/Application/Application.Driver.cs
@@ -5,13 +5,13 @@ public static partial class Application // Driver abstractions
 {
     internal static bool _forceFakeConsole;
 
-    /// <summary>Gets the <see cref="ConsoleDriver"/> that has been selected. See also <see cref="ForceDriver"/>.</summary>
-    public static ConsoleDriver? Driver { get; internal set; }
+    /// <summary>Gets the <see cref="IConsoleDriver"/> that has been selected. See also <see cref="ForceDriver"/>.</summary>
+    public static IConsoleDriver? Driver { get; internal set; }
 
     /// <summary>
     ///     Gets or sets whether <see cref="Application.Driver"/> will be forced to output only the 16 colors defined in
     ///     <see cref="ColorName16"/>. The default is <see langword="false"/>, meaning 24-bit (TrueColor) colors will be output
-    ///     as long as the selected <see cref="ConsoleDriver"/> supports TrueColor.
+    ///     as long as the selected <see cref="IConsoleDriver"/> supports TrueColor.
     /// </summary>
     [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
     public static bool Force16Colors { get; set; }
@@ -21,7 +21,7 @@ public static partial class Application // Driver abstractions
     ///     specified, the driver is selected based on the platform.
     /// </summary>
     /// <remarks>
-    ///     Note, <see cref="Application.Init(ConsoleDriver, string)"/> will override this configuration setting if called
+    ///     Note, <see cref="Application.Init(IConsoleDriver, string)"/> will override this configuration setting if called
     ///     with either `driver` or `driverName` specified.
     /// </remarks>
     [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
diff --git a/Terminal.Gui/Application/Application.Initialization.cs b/Terminal.Gui/Application/Application.Initialization.cs
index 47c07a19f3..c58e1bf92a 100644
--- a/Terminal.Gui/Application/Application.Initialization.cs
+++ b/Terminal.Gui/Application/Application.Initialization.cs
@@ -10,7 +10,7 @@ public static partial class Application // Initialization (Init/Shutdown)
     /// <summary>Initializes a new instance of <see cref="Terminal.Gui"/> Application.</summary>
     /// <para>Call this method once per instance (or after <see cref="Shutdown"/> has been called).</para>
     /// <para>
-    ///     This function loads the right <see cref="ConsoleDriver"/> for the platform, Creates a <see cref="Toplevel"/>. and
+    ///     This function loads the right <see cref="IConsoleDriver"/> for the platform, Creates a <see cref="Toplevel"/>. and
     ///     assigns it to <see cref="Top"/>
     /// </para>
     /// <para>
@@ -21,23 +21,23 @@ public static partial class Application // Initialization (Init/Shutdown)
     /// </para>
     /// <para>
     ///     The <see cref="Run{T}"/> function combines
-    ///     <see cref="Init(Terminal.Gui.ConsoleDriver,string)"/> and <see cref="Run(Toplevel, Func{Exception, bool})"/>
+    ///     <see cref="Init(Terminal.Gui.IConsoleDriver,string)"/> and <see cref="Run(Toplevel, Func{Exception, bool})"/>
     ///     into a single
     ///     call. An application cam use <see cref="Run{T}"/> without explicitly calling
-    ///     <see cref="Init(Terminal.Gui.ConsoleDriver,string)"/>.
+    ///     <see cref="Init(Terminal.Gui.IConsoleDriver,string)"/>.
     /// </para>
     /// <param name="driver">
-    ///     The <see cref="ConsoleDriver"/> to use. If neither <paramref name="driver"/> or
+    ///     The <see cref="IConsoleDriver"/> to use. If neither <paramref name="driver"/> or
     ///     <paramref name="driverName"/> are specified the default driver for the platform will be used.
     /// </param>
     /// <param name="driverName">
     ///     The short name (e.g. "net", "windows", "ansi", "fake", or "curses") of the
-    ///     <see cref="ConsoleDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are
+    ///     <see cref="IConsoleDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are
     ///     specified the default driver for the platform will be used.
     /// </param>
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
-    public static void Init (ConsoleDriver? driver = null, string? driverName = null) { InternalInit (driver, driverName); }
+    public static void Init (IConsoleDriver? driver = null, string? driverName = null) { InternalInit (driver, driverName); }
 
     internal static int MainThreadId { get; set; } = -1;
 
@@ -53,7 +53,7 @@ public static partial class Application // Initialization (Init/Shutdown)
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
     internal static void InternalInit (
-        ConsoleDriver? driver = null,
+        IConsoleDriver? driver = null,
         string? driverName = null,
         bool calledViaRunT = false
     )
@@ -136,7 +136,7 @@ internal static void InternalInit (
 
                 if (driverType is { })
                 {
-                    Driver = (ConsoleDriver)Activator.CreateInstance (driverType)!;
+                    Driver = (IConsoleDriver)Activator.CreateInstance (driverType)!;
                 }
                 else
                 {
@@ -181,7 +181,7 @@ internal static void InternalInit (
     private static void Driver_KeyUp (object? sender, Key e) { RaiseKeyUpEvent (e); }
     private static void Driver_MouseEvent (object? sender, MouseEventArgs e) { RaiseMouseEvent (e); }
 
-    /// <summary>Gets of list of <see cref="ConsoleDriver"/> types that are available.</summary>
+    /// <summary>Gets of list of <see cref="IConsoleDriver"/> types that are available.</summary>
     /// <returns></returns>
     [RequiresUnreferencedCode ("AOT")]
     public static List<Type?> GetDriverTypes ()
@@ -193,7 +193,7 @@ internal static void InternalInit (
         {
             foreach (Type? type in asm.GetTypes ())
             {
-                if (type.IsSubclassOf (typeof (ConsoleDriver)) && !type.IsAbstract)
+                if (typeof (IConsoleDriver).IsAssignableFrom (type) && !type.IsAbstract && type.IsClass)
                 {
                     driverTypes.Add (type);
                 }
diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs
index 18881d6c57..16ddcf66c3 100644
--- a/Terminal.Gui/Application/Application.Keyboard.cs
+++ b/Terminal.Gui/Application/Application.Keyboard.cs
@@ -4,7 +4,7 @@ namespace Terminal.Gui;
 public static partial class Application // Keyboard handling
 {
     /// <summary>
-    ///     Called when the user presses a key (by the <see cref="ConsoleDriver"/>). Raises the cancelable
+    ///     Called when the user presses a key (by the <see cref="IConsoleDriver"/>). Raises the cancelable
     ///     <see cref="KeyDown"/> event, then calls <see cref="View.NewKeyDownEvent"/> on all top level views, and finally
     ///     if the key was not handled, invokes any Application-scoped <see cref="KeyBindings"/>.
     /// </summary>
@@ -111,7 +111,7 @@ public static bool RaiseKeyDownEvent (Key key)
     public static event EventHandler<Key>? KeyDown;
 
     /// <summary>
-    ///     Called when the user releases a key (by the <see cref="ConsoleDriver"/>). Raises the cancelable <see cref="KeyUp"/>
+    ///     Called when the user releases a key (by the <see cref="IConsoleDriver"/>). Raises the cancelable <see cref="KeyUp"/>
     ///     event
     ///     then calls <see cref="View.NewKeyUpEvent"/> on all top level views. Called after <see cref="RaiseKeyDownEvent"/>.
     /// </summary>
diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs
index 7511f82161..cf70ff5e32 100644
--- a/Terminal.Gui/Application/Application.Run.cs
+++ b/Terminal.Gui/Application/Application.Run.cs
@@ -305,7 +305,7 @@ internal static bool PositionCursor ()
     /// <returns>The created <see cref="Toplevel"/> object. The caller is responsible for disposing this object.</returns>
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
-    public static Toplevel Run (Func<Exception, bool>? errorHandler = null, ConsoleDriver? driver = null) { return Run<Toplevel> (errorHandler, driver); }
+    public static Toplevel Run (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null) { return Run<Toplevel> (errorHandler, driver); }
 
     /// <summary>
     ///     Runs the application by creating a <see cref="Toplevel"/>-derived object of type <c>T</c> and calling
@@ -323,14 +323,14 @@ internal static bool PositionCursor ()
     /// </remarks>
     /// <param name="errorHandler"></param>
     /// <param name="driver">
-    ///     The <see cref="ConsoleDriver"/> to use. If not specified the default driver for the platform will
+    ///     The <see cref="IConsoleDriver"/> to use. If not specified the default driver for the platform will
     ///     be used ( <see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, or <see cref="NetDriver"/>). Must be
     ///     <see langword="null"/> if <see cref="Init"/> has already been called.
     /// </param>
     /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
-    public static T Run<T> (Func<Exception, bool>? errorHandler = null, ConsoleDriver? driver = null)
+    public static T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
         where T : Toplevel, new()
     {
         if (!Initialized)
@@ -369,7 +369,7 @@ public static T Run<T> (Func<Exception, bool>? errorHandler = null, ConsoleDrive
     ///         return control immediately.
     ///     </para>
     ///     <para>When using <see cref="Run{T}"/> or
-    ///         <see cref="Run(System.Func{System.Exception,bool},Terminal.Gui.ConsoleDriver)"/>
+    ///         <see cref="Run(System.Func{System.Exception,bool},Terminal.Gui.IConsoleDriver)"/>
     ///         <see cref="Init"/> will be called automatically.
     ///     </para>
     ///     <para>
diff --git a/Terminal.Gui/Application/Application.Screen.cs b/Terminal.Gui/Application/Application.Screen.cs
index c5bf6d6fd0..a322aa31f2 100644
--- a/Terminal.Gui/Application/Application.Screen.cs
+++ b/Terminal.Gui/Application/Application.Screen.cs
@@ -6,11 +6,11 @@ public static partial class Application // Screen related stuff
     private static Rectangle? _screen;
 
     /// <summary>
-    ///     Gets or sets the size of the screen. By default, this is the size of the screen as reported by the <see cref="ConsoleDriver"/>.
+    ///     Gets or sets the size of the screen. By default, this is the size of the screen as reported by the <see cref="IConsoleDriver"/>.
     /// </summary>
     /// <remarks>
     /// <para>
-    ///     If the <see cref="ConsoleDriver"/> has not been initialized, this will return a default size of 2048x2048; useful for unit tests.
+    ///     If the <see cref="IConsoleDriver"/> has not been initialized, this will return a default size of 2048x2048; useful for unit tests.
     /// </para>
     /// </remarks>
     public static Rectangle Screen
diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs
index d9e6c68d92..216f8a90d2 100644
--- a/Terminal.Gui/Application/Application.cs
+++ b/Terminal.Gui/Application/Application.cs
@@ -32,7 +32,7 @@ public static partial class Application
     /// <returns>A string representation of the Application </returns>
     public new static string ToString ()
     {
-        ConsoleDriver? driver = Driver;
+        IConsoleDriver? driver = Driver;
 
         if (driver is null)
         {
@@ -43,11 +43,11 @@ public static partial class Application
     }
 
     /// <summary>
-    ///     Gets a string representation of the Application rendered by the provided <see cref="ConsoleDriver"/>.
+    ///     Gets a string representation of the Application rendered by the provided <see cref="IConsoleDriver"/>.
     /// </summary>
     /// <param name="driver">The driver to use to render the contents.</param>
     /// <returns>A string representation of the Application </returns>
-    public static string ToString (ConsoleDriver? driver)
+    public static string ToString (IConsoleDriver? driver)
     {
         if (driver is null)
         {
diff --git a/Terminal.Gui/Application/MainLoop.cs b/Terminal.Gui/Application/MainLoop.cs
index bfd7429130..ec3ff5ad80 100644
--- a/Terminal.Gui/Application/MainLoop.cs
+++ b/Terminal.Gui/Application/MainLoop.cs
@@ -36,7 +36,7 @@ internal interface IMainLoopDriver
 ///     Monitoring of file descriptors is only available on Unix, there does not seem to be a way of supporting this
 ///     on Windows.
 /// </remarks>
-internal class MainLoop : IDisposable
+public class MainLoop : IDisposable
 {
     internal List<Func<bool>> _idleHandlers = new ();
     internal SortedList<long, Timeout> _timeouts = new ();
@@ -49,7 +49,7 @@ internal class MainLoop : IDisposable
     /// <summary>Creates a new MainLoop.</summary>
     /// <remarks>Use <see cref="Dispose"/> to release resources.</remarks>
     /// <param name="driver">
-    ///     The <see cref="ConsoleDriver"/> instance (one of the implementations FakeMainLoop, UnixMainLoop,
+    ///     The <see cref="IConsoleDriver"/> instance (one of the implementations FakeMainLoop, UnixMainLoop,
     ///     NetMainLoop or WindowsMainLoop).
     /// </param>
     internal MainLoop (IMainLoopDriver driver)
diff --git a/Terminal.Gui/Clipboard/Clipboard.cs b/Terminal.Gui/Clipboard/Clipboard.cs
index f8bf907c75..ecb59205f1 100644
--- a/Terminal.Gui/Clipboard/Clipboard.cs
+++ b/Terminal.Gui/Clipboard/Clipboard.cs
@@ -111,7 +111,7 @@ public static bool TrySetClipboardData (string text)
 
 /// <summary>
 ///     Helper class for console drivers to invoke shell commands to interact with the clipboard. Used primarily by
-///     CursesDriver, but also used in Unit tests which is why it is in ConsoleDriver.cs.
+///     CursesDriver, but also used in Unit tests which is why it is in IConsoleDriver.cs.
 /// </summary>
 internal static class ClipboardProcessRunner
 {
diff --git a/Terminal.Gui/Clipboard/ClipboardBase.cs b/Terminal.Gui/Clipboard/ClipboardBase.cs
index 8c01e9c437..8c1c0a93a7 100644
--- a/Terminal.Gui/Clipboard/ClipboardBase.cs
+++ b/Terminal.Gui/Clipboard/ClipboardBase.cs
@@ -103,7 +103,7 @@ public bool TrySetClipboardData (string text)
     }
 
     /// <summary>
-    ///     Returns the contents of the OS clipboard if possible. Implemented by <see cref="ConsoleDriver"/>-specific
+    ///     Returns the contents of the OS clipboard if possible. Implemented by <see cref="IConsoleDriver"/>-specific
     ///     subclasses.
     /// </summary>
     /// <returns>The contents of the OS clipboard if successful.</returns>
@@ -111,7 +111,7 @@ public bool TrySetClipboardData (string text)
     protected abstract string GetClipboardDataImpl ();
 
     /// <summary>
-    ///     Pastes the <paramref name="text"/> to the OS clipboard if possible. Implemented by <see cref="ConsoleDriver"/>
+    ///     Pastes the <paramref name="text"/> to the OS clipboard if possible. Implemented by <see cref="IConsoleDriver"/>
     ///     -specific subclasses.
     /// </summary>
     /// <param name="text">The text to paste to the OS clipboard.</param>
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequenceRequest.cs b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequenceRequest.cs
index 3d4c9ebc3e..8055f269a6 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequenceRequest.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiEscapeSequenceRequest.cs
@@ -48,7 +48,7 @@ public class AnsiEscapeSequenceRequest
     public required string Terminator { get; init; }
 
     /// <summary>
-    ///     Sends the <see cref="Request"/> to the raw output stream of the current <see cref="ConsoleDriver"/>.
+    ///     Sends the <see cref="Request"/> to the raw output stream of the current <see cref="IConsoleDriver"/>.
     ///     Only call this method from the main UI thread. You should use <see cref="AnsiRequestScheduler"/> if
     ///     sending many requests.
     /// </summary>
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiRequestScheduler.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiRequestScheduler.cs
index 12a2c270fb..f7d6c4d9eb 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiRequestScheduler.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiRequestScheduler.cs
@@ -9,7 +9,7 @@ namespace Terminal.Gui;
 ///     to prevent console becoming unresponsive and handles evicting ignored requests (no reply from
 ///     terminal).
 /// </summary>
-internal class AnsiRequestScheduler
+public class AnsiRequestScheduler
 {
     private readonly IAnsiResponseParser _parser;
 
diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
index a5992a1cda..6a52b4b74f 100644
--- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
@@ -1,25 +1,25 @@
 #nullable enable
 //
-// ConsoleDriver.cs: Base class for Terminal.Gui ConsoleDriver implementations.
+// IConsoleDriver.cs: Base class for Terminal.Gui IConsoleDriver implementations.
 //
 
 using System.Diagnostics;
 
 namespace Terminal.Gui;
 
-/// <summary>Base class for Terminal.Gui ConsoleDriver implementations.</summary>
+/// <summary>Base class for Terminal.Gui IConsoleDriver implementations.</summary>
 /// <remarks>
 ///     There are currently four implementations: - <see cref="CursesDriver"/> (for Unix and Mac) -
 ///     <see cref="WindowsDriver"/> - <see cref="NetDriver"/> that uses the .NET Console API - <see cref="FakeConsole"/>
 ///     for unit testing.
 /// </remarks>
-public abstract class ConsoleDriver
+public abstract class ConsoleDriver : IConsoleDriver
 {
 
     /// <summary>
     /// How long after Esc has been pressed before we give up on getting an Ansi escape sequence
     /// </summary>
-    internal TimeSpan EscTimeout = TimeSpan.FromMilliseconds (50);
+    public TimeSpan EscTimeout { get; } = TimeSpan.FromMilliseconds (50);
 
     // 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.
@@ -27,7 +27,7 @@ public abstract class ConsoleDriver
 
     // QUESTION: When non-full screen apps are supported, will this represent the app size, or will that be in Application?
     /// <summary>Gets the location and size of the terminal screen.</summary>
-    internal Rectangle Screen => new (0, 0, Cols, Rows);
+    public Rectangle Screen => new (0, 0, Cols, Rows);
 
     private Region? _clip = null;
 
@@ -36,7 +36,7 @@ public abstract class ConsoleDriver
     ///     to.
     /// </summary>
     /// <value>The rectangle describing the of <see cref="Clip"/> region.</value>
-    internal Region? Clip
+    public Region? Clip
     {
         get => _clip;
         set
@@ -63,10 +63,10 @@ internal Region? Clip
     ///     Gets the column 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.
     /// </summary>
-    internal int Col { get; private set; }
+    public int Col { get; private set; }
 
     /// <summary>The number of columns visible in the terminal.</summary>
-    internal virtual int Cols
+    public virtual int Cols
     {
         get => _cols;
         set
@@ -81,19 +81,19 @@ internal virtual int Cols
     ///     <see cref="UpdateScreen"/> is called.
     ///     <remarks>The format of the array is rows, columns. The first index is the row, the second index is the column.</remarks>
     /// </summary>
-    internal Cell [,]? Contents { get; set; }
+    public Cell [,]? Contents { get; set; }
 
     /// <summary>The leftmost column in the terminal.</summary>
-    internal virtual int Left { get; set; } = 0;
+    public virtual int Left { get; set; } = 0;
 
     /// <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.
     /// </summary>
-    internal int Row { get; private set; }
+    public int Row { get; private set; }
 
     /// <summary>The number of rows visible in the terminal.</summary>
-    internal virtual int Rows
+    public virtual int Rows
     {
         get => _rows;
         set
@@ -104,7 +104,7 @@ internal virtual int Rows
     }
 
     /// <summary>The topmost row in the terminal.</summary>
-    internal virtual int Top { get; set; } = 0;
+    public virtual int Top { get; set; } = 0;
 
     /// <summary>
     ///     Set this to true in any unit tests that attempt to test drivers other than FakeDriver.
@@ -131,7 +131,7 @@ internal virtual int Rows
     ///     </para>
     /// </remarks>
     /// <param name="rune">Rune to add.</param>
-    internal void AddRune (Rune rune)
+    public void AddRune (Rune rune)
     {
         int runeWidth = -1;
         bool validLocation = IsValidLocation (rune, Col, Row);
@@ -306,7 +306,7 @@ internal void AddRune (Rune rune)
     ///     convenience method that calls <see cref="AddRune(Rune)"/> with the <see cref="Rune"/> constructor.
     /// </summary>
     /// <param name="c">Character to add.</param>
-    internal void AddRune (char c) { AddRune (new Rune (c)); }
+    public void AddRune (char c) { AddRune (new Rune (c)); }
 
     /// <summary>Adds the <paramref name="str"/> to the display at the cursor position.</summary>
     /// <remarks>
@@ -318,7 +318,7 @@ internal void AddRune (Rune rune)
     ///     <para>If <paramref name="str"/> requires more columns than are available, the output will be clipped.</para>
     /// </remarks>
     /// <param name="str">String.</param>
-    internal void AddStr (string str)
+    public void AddStr (string str)
     {
         List<Rune> runes = str.EnumerateRunes ().ToList ();
 
@@ -329,7 +329,7 @@ internal void AddStr (string str)
     }
 
     /// <summary>Clears the <see cref="Contents"/> of the driver.</summary>
-    internal void ClearContents ()
+    public void ClearContents ()
     {
         Contents = new Cell [Rows, Cols];
 
@@ -368,7 +368,7 @@ internal void ClearContents ()
     /// Sets <see cref="Contents"/> as dirty for situations where views
     /// don't need layout and redrawing, but just refresh the screen.
     /// </summary>
-    internal void SetContentsAsDirty ()
+    public void SetContentsAsDirty ()
     {
         lock (Contents!)
         {
@@ -393,7 +393,7 @@ internal void SetContentsAsDirty ()
     /// </remarks>
     /// <param name="rect">The Screen-relative rectangle.</param>
     /// <param name="rune">The Rune used to fill the rectangle</param>
-    internal void FillRect (Rectangle rect, Rune rune = default)
+    public void FillRect (Rectangle rect, Rune rune = default)
     {
         // BUGBUG: This should be a method on Region
         rect = Rectangle.Intersect (rect, Clip?.GetBounds () ?? Screen);
@@ -424,7 +424,7 @@ internal void FillRect (Rectangle rect, Rune rune = default)
     /// </summary>
     /// <param name="rect"></param>
     /// <param name="c"></param>
-    internal void FillRect (Rectangle rect, char c) { FillRect (rect, new Rune (c)); }
+    public void FillRect (Rectangle rect, char c) { FillRect (rect, new Rune (c)); }
 
     /// <summary>Gets the terminal cursor visibility.</summary>
     /// <param name="visibility">The current <see cref="CursorVisibility"/></param>
@@ -451,7 +451,7 @@ internal void FillRect (Rectangle rect, Rune rune = default)
     ///     <see langword="false"/> if the coordinate is outside the screen bounds or outside of <see cref="Clip"/>.
     ///     <see langword="true"/> otherwise.
     /// </returns>
-    internal bool IsValidLocation (Rune rune, int col, int row)
+    public bool IsValidLocation (Rune rune, int col, int row)
     {
         if (rune.GetColumns () < 2)
         {
@@ -487,10 +487,10 @@ public virtual void Move (int col, int row)
 
     /// <summary>Called when the terminal size changes. Fires the <see cref="SizeChanged"/> event.</summary>
     /// <param name="args"></param>
-    internal void OnSizeChanged (SizeChangedEventArgs args) { SizeChanged?.Invoke (this, args); }
+    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>
-    internal void Refresh ()
+    public void Refresh ()
     {
         bool updated = UpdateScreen ();
         UpdateCursor ();
@@ -526,31 +526,31 @@ internal void Refresh ()
 
     /// <summary>Initializes the driver</summary>
     /// <returns>Returns an instance of <see cref="MainLoop"/> using the <see cref="IMainLoopDriver"/> for the driver.</returns>
-    internal abstract MainLoop Init ();
+    public abstract MainLoop Init ();
 
     /// <summary>Ends the execution of the console driver.</summary>
-    internal abstract void End ();
+    public abstract void End ();
 
     #endregion
 
     #region Color Handling
 
-    /// <summary>Gets whether the <see cref="ConsoleDriver"/> supports TrueColor output.</summary>
+    /// <summary>Gets whether the <see cref="IConsoleDriver"/> supports TrueColor output.</summary>
     public virtual bool SupportsTrueColor => true;
 
-    // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
+    // TODO: This makes IConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
     // BUGBUG: Application.Force16Colors should be bool? so if SupportsTrueColor and Application.Force16Colors == false, this doesn't override
     /// <summary>
-    ///     Gets or sets whether the <see cref="ConsoleDriver"/> should use 16 colors instead of the default TrueColors.
+    ///     Gets or sets whether the <see cref="IConsoleDriver"/> should use 16 colors instead of the default TrueColors.
     ///     See <see cref="Application.Force16Colors"/> to change this setting via <see cref="ConfigurationManager"/>.
     /// </summary>
     /// <remarks>
     ///     <para>
-    ///         Will be forced to <see langword="true"/> if <see cref="ConsoleDriver.SupportsTrueColor"/> is
-    ///         <see langword="false"/>, indicating that the <see cref="ConsoleDriver"/> cannot support TrueColor.
+    ///         Will be forced to <see langword="true"/> if <see cref="IConsoleDriver.SupportsTrueColor"/> is
+    ///         <see langword="false"/>, indicating that the <see cref="IConsoleDriver"/> cannot support TrueColor.
     ///     </para>
     /// </remarks>
-    internal virtual bool Force16Colors
+    public virtual bool Force16Colors
     {
         get => Application.Force16Colors || !SupportsTrueColor;
         set => Application.Force16Colors = value || !SupportsTrueColor;
@@ -569,7 +569,7 @@ public Attribute CurrentAttribute
         get => _currentAttribute;
         set
         {
-            // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. Once Attribute.PlatformColor is removed, this can be fixed.
+            // TODO: This makes IConsoleDriver 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);
@@ -584,7 +584,7 @@ public Attribute CurrentAttribute
     /// <summary>Selects the specified attribute as the attribute to use for future calls to AddRune and AddString.</summary>
     /// <remarks>Implementations should call <c>base.SetAttribute(c)</c>.</remarks>
     /// <param name="c">C.</param>
-    internal Attribute SetAttribute (Attribute c)
+    public Attribute SetAttribute (Attribute c)
     {
         Attribute prevAttribute = CurrentAttribute;
         CurrentAttribute = c;
@@ -594,7 +594,7 @@ internal Attribute SetAttribute (Attribute c)
 
     /// <summary>Gets the current <see cref="Attribute"/>.</summary>
     /// <returns>The current attribute.</returns>
-    internal Attribute GetAttribute () { return CurrentAttribute; }
+    public Attribute GetAttribute () { return CurrentAttribute; }
 
     // TODO: This is only overridden by CursesDriver. Once CursesDriver supports 24-bit color, this virtual method can be
     // removed (and Attribute can lose the platformColor property).
@@ -675,9 +675,9 @@ public void QueueAnsiRequest (AnsiEscapeSequenceRequest request)
         GetRequestScheduler ().SendOrSchedule (request);
     }
 
-    internal abstract IAnsiResponseParser GetParser ();
+    public abstract IAnsiResponseParser GetParser ();
 
-    internal AnsiRequestScheduler GetRequestScheduler ()
+    public AnsiRequestScheduler GetRequestScheduler ()
     {
         // Lazy initialization because GetParser is virtual
         return _scheduler ??= new (GetParser ());
@@ -688,11 +688,11 @@ internal AnsiRequestScheduler GetRequestScheduler ()
     /// draw buffer).
     /// </summary>
     /// <param name="str"></param>
-    internal abstract void RawWrite (string str);
+    public abstract void RawWrite (string str);
 }
 
 /// <summary>
-///     The <see cref="KeyCode"/> enumeration encodes key information from <see cref="ConsoleDriver"/>s and provides a
+///     The <see cref="KeyCode"/> enumeration encodes key information from <see cref="IConsoleDriver"/>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
diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
index 06fa271e9d..4d94beeca5 100644
--- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
@@ -19,7 +19,7 @@ internal class CursesDriver : ConsoleDriver
     private UnixMainLoop _mainLoopDriver;
     private object _processInputToken;
 
-    internal override int Cols
+    public override int Cols
     {
         get => Curses.Cols;
         set
@@ -29,7 +29,7 @@ internal override int Cols
         }
     }
 
-    internal override int Rows
+    public override int Rows
     {
         get => Curses.Lines;
         set
@@ -190,7 +190,7 @@ public void StopReportingMouseMoves ()
     }
 
     /// <inheritdoc />
-    internal override void RawWrite (string str)
+    public override void RawWrite (string str)
     {
         Console.Out.Write (str);
     }
@@ -457,7 +457,7 @@ private bool SetCursorPosition (int col, int row)
         return true;
     }
 
-    internal override void End ()
+    public override void End ()
     {
         StopReportingMouseMoves ();
         SetCursorVisibility (CursorVisibility.Default);
@@ -479,7 +479,7 @@ internal override void End ()
         Curses.endwin ();
     }
 
-    internal override MainLoop Init ()
+    public override MainLoop Init ()
     {
         _mainLoopDriver = new UnixMainLoop (this);
 
@@ -592,7 +592,7 @@ internal override MainLoop Init ()
 
     private readonly AnsiResponseParser _parser = new ();
     /// <inheritdoc />
-    internal override IAnsiResponseParser GetParser () => _parser;
+    public override IAnsiResponseParser GetParser () => _parser;
 
     internal void ProcessInput ()
     {
diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs
index 34139815c5..f5217bed99 100644
--- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs
@@ -47,7 +47,7 @@ public enum Condition : short
     private Pollfd [] _pollMap;
     private bool _winChanged;
 
-    public UnixMainLoop (ConsoleDriver consoleDriver = null)
+    public UnixMainLoop (IConsoleDriver consoleDriver = null)
     {
         // UnixDriver doesn't use the consoleDriver parameter, but the WindowsDriver does.
         _cursesDriver = (CursesDriver)Application.Driver;
diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
index 9f30dc798c..3f1bda92bc 100644
--- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
@@ -1,5 +1,5 @@
 //
-// FakeDriver.cs: A fake ConsoleDriver for unit tests. 
+// FakeDriver.cs: A fake IConsoleDriver for unit tests. 
 //
 
 using System.Diagnostics;
@@ -10,7 +10,7 @@
 
 namespace Terminal.Gui;
 
-/// <summary>Implements a mock ConsoleDriver for unit testing</summary>
+/// <summary>Implements a mock IConsoleDriver for unit testing</summary>
 public class FakeDriver : ConsoleDriver
 {
 #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
@@ -76,7 +76,7 @@ public FakeDriver ()
         }
     }
 
-    internal override void End ()
+    public override void End ()
     {
         FakeConsole.ResetColor ();
         FakeConsole.Clear ();
@@ -84,7 +84,7 @@ internal override void End ()
 
     private FakeMainLoop _mainLoopDriver;
 
-    internal override MainLoop Init ()
+    public override MainLoop Init ()
     {
         FakeConsole.MockKeyPresses.Clear ();
 
@@ -395,10 +395,10 @@ public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool al
     private AnsiResponseParser _parser = new ();
 
     /// <inheritdoc />
-    internal override IAnsiResponseParser GetParser () => _parser;
+    public override IAnsiResponseParser GetParser () => _parser;
 
     /// <inheritdoc />
-    internal override void RawWrite (string str) { }
+    public override void RawWrite (string str) { }
 
     public void SetBufferSize (int width, int height)
     {
diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs
index d90caace7b..6b6789fc46 100644
--- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs
@@ -4,7 +4,7 @@ internal class FakeMainLoop : IMainLoopDriver
 {
     public Action<ConsoleKeyInfo> MockKeyPressed;
 
-    public FakeMainLoop (ConsoleDriver consoleDriver = null)
+    public FakeMainLoop (IConsoleDriver consoleDriver = null)
     {
         // No implementation needed for FakeMainLoop
     }
diff --git a/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
new file mode 100644
index 0000000000..f3d742ab3b
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
@@ -0,0 +1,295 @@
+namespace Terminal.Gui;
+
+public interface IConsoleDriver
+{
+
+    /// <summary>
+    /// How long after Esc has been pressed before we give up on getting an Ansi escape sequence
+    /// </summary>
+    TimeSpan EscTimeout { get; }
+
+    /// <summary>Gets the location and size of the terminal screen.</summary>
+    Rectangle Screen { get; }
+
+    /// <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>
+    Region Clip { get; set; }
+
+    /// <summary>Get the operating system clipboard.</summary>
+    IClipboard Clipboard { get; }
+
+    /// <summary>
+    ///     Gets the column 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.
+    /// </summary>
+    int Col { get; }
+
+    /// <summary>The number of columns visible in the terminal.</summary>
+    int Cols { get; set; }
+
+    /// <summary>
+    ///     The contents of the application output. The driver outputs this buffer to the terminal when
+    ///     <see cref="UpdateScreen"/> is called.
+    ///     <remarks>The format of the array is rows, columns. The first index is the row, the second index is the column.</remarks>
+    /// </summary>
+    Cell [,] Contents { get; set; }
+
+    /// <summary>The leftmost column in the terminal.</summary>
+    int Left { get; set; }
+
+    /// <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.
+    /// </summary>
+    int Row { get; }
+
+    /// <summary>The number of rows visible in the terminal.</summary>
+    int Rows { get; set; }
+
+    /// <summary>The topmost row in the terminal.</summary>
+    int Top { get; set; }
+
+    /// <summary>Gets whether the <see cref="ConsoleDriver"/> supports TrueColor output.</summary>
+    bool SupportsTrueColor { get; }
+
+    /// <summary>
+    ///     Gets or sets whether the <see cref="ConsoleDriver"/> should use 16 colors instead of the default TrueColors.
+    ///     See <see cref="Application.Force16Colors"/> to change this setting via <see cref="ConfigurationManager"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Will be forced to <see langword="true"/> if <see cref="ConsoleDriver.SupportsTrueColor"/> is
+    ///         <see langword="false"/>, indicating that the <see cref="ConsoleDriver"/> cannot support TrueColor.
+    ///     </para>
+    /// </remarks>
+    bool Force16Colors { get; set; }
+
+    /// <summary>
+    ///     The <see cref="Attribute"/> that will be used for the next <see cref="AddRune(Rune)"/> or <see cref="AddStr"/>
+    ///     call.
+    /// </summary>
+    Attribute CurrentAttribute { get; set; }
+
+    /// <summary>Adds the specified rune to the display at the current cursor position.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         When the method returns, <see cref="ConsoleDriver.Col"/> will be incremented by the number of columns
+    ///         <paramref name="rune"/> required, even if the new column value is outside of the <see cref="ConsoleDriver.Clip"/> or screen
+    ///         dimensions defined by <see cref="ConsoleDriver.Cols"/>.
+    ///     </para>
+    ///     <para>
+    ///         If <paramref name="rune"/> requires more than one column, and <see cref="ConsoleDriver.Col"/> plus the number of columns
+    ///         needed exceeds the <see cref="ConsoleDriver.Clip"/> or screen dimensions, the default Unicode replacement character (U+FFFD)
+    ///         will be added instead.
+    ///     </para>
+    /// </remarks>
+    /// <param name="rune">Rune to add.</param>
+    void AddRune (Rune rune);
+
+    /// <summary>
+    ///     Adds the specified <see langword="char"/> to the display at the current cursor position. This method is a
+    ///     convenience method that calls <see cref="ConsoleDriver.AddRune(System.Text.Rune)"/> with the <see cref="Rune"/> constructor.
+    /// </summary>
+    /// <param name="c">Character to add.</param>
+    void AddRune (char c);
+
+    /// <summary>Adds the <paramref name="str"/> to the display at the cursor position.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         When the method returns, <see cref="ConsoleDriver.Col"/> will be incremented by the number of columns
+    ///         <paramref name="str"/> required, unless the new column value is outside of the <see cref="ConsoleDriver.Clip"/> or screen
+    ///         dimensions defined by <see cref="ConsoleDriver.Cols"/>.
+    ///     </para>
+    ///     <para>If <paramref name="str"/> requires more columns than are available, the output will be clipped.</para>
+    /// </remarks>
+    /// <param name="str">String.</param>
+    void AddStr (string str);
+
+    /// <summary>Clears the <see cref="ConsoleDriver.Contents"/> of the driver.</summary>
+    void ClearContents ();
+
+    /// <summary>
+    ///     Raised each time <see cref="ConsoleDriver.ClearContents"/> is called. For benchmarking.
+    /// </summary>
+    event EventHandler<EventArgs> ClearedContents;
+
+    /// <summary>
+    /// Sets <see cref="ConsoleDriver.Contents"/> as dirty for situations where views
+    /// don't need layout and redrawing, but just refresh the screen.
+    /// </summary>
+    void SetContentsAsDirty ();
+
+    /// <summary>Determines if the terminal cursor should be visible or not and sets it accordingly.</summary>
+    /// <returns><see langword="true"/> upon success</returns>
+    bool EnsureCursorVisibility ();
+
+    /// <summary>Fills the specified rectangle with the specified rune, using <see cref="ConsoleDriver.CurrentAttribute"/></summary>
+    /// <remarks>
+    /// The value of <see cref="ConsoleDriver.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>
+    void FillRect (Rectangle rect, Rune rune = default);
+
+    /// <summary>
+    ///     Fills the specified rectangle with the specified <see langword="char"/>. This method is a convenience method
+    ///     that calls <see cref="ConsoleDriver.FillRect(System.Drawing.Rectangle,System.Text.Rune)"/>.
+    /// </summary>
+    /// <param name="rect"></param>
+    /// <param name="c"></param>
+    void FillRect (Rectangle rect, char c);
+
+    /// <summary>Gets the terminal cursor visibility.</summary>
+    /// <param name="visibility">The current <see cref="CursorVisibility"/></param>
+    /// <returns><see langword="true"/> upon success</returns>
+    bool GetCursorVisibility (out CursorVisibility visibility);
+
+    /// <summary>Returns the name of the driver and relevant library version information.</summary>
+    /// <returns></returns>
+    string GetVersionInfo ();
+
+    /// <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>
+    bool IsRuneSupported (Rune rune);
+
+    /// <summary>Tests whether the specified coordinate are valid for drawing the specified Rune.</summary>
+    /// <param name="rune">Used to determine if one or two columns are required.</param>
+    /// <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="ConsoleDriver.Clip"/>.
+    ///     <see langword="true"/> otherwise.
+    /// </returns>
+    bool IsValidLocation (Rune rune, int col, int row);
+
+    /// <summary>
+    ///     Updates <see cref="ConsoleDriver.Col"/> and <see cref="ConsoleDriver.Row"/> to the specified column and row in <see cref="ConsoleDriver.Contents"/>.
+    ///     Used by <see cref="ConsoleDriver.AddRune(System.Text.Rune)"/> and <see cref="ConsoleDriver.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="ConsoleDriver.Cols"/> and
+    ///         <see cref="ConsoleDriver.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>
+    void Move (int col, int row);
+
+    /// <summary>Called when the terminal size changes. Fires the <see cref="ConsoleDriver.SizeChanged"/> event.</summary>
+    /// <param name="args"></param>
+    void OnSizeChanged (SizeChangedEventArgs args);
+
+    /// <summary>Updates the screen to reflect all the changes that have been done to the display buffer</summary>
+    void Refresh ();
+
+    /// <summary>
+    ///     Raised each time <see cref="ConsoleDriver.Refresh"/> is called. For benchmarking.
+    /// </summary>
+    event EventHandler<EventArgs<bool>> Refreshed;
+
+    /// <summary>Sets the terminal cursor visibility.</summary>
+    /// <param name="visibility">The wished <see cref="CursorVisibility"/></param>
+    /// <returns><see langword="true"/> upon success</returns>
+    bool SetCursorVisibility (CursorVisibility visibility);
+
+    /// <summary>The event fired when the terminal is resized.</summary>
+    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>
+    void Suspend ();
+
+    /// <summary>Sets the position of the terminal cursor to <see cref="ConsoleDriver.Col"/> and <see cref="ConsoleDriver.Row"/>.</summary>
+    void UpdateCursor ();
+
+    /// <summary>Redraws the physical screen with the contents that have been queued up via any of the printing commands.</summary>
+    /// <returns><see langword="true"/> if any updates to the screen were made.</returns>
+    bool UpdateScreen ();
+
+    /// <summary>Initializes the driver</summary>
+    /// <returns>Returns an instance of <see cref="MainLoop"/> using the <see cref="IMainLoopDriver"/> for the driver.</returns>
+    MainLoop Init ();
+
+    /// <summary>Ends the execution of the console driver.</summary>
+    void End ();
+
+    /// <summary>Selects the specified attribute as the attribute to use for future calls to AddRune and AddString.</summary>
+    /// <remarks>Implementations should call <c>base.SetAttribute(c)</c>.</remarks>
+    /// <param name="c">C.</param>
+    Attribute SetAttribute (Attribute c);
+
+    /// <summary>Gets the current <see cref="Attribute"/>.</summary>
+    /// <returns>The current attribute.</returns>
+    Attribute GetAttribute ();
+
+    /// <summary>Makes an <see cref="Attribute"/>.</summary>
+    /// <param name="foreground">The foreground color.</param>
+    /// <param name="background">The background color.</param>
+    /// <returns>The attribute for the foreground and background colors.</returns>
+    Attribute MakeColor (in Color foreground, in Color background);
+
+    /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="ConsoleDriver.KeyUp"/>.</summary>
+    event EventHandler<Key> KeyDown;
+
+    /// <summary>
+    ///     Called when a key is pressed down. Fires the <see cref="ConsoleDriver.KeyDown"/> event. This is a precursor to
+    ///     <see cref="ConsoleDriver.OnKeyUp"/>.
+    /// </summary>
+    /// <param name="a"></param>
+    void OnKeyDown (Key a);
+
+    /// <summary>Event fired when a key is released.</summary>
+    /// <remarks>
+    ///     Drivers that do not support key release events will fire this event after <see cref="ConsoleDriver.KeyDown"/> processing is
+    ///     complete.
+    /// </remarks>
+    event EventHandler<Key> KeyUp;
+
+    /// <summary>Called when a key is released. Fires the <see cref="ConsoleDriver.KeyUp"/> event.</summary>
+    /// <remarks>
+    ///     Drivers that do not support key release events will call this method after <see cref="ConsoleDriver.OnKeyDown"/> processing
+    ///     is complete.
+    /// </remarks>
+    /// <param name="a"></param>
+    void OnKeyUp (Key a);
+
+    /// <summary>Event fired when a mouse event occurs.</summary>
+    event EventHandler<MouseEventArgs> MouseEvent;
+
+    /// <summary>Called when a mouse event occurs. Fires the <see cref="ConsoleDriver.MouseEvent"/> event.</summary>
+    /// <param name="a"></param>
+    void OnMouseEvent (MouseEventArgs a);
+
+    /// <summary>Simulates a key press.</summary>
+    /// <param name="keyChar">The key character.</param>
+    /// <param name="key">The key.</param>
+    /// <param name="shift">If <see langword="true"/> simulates the Shift key being pressed.</param>
+    /// <param name="alt">If <see langword="true"/> simulates the Alt key being pressed.</param>
+    /// <param name="ctrl">If <see langword="true"/> simulates the Ctrl key being pressed.</param>
+    void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl);
+
+    /// <summary>
+    /// Queues the given <paramref name="request"/> for execution
+    /// </summary>
+    /// <param name="request"></param>
+    void QueueAnsiRequest (AnsiEscapeSequenceRequest request);
+
+    IAnsiResponseParser GetParser ();
+    AnsiRequestScheduler GetRequestScheduler ();
+
+    /// <summary>
+    /// Writes the given <paramref name="str"/> directly to the console (rather than the output
+    /// draw buffer).
+    /// </summary>
+    /// <param name="str"></param>
+    void RawWrite (string str);
+}
diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs
index 8413b1d9f0..162de5f8a3 100644
--- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs
@@ -140,13 +140,13 @@ internal class NetEvents : IDisposable
     //CancellationTokenSource _waitForStartCancellationTokenSource;
     private readonly ManualResetEventSlim _winChange = new (false);
     private readonly BlockingCollection<InputResult?> _inputQueue = new (new ConcurrentQueue<InputResult?> ());
-    private readonly ConsoleDriver _consoleDriver;
+    private readonly IConsoleDriver _consoleDriver;
 
     public EscSeqRequests EscSeqRequests { get; } = new ();
 
     public AnsiResponseParser<ConsoleKeyInfo> Parser { get; private set; } = new ();
 
-    public NetEvents (ConsoleDriver consoleDriver)
+    public NetEvents (IConsoleDriver consoleDriver)
     {
         _consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver));
 
@@ -992,15 +992,15 @@ void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int out
     }
 
     /// <inheritdoc />
-    internal override IAnsiResponseParser GetParser () => _mainLoopDriver._netEvents.Parser;
+    public override IAnsiResponseParser GetParser () => _mainLoopDriver._netEvents.Parser;
 
     /// <inheritdoc />
-    internal override void RawWrite (string str)
+    public override void RawWrite (string str)
     {
         Console.Write (str);
     }
 
-    internal override void End ()
+    public override void End ()
     {
         if (IsWinPlatform)
         {
@@ -1022,7 +1022,7 @@ internal override void End ()
         }
     }
 
-    internal override MainLoop Init ()
+    public override MainLoop Init ()
     {
         PlatformID p = Environment.OSVersion.Platform;
 
@@ -1558,7 +1558,7 @@ internal class NetMainLoop : IMainLoopDriver
     /// <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)
+    public NetMainLoop (IConsoleDriver consoleDriver = null)
     {
         if (consoleDriver is null)
         {
diff --git a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
index 20cd5e112d..16a3a4fb68 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
@@ -29,7 +29,7 @@ public Attribute CurrentAttribute
         get => _currentAttribute;
         set
         {
-            // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. Once Attribute.PlatformColor is removed, this can be fixed.
+            // TODO: This makes IConsoleDriver 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);
diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
index 84b8fe9bec..c66945e1a5 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
@@ -1173,10 +1173,10 @@ public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool al
     }
 
     /// <inheritdoc />
-    internal override IAnsiResponseParser GetParser () => _parser;
+    public override IAnsiResponseParser GetParser () => _parser;
 
     /// <inheritdoc />
-    internal override void RawWrite (string str) => WinConsole?.WriteANSI (str);
+    public override void RawWrite (string str) => WinConsole?.WriteANSI (str);
 
     #region Not Implemented
 
@@ -1386,7 +1386,7 @@ public override bool UpdateScreen ()
         return updated;
     }
 
-    internal override void End ()
+    public override void End ()
     {
         if (_mainLoopDriver is { })
         {
@@ -1408,7 +1408,7 @@ internal override void End ()
         }
     }
 
-    internal override MainLoop Init ()
+    public override MainLoop Init ()
     {
         _mainLoopDriver = new WindowsMainLoop (this);
 
@@ -1881,7 +1881,7 @@ private async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag)
         int delay = startDelay;
         while (_isButtonPressed)
         {
-            // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
+            // TODO: This makes IConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
             View view = Application.WantContinuousButtonPressedView;
 
             if (view is null)
@@ -1904,7 +1904,7 @@ private async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag)
             //Debug.WriteLine($"ProcessContinuousButtonPressedAsync: {view}");
             if (_isButtonPressed && (mouseFlag & MouseFlags.ReportMousePosition) == 0)
             {
-                // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
+                // TODO: This makes IConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
                 Application.Invoke (() => OnMouseEvent (me));
             }
         }
@@ -1959,7 +1959,7 @@ private MouseEventArgs ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent
 
         if (_isButtonDoubleClicked || _isOneFingerDoubleClicked)
         {
-            // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
+            // TODO: This makes IConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
             Application.MainLoop.AddIdle (
                                           () =>
                                           {
@@ -2031,7 +2031,7 @@ private MouseEventArgs ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent
 
             if ((mouseFlag & MouseFlags.ReportMousePosition) == 0)
             {
-                // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
+                // TODO: This makes IConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
                 Application.MainLoop.AddIdle (
                                               () =>
                                               {
@@ -2217,7 +2217,7 @@ internal class WindowsMainLoop : IMainLoopDriver
     /// </summary>
     public EventHandler<SizeChangedEventArgs> WinChanged;
 
-    private readonly ConsoleDriver _consoleDriver;
+    private readonly IConsoleDriver _consoleDriver;
     private readonly ManualResetEventSlim _eventReady = new (false);
 
     // The records that we keep fetching
@@ -2228,7 +2228,7 @@ internal class WindowsMainLoop : IMainLoopDriver
     private readonly CancellationTokenSource _inputHandlerTokenSource = new ();
     private MainLoop _mainLoop;
 
-    public WindowsMainLoop (ConsoleDriver consoleDriver = null)
+    public WindowsMainLoop (IConsoleDriver consoleDriver = null)
     {
         _consoleDriver = consoleDriver ?? throw new ArgumentNullException (nameof (consoleDriver));
         _winConsole = ((WindowsDriver)consoleDriver).WinConsole;
diff --git a/Terminal.Gui/Drawing/Attribute.cs b/Terminal.Gui/Drawing/Attribute.cs
index 32c948d2e4..a36601ba1e 100644
--- a/Terminal.Gui/Drawing/Attribute.cs
+++ b/Terminal.Gui/Drawing/Attribute.cs
@@ -17,7 +17,7 @@ namespace Terminal.Gui;
     [JsonIgnore]
     public static Attribute Default => new (Color.White, Color.Black);
 
-    /// <summary>The <see cref="ConsoleDriver"/>-specific color value.</summary>
+    /// <summary>The <see cref="IConsoleDriver"/>-specific color value.</summary>
     [JsonIgnore (Condition = JsonIgnoreCondition.Always)]
     internal int PlatformColor { get; init; }
 
diff --git a/Terminal.Gui/Drawing/Cell.cs b/Terminal.Gui/Drawing/Cell.cs
index 16af046a7f..5ce4e21df5 100644
--- a/Terminal.Gui/Drawing/Cell.cs
+++ b/Terminal.Gui/Drawing/Cell.cs
@@ -2,7 +2,7 @@
 
 /// <summary>
 ///     Represents a single row/column in a Terminal.Gui rendering surface (e.g. <see cref="LineCanvas"/> and
-///     <see cref="ConsoleDriver"/>).
+///     <see cref="IConsoleDriver"/>).
 /// </summary>
 public record struct Cell (Attribute? Attribute = null, bool IsDirty = false, Rune Rune = default)
 {
diff --git a/Terminal.Gui/Drawing/LineCanvas.cs b/Terminal.Gui/Drawing/LineCanvas.cs
index 8261b156a6..4a69a96b72 100644
--- a/Terminal.Gui/Drawing/LineCanvas.cs
+++ b/Terminal.Gui/Drawing/LineCanvas.cs
@@ -384,7 +384,7 @@ private void ConfigurationManager_Applied (object? sender, ConfigurationManagerE
         // TODO: Add other resolvers
     };
 
-    private Cell? GetCellForIntersects (ConsoleDriver? driver, IntersectionDefinition? [] intersects)
+    private Cell? GetCellForIntersects (IConsoleDriver? driver, IntersectionDefinition? [] intersects)
     {
         if (!intersects.Any ())
         {
@@ -404,7 +404,7 @@ private void ConfigurationManager_Applied (object? sender, ConfigurationManagerE
         return cell;
     }
 
-    private Rune? GetRuneForIntersects (ConsoleDriver? driver, IntersectionDefinition? [] intersects)
+    private Rune? GetRuneForIntersects (IConsoleDriver? driver, IntersectionDefinition? [] intersects)
     {
         if (!intersects.Any ())
         {
@@ -727,7 +727,7 @@ private abstract class IntersectionRuneResolver
         internal Rune _thickV;
         protected IntersectionRuneResolver () { SetGlyphs (); }
 
-        public Rune? GetRuneForIntersects (ConsoleDriver? driver, IntersectionDefinition? [] intersects)
+        public Rune? GetRuneForIntersects (IConsoleDriver? driver, IntersectionDefinition? [] intersects)
         {
             bool useRounded = intersects.Any (
                                               i => i?.Line.Length != 0
diff --git a/Terminal.Gui/Drawing/SixelToRender.cs b/Terminal.Gui/Drawing/SixelToRender.cs
index dedd399ef9..f9981f19f7 100644
--- a/Terminal.Gui/Drawing/SixelToRender.cs
+++ b/Terminal.Gui/Drawing/SixelToRender.cs
@@ -2,7 +2,7 @@
 
 /// <summary>
 ///     Describes a request to render a given <see cref="SixelData"/> at a given <see cref="ScreenPosition"/>.
-///     Requires that the terminal and <see cref="ConsoleDriver"/> both support sixel.
+///     Requires that the terminal and <see cref="IConsoleDriver"/> both support sixel.
 /// </summary>
 public class SixelToRender
 {
diff --git a/Terminal.Gui/README.md b/Terminal.Gui/README.md
index 5c22df0aca..a9ec754602 100644
--- a/Terminal.Gui/README.md
+++ b/Terminal.Gui/README.md
@@ -9,8 +9,8 @@ All files required to build the **Terminal.Gui** library (and NuGet package).
 	- `Application\` - The core `Application` logic, including `Application.cs`, which is is a `static` class that provides the base 'application engine', `RunState`, and `MainLoop`.
 
 - `ConsoleDrivers\`
-	- `ConsoleDriver.cs` - Definition for the Console Driver API.
-	- Source files for the three `ConsoleDriver`-based drivers: .NET: `NetDriver`, Unix & Mac: `UnixDriver`, and Windows: `WindowsDriver`.
+	- `IConsoleDriver.cs` - Definition for the Console Driver API.
+	- Source files for the three `IConsoleDriver`-based drivers: .NET: `NetDriver`, Unix & Mac: `UnixDriver`, and Windows: `WindowsDriver`.
 
 - `Configuration\` - Classes related the `ConfigurationManager`.
 
diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs
index 2938b69bc0..90152452fd 100644
--- a/Terminal.Gui/Text/TextFormatter.cs
+++ b/Terminal.Gui/Text/TextFormatter.cs
@@ -43,7 +43,7 @@ public TextDirection Direction
         set => _textDirection = EnableNeedsFormat (value);
     }
 
-    /// <summary>Draws the text held by <see cref="TextFormatter"/> to <see cref="ConsoleDriver"/> using the colors specified.</summary>
+    /// <summary>Draws the text held by <see cref="TextFormatter"/> to <see cref="IConsoleDriver"/> using the colors specified.</summary>
     /// <remarks>
     ///     Causes the text to be formatted (references <see cref="GetLines"/>). Sets <see cref="NeedsFormat"/> to
     ///     <c>false</c>.
@@ -59,7 +59,7 @@ public void Draw (
         Attribute normalColor,
         Attribute hotColor,
         Rectangle maximum = default,
-        ConsoleDriver? driver = null
+        IConsoleDriver? driver = null
     )
     {
         // With this check, we protect against subclasses with overrides of Text (like Button)
diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs
index 27fcb065af..433822b4bf 100644
--- a/Terminal.Gui/View/View.cs
+++ b/Terminal.Gui/View/View.cs
@@ -126,7 +126,7 @@ public partial class View : IDisposable, ISupportInitializeNotification
     ///     Points to the current driver in use by the view, it is a convenience property for simplifying the development
     ///     of new views.
     /// </summary>
-    public static ConsoleDriver? Driver => Application.Driver;
+    public static IConsoleDriver? Driver => Application.Driver;
 
     /// <summary>Initializes a new instance of <see cref="View"/>.</summary>
     /// <remarks>
diff --git a/Terminal.Gui/Views/ColorPicker.Prompt.cs b/Terminal.Gui/Views/ColorPicker.Prompt.cs
index 3f2372db9a..0b79d3e9ff 100644
--- a/Terminal.Gui/Views/ColorPicker.Prompt.cs
+++ b/Terminal.Gui/Views/ColorPicker.Prompt.cs
@@ -4,7 +4,7 @@ public partial class ColorPicker
 {
     /// <summary>
     ///     Open a <see cref="Dialog"/> with two <see cref="ColorPicker"/> or <see cref="ColorPicker16"/>, based on the
-    ///     <see cref="ConsoleDriver.Force16Colors"/> is false or true, respectively, for <see cref="Attribute.Foreground"/>
+    ///     <see cref="IConsoleDriver.Force16Colors"/> is false or true, respectively, for <see cref="Attribute.Foreground"/>
     ///     and <see cref="Attribute.Background"/> colors.
     /// </summary>
     /// <param name="title">The title to show in the dialog.</param>
diff --git a/Terminal.Gui/Views/GraphView/Axis.cs b/Terminal.Gui/Views/GraphView/Axis.cs
index c469388904..01d78ee03c 100644
--- a/Terminal.Gui/Views/GraphView/Axis.cs
+++ b/Terminal.Gui/Views/GraphView/Axis.cs
@@ -97,7 +97,7 @@ public HorizontalAxis () : base (Orientation.Horizontal) { }
     /// <param name="text">Text to render under the axis tick</param>
     public override void DrawAxisLabel (GraphView graph, int screenPosition, string text)
     {
-        ConsoleDriver driver = Application.Driver;
+        IConsoleDriver driver = Application.Driver;
         int y = GetAxisYPosition (graph);
 
         graph.Move (screenPosition, y);
diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs
index 7bba8b7143..6da4f21254 100644
--- a/Terminal.Gui/Views/Menu/MenuBar.cs
+++ b/Terminal.Gui/Views/Menu/MenuBar.cs
@@ -667,7 +667,7 @@ internal bool CloseMenu (bool reopen, bool isSubMenu, bool ignoreUseSubMenusSing
         return true;
     }
 
-    /// <summary>Gets the superview location offset relative to the <see cref="ConsoleDriver"/> location.</summary>
+    /// <summary>Gets the superview location offset relative to the <see cref="IConsoleDriver"/> location.</summary>
     /// <returns>The location offset.</returns>
     internal Point GetScreenOffset ()
     {
diff --git a/Terminal.Gui/Views/TableView/TableStyle.cs b/Terminal.Gui/Views/TableView/TableStyle.cs
index 4dd9477342..ffc8068805 100644
--- a/Terminal.Gui/Views/TableView/TableStyle.cs
+++ b/Terminal.Gui/Views/TableView/TableStyle.cs
@@ -29,7 +29,7 @@ public class TableStyle
 
     /// <summary>
     ///     True to invert the colors of the first symbol of the selected cell in the <see cref="TableView"/>. This gives
-    ///     the appearance of a cursor for when the <see cref="ConsoleDriver"/> doesn't otherwise show this
+    ///     the appearance of a cursor for when the <see cref="IConsoleDriver"/> doesn't otherwise show this
     /// </summary>
     public bool InvertSelectedCellFirstCharacter { get; set; } = false;
 
diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs
index 468b851f65..68c5a037e0 100644
--- a/Terminal.Gui/Views/TableView/TableView.cs
+++ b/Terminal.Gui/Views/TableView/TableView.cs
@@ -1277,7 +1277,7 @@ protected virtual bool OnCellActivated (CellActivatedEventArgs args)
 
     /// <summary>
     ///     Override to provide custom multi colouring to cells.  Use <see cref="View.Driver"/> to with
-    ///     <see cref="ConsoleDriver.AddStr(string)"/>.  The driver will already be in the correct place when rendering and you
+    ///     <see cref="IConsoleDriver.AddStr(string)"/>.  The driver will already be in the correct place when rendering and you
     ///     must render the full <paramref name="render"/> or the view will not look right.  For simpler provision of color use
     ///     <see cref="ColumnStyle.ColorGetter"/> For changing the content that is rendered use
     ///     <see cref="ColumnStyle.RepresentationGetter"/>
@@ -1335,7 +1335,7 @@ internal int GetHeaderHeight ()
     /// <returns></returns>
     internal int GetHeaderHeightIfAny () { return ShouldRenderHeaders () ? GetHeaderHeight () : 0; }
 
-    private void AddRuneAt (ConsoleDriver d, int col, int row, Rune ch)
+    private void AddRuneAt (IConsoleDriver d, int col, int row, Rune ch)
     {
         Move (col, row);
         d?.AddRune (ch);
diff --git a/Terminal.Gui/Views/TreeView/Branch.cs b/Terminal.Gui/Views/TreeView/Branch.cs
index e2d220aceb..e7a5eb4ca4 100644
--- a/Terminal.Gui/Views/TreeView/Branch.cs
+++ b/Terminal.Gui/Views/TreeView/Branch.cs
@@ -73,7 +73,7 @@ public bool CanExpand ()
     /// <param name="colorScheme"></param>
     /// <param name="y"></param>
     /// <param name="availableWidth"></param>
-    public virtual void Draw (ConsoleDriver driver, ColorScheme colorScheme, int y, int availableWidth)
+    public virtual void Draw (IConsoleDriver driver, ColorScheme colorScheme, int y, int availableWidth)
     {
         List<Cell> cells = new ();
         int? indexOfExpandCollapseSymbol = null;
@@ -291,7 +291,7 @@ public virtual void FetchChildren ()
     /// </summary>
     /// <param name="driver"></param>
     /// <returns></returns>
-    public Rune GetExpandableSymbol (ConsoleDriver driver)
+    public Rune GetExpandableSymbol (IConsoleDriver driver)
     {
         Rune leafSymbol = tree.Style.ShowBranchLines ? Glyphs.HLine : (Rune)' ';
 
@@ -313,7 +313,7 @@ public Rune GetExpandableSymbol (ConsoleDriver driver)
     ///     line body).
     /// </summary>
     /// <returns></returns>
-    public virtual int GetWidth (ConsoleDriver driver)
+    public virtual int GetWidth (IConsoleDriver driver)
     {
         return
             GetLinePrefix (driver).Sum (r => r.GetColumns ()) + GetExpandableSymbol (driver).GetColumns () + (tree.AspectGetter (Model) ?? "").Length;
@@ -408,7 +408,7 @@ internal void ExpandAll ()
     /// </summary>
     /// <param name="driver"></param>
     /// <returns></returns>
-    internal IEnumerable<Rune> GetLinePrefix (ConsoleDriver driver)
+    internal IEnumerable<Rune> GetLinePrefix (IConsoleDriver driver)
     {
         // If not showing line branches or this is a root object.
         if (!tree.Style.ShowBranchLines)
@@ -453,7 +453,7 @@ internal IEnumerable<Rune> GetLinePrefix (ConsoleDriver driver)
     /// <param name="driver"></param>
     /// <param name="x"></param>
     /// <returns></returns>
-    internal bool IsHitOnExpandableSymbol (ConsoleDriver driver, int x)
+    internal bool IsHitOnExpandableSymbol (IConsoleDriver driver, int x)
     {
         // if leaf node then we cannot expand
         if (!CanExpand ())
diff --git a/UICatalog/Scenarios/GraphViewExample.cs b/UICatalog/Scenarios/GraphViewExample.cs
index 056086c645..f66463664a 100644
--- a/UICatalog/Scenarios/GraphViewExample.cs
+++ b/UICatalog/Scenarios/GraphViewExample.cs
@@ -1000,7 +1000,7 @@ public DiscoBarSeries ()
 
         protected override void DrawBarLine (GraphView graph, Point start, Point end, BarSeriesBar beingDrawn)
         {
-            ConsoleDriver driver = Application.Driver;
+            IConsoleDriver driver = Application.Driver;
 
             int x = start.X;
 
diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs
index ba7e3f26d7..fffa989f24 100644
--- a/UICatalog/UICatalog.cs
+++ b/UICatalog/UICatalog.cs
@@ -136,7 +136,7 @@ private static int Main (string [] args)
         // Process command line args
         // "UICatalog [--driver <driver>] [--benchmark] [scenario name]"
         // If no driver is provided, the default driver is used.
-        Option<string> driverOption = new Option<string> ("--driver", "The ConsoleDriver to use.").FromAmong (
+        Option<string> driverOption = new Option<string> ("--driver", "The IConsoleDriver to use.").FromAmong (
              Application.GetDriverTypes ()
                         .Select (d => d!.Name)
                         .ToArray ()
diff --git a/UnitTests/Application/ApplicationTests.cs b/UnitTests/Application/ApplicationTests.cs
index 38400d8a2a..aac767459b 100644
--- a/UnitTests/Application/ApplicationTests.cs
+++ b/UnitTests/Application/ApplicationTests.cs
@@ -248,7 +248,7 @@ public void Init_Begin_End_Cleans_Up ()
     [InlineData (typeof (CursesDriver))]
     public void Init_DriverName_Should_Pick_Correct_Driver (Type driverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         Application.Init (driverName: driverType.Name);
         Assert.NotNull (Application.Driver);
         Assert.NotEqual (driver, Application.Driver);
diff --git a/UnitTests/ConsoleDrivers/AddRuneTests.cs b/UnitTests/ConsoleDrivers/AddRuneTests.cs
index 6769db8300..5c753386ee 100644
--- a/UnitTests/ConsoleDrivers/AddRuneTests.cs
+++ b/UnitTests/ConsoleDrivers/AddRuneTests.cs
@@ -25,7 +25,7 @@ public AddRuneTests (ITestOutputHelper output)
     [InlineData (typeof (CursesDriver))]
     public void AddRune (Type driverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         driver.Init ();
 
         driver.Rows = 25;
diff --git a/UnitTests/ConsoleDrivers/ClipRegionTests.cs b/UnitTests/ConsoleDrivers/ClipRegionTests.cs
index c85f7fec80..9e01f83ee6 100644
--- a/UnitTests/ConsoleDrivers/ClipRegionTests.cs
+++ b/UnitTests/ConsoleDrivers/ClipRegionTests.cs
@@ -24,7 +24,7 @@ public ClipRegionTests (ITestOutputHelper output)
     [InlineData (typeof (CursesDriver))]
     public void AddRune_Is_Clipped (Type driverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         Application.Init (driver);
         Application.Driver!.Rows = 25;
         Application.Driver!.Cols = 80;
@@ -62,7 +62,7 @@ public void AddRune_Is_Clipped (Type driverType)
     [InlineData (typeof (CursesDriver))]
     public void Clip_Set_To_Empty_AllInvalid (Type driverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         Application.Init (driver);
 
         // Define a clip rectangle
@@ -92,7 +92,7 @@ public void Clip_Set_To_Empty_AllInvalid (Type driverType)
     [InlineData (typeof (CursesDriver))]
     public void IsValidLocation (Type driverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         Application.Init (driver);
         Application.Driver!.Rows = 10;
         Application.Driver!.Cols = 10;
diff --git a/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs b/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs
index 8ecc978076..d75a56639d 100644
--- a/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs
+++ b/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs
@@ -25,7 +25,7 @@ public ConsoleDriverTests (ITestOutputHelper output)
     [InlineData (typeof (CursesDriver))]
     public void End_Cleans_Up (Type driverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         driver.Init ();
         driver.End ();
     }
@@ -34,7 +34,7 @@ public void End_Cleans_Up (Type driverType)
     [InlineData (typeof (FakeDriver))]
     public void FakeDriver_MockKeyPresses (Type driverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         Application.Init (driver);
 
         var text = "MockKeyPresses";
@@ -85,7 +85,7 @@ public void FakeDriver_MockKeyPresses (Type driverType)
     [InlineData (typeof (FakeDriver))]
     public void FakeDriver_Only_Sends_Keystrokes_Through_MockKeyPresses (Type driverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         Application.Init (driver);
 
         Toplevel top = new ();
@@ -124,7 +124,7 @@ public void FakeDriver_Only_Sends_Keystrokes_Through_MockKeyPresses (Type driver
     [InlineData (typeof (CursesDriver))]
     public void Init_Inits (Type driverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         MainLoop ml = driver.Init ();
         Assert.NotNull (ml);
         Assert.NotNull (driver.Clipboard);
@@ -140,7 +140,7 @@ public void Init_Inits (Type driverType)
     //[InlineData (typeof (FakeDriver))]
     //public void FakeDriver_MockKeyPresses_Press_AfterTimeOut (Type driverType)
     //{
-    //	var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+    //	var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
     //	Application.Init (driver);
 
     //	// Simulating pressing of QuitKey after a short period of time
@@ -201,7 +201,7 @@ public void Init_Inits (Type driverType)
     [InlineData (typeof (CursesDriver))]
     public void TerminalResized_Simulation (Type driverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         driver?.Init ();
         driver.Cols = 80;
         driver.Rows = 25;
diff --git a/UnitTests/ConsoleDrivers/ContentsTests.cs b/UnitTests/ConsoleDrivers/ContentsTests.cs
index 0dd4be3bfc..d0f2d83ff7 100644
--- a/UnitTests/ConsoleDrivers/ContentsTests.cs
+++ b/UnitTests/ConsoleDrivers/ContentsTests.cs
@@ -24,7 +24,7 @@ public ContentsTests (ITestOutputHelper output)
     //[InlineData (typeof (WindowsDriver))] // TODO: Uncomment when #2610 is fixed
     public void AddStr_Combining_Character_1st_Column (Type driverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         driver.Init ();
         var expected = "\u0301!";
         driver.AddStr ("\u0301!"); // acute accent + exclamation mark
@@ -42,7 +42,7 @@ public void AddStr_Combining_Character_1st_Column (Type driverType)
     //[InlineData (typeof (WindowsDriver))] // TODO: Uncomment when #2610 is fixed
     public void AddStr_With_Combining_Characters (Type driverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         driver.Init ();
 
         var acuteaccent = new Rune (0x0301); // Combining acute accent (é)
@@ -98,7 +98,7 @@ public void AddStr_With_Combining_Characters (Type driverType)
     [InlineData (typeof (CursesDriver))]
     public void Move_Bad_Coordinates (Type driverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         driver.Init ();
 
         Assert.Equal (0, driver.Col);
diff --git a/UnitTests/ConsoleDrivers/DriverColorTests.cs b/UnitTests/ConsoleDrivers/DriverColorTests.cs
index 8bebace1e8..856e0481a0 100644
--- a/UnitTests/ConsoleDrivers/DriverColorTests.cs
+++ b/UnitTests/ConsoleDrivers/DriverColorTests.cs
@@ -17,7 +17,7 @@ public class DriverColorTests
     [InlineData (typeof (CursesDriver))]
     public void Force16Colors_Sets (Type driverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         driver.Init ();
 
         driver.Force16Colors = true;
@@ -35,7 +35,7 @@ public void Force16Colors_Sets (Type driverType)
     [InlineData (typeof (CursesDriver))]
     public void SetColors_Changes_Colors (Type driverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         driver.Init ();
 
         Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor);
@@ -63,7 +63,7 @@ public void SetColors_Changes_Colors (Type driverType)
     [InlineData (typeof (CursesDriver), true)]
     public void SupportsTrueColor_Defaults (Type driverType, bool expectedSetting)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         driver.Init ();
 
         Assert.Equal (expectedSetting, driver.SupportsTrueColor);
diff --git a/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs b/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs
index 2380ed83e2..9720d51b5e 100644
--- a/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs
+++ b/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs
@@ -17,7 +17,7 @@ public class MainLoopDriverTests
     //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
     public void MainLoop_AddIdle_ValidIdleHandler_ReturnsToken (Type driverType, Type mainLoopDriverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
         var mainLoop = new MainLoop (mainLoopDriver);
         var idleHandlerInvoked = false;
@@ -47,7 +47,7 @@ bool IdleHandler ()
     //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
     public void MainLoop_AddTimeout_ValidParameters_ReturnsToken (Type driverType, Type mainLoopDriverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
         var mainLoop = new MainLoop (mainLoopDriver);
         var callbackInvoked = false;
@@ -83,7 +83,7 @@ public void MainLoop_CheckTimersAndIdleHandlers_IdleHandlersActive_ReturnsTrue (
         Type mainLoopDriverType
     )
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
         var mainLoop = new MainLoop (mainLoopDriver);
 
@@ -107,7 +107,7 @@ public void MainLoop_CheckTimersAndIdleHandlers_NoTimersOrIdleHandlers_ReturnsFa
         Type mainLoopDriverType
     )
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
         var mainLoop = new MainLoop (mainLoopDriver);
 
@@ -130,7 +130,7 @@ public void MainLoop_CheckTimersAndIdleHandlers_TimersActive_ReturnsTrue (
         Type mainLoopDriverType
     )
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
         var mainLoop = new MainLoop (mainLoopDriver);
 
@@ -151,7 +151,7 @@ Type mainLoopDriverType
     //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
     public void MainLoop_Constructs_Disposes (Type driverType, Type mainLoopDriverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
         var mainLoop = new MainLoop (mainLoopDriver);
 
@@ -182,7 +182,7 @@ public void MainLoop_Constructs_Disposes (Type driverType, Type mainLoopDriverTy
     //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
     public void MainLoop_RemoveIdle_InvalidToken_ReturnsFalse (Type driverType, Type mainLoopDriverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
         var mainLoop = new MainLoop (mainLoopDriver);
 
@@ -201,7 +201,7 @@ public void MainLoop_RemoveIdle_InvalidToken_ReturnsFalse (Type driverType, Type
     //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
     public void MainLoop_RemoveIdle_ValidToken_ReturnsTrue (Type driverType, Type mainLoopDriverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
         var mainLoop = new MainLoop (mainLoopDriver);
 
@@ -223,7 +223,7 @@ public void MainLoop_RemoveIdle_ValidToken_ReturnsTrue (Type driverType, Type ma
     //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
     public void MainLoop_RemoveTimeout_InvalidToken_ReturnsFalse (Type driverType, Type mainLoopDriverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
         var mainLoop = new MainLoop (mainLoopDriver);
 
@@ -241,7 +241,7 @@ public void MainLoop_RemoveTimeout_InvalidToken_ReturnsFalse (Type driverType, T
     //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
     public void MainLoop_RemoveTimeout_ValidToken_ReturnsTrue (Type driverType, Type mainLoopDriverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
         var mainLoop = new MainLoop (mainLoopDriver);
 
@@ -261,7 +261,7 @@ public void MainLoop_RemoveTimeout_ValidToken_ReturnsTrue (Type driverType, Type
     //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
     public void MainLoop_RunIteration_ValidIdleHandler_CallsIdleHandler (Type driverType, Type mainLoopDriverType)
     {
-        var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
         var mainLoop = new MainLoop (mainLoopDriver);
         var idleHandlerInvoked = false;
@@ -287,7 +287,7 @@ public void MainLoop_RunIteration_ValidIdleHandler_CallsIdleHandler (Type driver
     //[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
     //public void MainLoop_Invoke_ValidAction_RunsAction (Type driverType, Type mainLoopDriverType)
     //{
-    //	var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+    //	var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
     //	var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver });
     //	var mainLoop = new MainLoop (mainLoopDriver);
     //	var actionInvoked = false;
diff --git a/UnitTests/TestHelpers.cs b/UnitTests/TestHelpers.cs
index 85bbc7d1d5..4655c3a7e9 100644
--- a/UnitTests/TestHelpers.cs
+++ b/UnitTests/TestHelpers.cs
@@ -25,21 +25,21 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute
     /// </summary>
     /// <param name="autoInit">If true, Application.Init will be called Before the test runs.</param>
     /// <param name="consoleDriverType">
-    ///     Determines which ConsoleDriver (FakeDriver, WindowsDriver, CursesDriver, NetDriver)
+    ///     Determines which IConsoleDriver (FakeDriver, WindowsDriver, CursesDriver, NetDriver)
     ///     will be used when Application.Init is called. If null FakeDriver will be used. Only valid if
     ///     <paramref name="autoInit"/> is true.
     /// </param>
     /// <param name="useFakeClipboard">
     ///     If true, will force the use of <see cref="FakeDriver.FakeClipboard"/>. Only valid if
-    ///     <see cref="ConsoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.
+    ///     <see cref="IConsoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.
     /// </param>
     /// <param name="fakeClipboardAlwaysThrowsNotSupportedException">
     ///     Only valid if <paramref name="autoInit"/> is true. Only
-    ///     valid if <see cref="ConsoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.
+    ///     valid if <see cref="IConsoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.
     /// </param>
     /// <param name="fakeClipboardIsSupportedAlwaysTrue">
     ///     Only valid if <paramref name="autoInit"/> is true. Only valid if
-    ///     <see cref="ConsoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.
+    ///     <see cref="IConsoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.
     /// </param>
     /// <param name="configLocation">Determines what config file locations <see cref="ConfigurationManager"/> will load from.</param>
     /// <param name="verifyShutdown">If true and <see cref="Application.Initialized"/> is true, the test will fail.</param>
@@ -135,7 +135,7 @@ public override void Before (MethodInfo methodUnderTest)
                 View.Instances.Clear ();
             }
 #endif
-            Application.Init ((ConsoleDriver)Activator.CreateInstance (_driverType));
+            Application.Init ((IConsoleDriver)Activator.CreateInstance (_driverType));
         }
     }
 
@@ -249,12 +249,12 @@ internal partial class TestHelpers
     ///     <paramref name="expectedAttributes"/>.
     /// </param>
     /// <param name="output"></param>
-    /// <param name="driver">The ConsoleDriver to use. If null <see cref="Application.Driver"/> will be used.</param>
+    /// <param name="driver">The IConsoleDriver to use. If null <see cref="Application.Driver"/> will be used.</param>
     /// <param name="expectedAttributes"></param>
     public static void AssertDriverAttributesAre (
         string expectedLook,
         ITestOutputHelper output,
-        ConsoleDriver driver = null,
+        IConsoleDriver driver = null,
         params Attribute [] expectedAttributes
     )
     {
@@ -319,12 +319,12 @@ params Attribute [] expectedAttributes
     /// <summary>Asserts that the driver contents match the expected contents, optionally ignoring any trailing whitespace.</summary>
     /// <param name="expectedLook"></param>
     /// <param name="output"></param>
-    /// <param name="driver">The ConsoleDriver to use. If null <see cref="Application.Driver"/> will be used.</param>
+    /// <param name="driver">The IConsoleDriver to use. If null <see cref="Application.Driver"/> will be used.</param>
     /// <param name="ignoreLeadingWhitespace"></param>
     public static void AssertDriverContentsAre (
         string expectedLook,
         ITestOutputHelper output,
-        ConsoleDriver driver = null,
+        IConsoleDriver driver = null,
         bool ignoreLeadingWhitespace = false
     )
     {
@@ -365,12 +365,12 @@ public static void AssertDriverContentsAre (
     /// </summary>
     /// <param name="expectedLook"></param>
     /// <param name="output"></param>
-    /// <param name="driver">The ConsoleDriver to use. If null <see cref="Application.Driver"/> will be used.</param>
+    /// <param name="driver">The IConsoleDriver to use. If null <see cref="Application.Driver"/> will be used.</param>
     /// <returns></returns>
     public static Rectangle AssertDriverContentsWithFrameAre (
         string expectedLook,
         ITestOutputHelper output,
-        ConsoleDriver driver = null
+        IConsoleDriver driver = null
     )
     {
         List<List<Rune>> lines = new ();
@@ -623,7 +623,7 @@ public static List<Type> GetAllViewClasses ()
     /// </summary>
     /// <param name="driver">if null uses <see cref="Application.Driver"/></param>
     /// <param name="expectedColors"></param>
-    internal static void AssertDriverUsedColors (ConsoleDriver driver = null, params Attribute [] expectedColors)
+    internal static void AssertDriverUsedColors (IConsoleDriver driver = null, params Attribute [] expectedColors)
     {
         driver ??= Application.Driver;
         Cell [,] contents = driver.Contents;

From ee335bd5d14f23c112f54e218f946c1923898927 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 24 Nov 2024 19:45:16 +0000
Subject: [PATCH 021/198] WIP: trying to implement IConsoleDriver facade

---
 Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs  |  19 --
 Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs |  41 ---
 .../ConsoleDrivers/V2/ConsoleDriverFacade.cs  | 302 ++++++++++++++++++
 .../ConsoleDrivers/V2/IOutputBuffer.cs        |  14 +
 .../ConsoleDrivers/V2/OutputBuffer.cs         |  31 +-
 Terminal.Gui/ConsoleDrivers/V2/V2.cd          |   4 +-
 6 files changed, 346 insertions(+), 65 deletions(-)
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs

diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
index 6a52b4b74f..0d0588720c 100644
--- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
@@ -364,25 +364,6 @@ public void ClearContents ()
     /// </summary>
     public event EventHandler<EventArgs>? ClearedContents;
 
-    /// <summary>
-    /// 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 ()
-    {
-        lock (Contents!)
-        {
-            for (var row = 0; row < Rows; row++)
-            {
-                for (var c = 0; c < Cols; c++)
-                {
-                    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 ();
diff --git a/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
index f3d742ab3b..f64a3a5186 100644
--- a/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
@@ -116,16 +116,6 @@ public interface IConsoleDriver
     /// </summary>
     event EventHandler<EventArgs> ClearedContents;
 
-    /// <summary>
-    /// Sets <see cref="ConsoleDriver.Contents"/> as dirty for situations where views
-    /// don't need layout and redrawing, but just refresh the screen.
-    /// </summary>
-    void SetContentsAsDirty ();
-
-    /// <summary>Determines if the terminal cursor should be visible or not and sets it accordingly.</summary>
-    /// <returns><see langword="true"/> upon success</returns>
-    bool EnsureCursorVisibility ();
-
     /// <summary>Fills the specified rectangle with the specified rune, using <see cref="ConsoleDriver.CurrentAttribute"/></summary>
     /// <remarks>
     /// The value of <see cref="ConsoleDriver.Clip"/> is honored. Any parts of the rectangle not in the clip will not be drawn.
@@ -184,18 +174,6 @@ public interface IConsoleDriver
     /// <param name="row">Row to move to.</param>
     void Move (int col, int row);
 
-    /// <summary>Called when the terminal size changes. Fires the <see cref="ConsoleDriver.SizeChanged"/> event.</summary>
-    /// <param name="args"></param>
-    void OnSizeChanged (SizeChangedEventArgs args);
-
-    /// <summary>Updates the screen to reflect all the changes that have been done to the display buffer</summary>
-    void Refresh ();
-
-    /// <summary>
-    ///     Raised each time <see cref="ConsoleDriver.Refresh"/> is called. For benchmarking.
-    /// </summary>
-    event EventHandler<EventArgs<bool>> Refreshed;
-
     /// <summary>Sets the terminal cursor visibility.</summary>
     /// <param name="visibility">The wished <see cref="CursorVisibility"/></param>
     /// <returns><see langword="true"/> upon success</returns>
@@ -240,12 +218,6 @@ public interface IConsoleDriver
     /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="ConsoleDriver.KeyUp"/>.</summary>
     event EventHandler<Key> KeyDown;
 
-    /// <summary>
-    ///     Called when a key is pressed down. Fires the <see cref="ConsoleDriver.KeyDown"/> event. This is a precursor to
-    ///     <see cref="ConsoleDriver.OnKeyUp"/>.
-    /// </summary>
-    /// <param name="a"></param>
-    void OnKeyDown (Key a);
 
     /// <summary>Event fired when a key is released.</summary>
     /// <remarks>
@@ -254,21 +226,9 @@ public interface IConsoleDriver
     /// </remarks>
     event EventHandler<Key> KeyUp;
 
-    /// <summary>Called when a key is released. Fires the <see cref="ConsoleDriver.KeyUp"/> event.</summary>
-    /// <remarks>
-    ///     Drivers that do not support key release events will call this method after <see cref="ConsoleDriver.OnKeyDown"/> processing
-    ///     is complete.
-    /// </remarks>
-    /// <param name="a"></param>
-    void OnKeyUp (Key a);
-
     /// <summary>Event fired when a mouse event occurs.</summary>
     event EventHandler<MouseEventArgs> MouseEvent;
 
-    /// <summary>Called when a mouse event occurs. Fires the <see cref="ConsoleDriver.MouseEvent"/> event.</summary>
-    /// <param name="a"></param>
-    void OnMouseEvent (MouseEventArgs a);
-
     /// <summary>Simulates a key press.</summary>
     /// <param name="keyChar">The key character.</param>
     /// <param name="key">The key.</param>
@@ -283,7 +243,6 @@ public interface IConsoleDriver
     /// <param name="request"></param>
     void QueueAnsiRequest (AnsiEscapeSequenceRequest request);
 
-    IAnsiResponseParser GetParser ();
     AnsiRequestScheduler GetRequestScheduler ();
 
     /// <summary>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
new file mode 100644
index 0000000000..974cec31c7
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
@@ -0,0 +1,302 @@
+namespace Terminal.Gui.ConsoleDrivers.V2;
+class ConsoleDriverFacade<T> : IConsoleDriver
+{
+    
+    private readonly IConsoleOutput _output;
+    private readonly IConsoleInput<T> _input;
+    private readonly InputProcessor<T> _inputProcessor;
+    private readonly IOutputBuffer _outputBuffer;
+
+    public ConsoleDriverFacade (IConsoleInput<T> input,InputProcessor<T> inputProcessor,IOutputBuffer outputBuffer, IConsoleOutput output )
+    {
+        _output = output;
+        _input = input;
+        _inputProcessor = inputProcessor;
+        _outputBuffer = outputBuffer;
+    }
+    /// <summary>
+    /// How long after Esc has been pressed before we give up on getting an Ansi escape sequence
+    /// </summary>
+    public TimeSpan EscTimeout { get; } = TimeSpan.FromMilliseconds (50);
+
+    /// <summary>Gets the location and size of the terminal screen.</summary>
+    public Rectangle Screen => new (new (0,0),_output.GetWindowSize ());
+
+    /// <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 Region Clip {
+        get => _outputBuffer.Clip;
+        set => _outputBuffer.Clip = value;
+    }
+
+    // TODO: Clipboard support
+    /// <summary>Get the operating system clipboard.</summary>
+    public IClipboard Clipboard { get; } = new FakeDriver.FakeClipboard ();
+
+    /// <summary>
+    ///     Gets the column 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.
+    /// </summary>
+    public int Col => _outputBuffer.Col;
+
+    /// <summary>The number of columns visible in the terminal.</summary>
+    public int Cols
+    {
+        get => _outputBuffer.Cols;
+        set => _outputBuffer.Cols = value;
+    }
+
+    /// <summary>
+    ///     The contents of the application output. The driver outputs this buffer to the terminal when
+    ///     <see cref="UpdateScreen"/> is called.
+    ///     <remarks>The format of the array is rows, columns. The first index is the row, the second index is the column.</remarks>
+    /// </summary>
+    public Cell [,] Contents
+    {
+        get => _outputBuffer.Contents;
+        set => _outputBuffer.Contents = value;
+    }
+
+    /// <summary>The leftmost column in the terminal.</summary>
+    public int Left
+    {
+        get => _outputBuffer.Left; set => _outputBuffer.Left = value; }
+
+    /// <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.
+    /// </summary>
+    public int Row => _outputBuffer.Row;
+
+    /// <summary>The number of rows visible in the terminal.</summary>
+    public int Rows
+    {
+        get => _outputBuffer.Rows;
+        set => _outputBuffer.Rows = value;
+    }
+
+    /// <summary>The topmost row in the terminal.</summary>
+    public int Top
+    {
+        get => _outputBuffer.Top;
+        set => _outputBuffer.Top = value;
+    }
+
+    // TODO: Probably not everyone right?
+
+    /// <summary>Gets whether the <see cref="ConsoleDriver"/> supports TrueColor output.</summary>
+    public bool SupportsTrueColor => true;
+
+
+    // TODO: Currently ignored
+    /// <summary>
+    ///     Gets or sets whether the <see cref="ConsoleDriver"/> should use 16 colors instead of the default TrueColors.
+    ///     See <see cref="Application.Force16Colors"/> to change this setting via <see cref="ConfigurationManager"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>
+    ///         Will be forced to <see langword="true"/> if <see cref="ConsoleDriver.SupportsTrueColor"/> is
+    ///         <see langword="false"/>, indicating that the <see cref="ConsoleDriver"/> cannot support TrueColor.
+    ///     </para>
+    /// </remarks>
+    public bool Force16Colors { get; set; }
+
+    /// <summary>
+    ///     The <see cref="Attribute"/> that will be used for the next <see cref="AddRune(Rune)"/> or <see cref="AddStr"/>
+    ///     call.
+    /// </summary>
+    public Attribute CurrentAttribute
+    {
+        get => _outputBuffer.CurrentAttribute;
+        set => _outputBuffer.CurrentAttribute = value;
+    }
+
+    /// <summary>Adds the specified rune to the display at the current cursor position.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         When the method returns, <see cref="ConsoleDriver.Col"/> will be incremented by the number of columns
+    ///         <paramref name="rune"/> required, even if the new column value is outside of the <see cref="ConsoleDriver.Clip"/> or screen
+    ///         dimensions defined by <see cref="ConsoleDriver.Cols"/>.
+    ///     </para>
+    ///     <para>
+    ///         If <paramref name="rune"/> requires more than one column, and <see cref="ConsoleDriver.Col"/> plus the number of columns
+    ///         needed exceeds the <see cref="ConsoleDriver.Clip"/> or screen dimensions, the default Unicode replacement character (U+FFFD)
+    ///         will be added instead.
+    ///     </para>
+    /// </remarks>
+    /// <param name="rune">Rune to add.</param>
+    public void AddRune (Rune rune) => _outputBuffer.AddRune (rune);
+
+    /// <summary>
+    ///     Adds the specified <see langword="char"/> to the display at the current cursor position. This method is a
+    ///     convenience method that calls <see cref="ConsoleDriver.AddRune(System.Text.Rune)"/> with the <see cref="Rune"/> constructor.
+    /// </summary>
+    /// <param name="c">Character to add.</param>
+    public void AddRune (char c) => _outputBuffer.AddRune (c);
+
+    /// <summary>Adds the <paramref name="str"/> to the display at the cursor position.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         When the method returns, <see cref="ConsoleDriver.Col"/> will be incremented by the number of columns
+    ///         <paramref name="str"/> required, unless the new column value is outside of the <see cref="ConsoleDriver.Clip"/> or screen
+    ///         dimensions defined by <see cref="ConsoleDriver.Cols"/>.
+    ///     </para>
+    ///     <para>If <paramref name="str"/> requires more columns than are available, the output will be clipped.</para>
+    /// </remarks>
+    /// <param name="str">String.</param>
+    public void AddStr (string str) => _outputBuffer.AddStr (str);
+
+    /// <summary>Clears the <see cref="ConsoleDriver.Contents"/> of the driver.</summary>
+    public void ClearContents ()
+    {
+        _outputBuffer.ClearContents ();
+        ClearedContents?.Invoke (this,new MouseEventArgs ());
+    }
+
+    /// <summary>
+    ///     Raised each time <see cref="ConsoleDriver.ClearContents"/> is called. For benchmarking.
+    /// </summary>
+    public event EventHandler<EventArgs> ClearedContents;
+
+
+    /// <summary>Fills the specified rectangle with the specified rune, using <see cref="ConsoleDriver.CurrentAttribute"/></summary>
+    /// <remarks>
+    /// The value of <see cref="ConsoleDriver.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) => _outputBuffer.FillRect (rect, rune);
+
+    /// <summary>
+    ///     Fills the specified rectangle with the specified <see langword="char"/>. This method is a convenience method
+    ///     that calls <see cref="ConsoleDriver.FillRect(System.Drawing.Rectangle,System.Text.Rune)"/>.
+    /// </summary>
+    /// <param name="rect"></param>
+    /// <param name="c"></param>
+    public void FillRect (Rectangle rect, char c) => _outputBuffer.FillRect (rect, c);
+
+    /// <summary>Gets the terminal cursor visibility.</summary>
+    /// <param name="visibility">The current <see cref="CursorVisibility"/></param>
+    /// <returns><see langword="true"/> upon success</returns>
+    public bool GetCursorVisibility (out CursorVisibility visibility);
+
+    /// <summary>Returns the name of the driver and relevant library version information.</summary>
+    /// <returns></returns>
+    public string GetVersionInfo ();
+
+    /// <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 bool IsRuneSupported (Rune rune) => Rune.IsValid(rune.Value);
+
+    /// <summary>Tests whether the specified coordinate are valid for drawing the specified Rune.</summary>
+    /// <param name="rune">Used to determine if one or two columns are required.</param>
+    /// <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="ConsoleDriver.Clip"/>.
+    ///     <see langword="true"/> otherwise.
+    /// </returns>
+    public bool IsValidLocation (Rune rune, int col, int row) => _outputBuffer.IsValidLocation (rune, col, row);
+
+    /// <summary>
+    ///     Updates <see cref="ConsoleDriver.Col"/> and <see cref="ConsoleDriver.Row"/> to the specified column and row in <see cref="ConsoleDriver.Contents"/>.
+    ///     Used by <see cref="ConsoleDriver.AddRune(System.Text.Rune)"/> and <see cref="ConsoleDriver.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="ConsoleDriver.Cols"/> and
+    ///         <see cref="ConsoleDriver.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 void Move (int col, int row) => _outputBuffer.Move (col, row);
+
+    // TODO: Probably part of output
+
+    /// <summary>Sets the terminal cursor visibility.</summary>
+    /// <param name="visibility">The wished <see cref="CursorVisibility"/></param>
+    /// <returns><see langword="true"/> upon success</returns>
+    public bool SetCursorVisibility (CursorVisibility visibility);
+
+    /// <summary>The event fired when the terminal is resized.</summary>
+    public event EventHandler<SizeChangedEventArgs> SizeChanged;
+
+    // TODO: probably output
+
+    /// <summary>Sets the position of the terminal cursor to <see cref="ConsoleDriver.Col"/> and <see cref="ConsoleDriver.Row"/>.</summary>
+    public void UpdateCursor ();
+
+    /// <summary>Redraws the physical screen with the contents that have been queued up via any of the printing commands.</summary>
+    /// <returns><see langword="true"/> if any updates to the screen were made.</returns>
+    public bool UpdateScreen ();
+
+    /// <summary>Initializes the driver</summary>
+    /// <returns>Returns an instance of <see cref="MainLoop"/> using the <see cref="IMainLoopDriver"/> for the driver.</returns>
+    public MainLoop Init ();
+
+    /// <summary>Ends the execution of the console driver.</summary>
+    public void End ()
+    {
+        // TODO: Nope
+    }
+
+    /// <summary>Selects the specified attribute as the attribute to use for future calls to AddRune and AddString.</summary>
+    /// <remarks>Implementations should call <c>base.SetAttribute(c)</c>.</remarks>
+    /// <param name="c">C.</param>
+    public Attribute SetAttribute (Attribute c) => _outputBuffer.CurrentAttribute = c;
+
+    /// <summary>Gets the current <see cref="Attribute"/>.</summary>
+    /// <returns>The current attribute.</returns>
+    public Attribute GetAttribute () => _outputBuffer.CurrentAttribute;
+
+    /// <summary>Makes an <see cref="Attribute"/>.</summary>
+    /// <param name="foreground">The foreground color.</param>
+    /// <param name="background">The background color.</param>
+    /// <returns>The attribute for the foreground and background colors.</returns>
+    public Attribute MakeColor (in Color foreground, in Color background);
+
+    /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="ConsoleDriver.KeyUp"/>.</summary>
+    public event EventHandler<Key> KeyDown;
+
+    /// <summary>Event fired when a key is released.</summary>
+    /// <remarks>
+    ///     Drivers that do not support key release events will fire this event after <see cref="ConsoleDriver.KeyDown"/> processing is
+    ///     complete.
+    /// </remarks>
+    public event EventHandler<Key> KeyUp;
+
+    /// <summary>Event fired when a mouse event occurs.</summary>
+    public event EventHandler<MouseEventArgs> MouseEvent;
+
+    /// <summary>Simulates a key press.</summary>
+    /// <param name="keyChar">The key character.</param>
+    /// <param name="key">The key.</param>
+    /// <param name="shift">If <see langword="true"/> simulates the Shift key being pressed.</param>
+    /// <param name="alt">If <see langword="true"/> simulates the Alt key being pressed.</param>
+    /// <param name="ctrl">If <see langword="true"/> simulates the Ctrl key being pressed.</param>
+    public void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl);
+
+    /// <summary>
+    /// Queues the given <paramref name="request"/> for execution
+    /// </summary>
+    /// <param name="request"></param>
+    public void QueueAnsiRequest (AnsiEscapeSequenceRequest request);
+
+    public AnsiRequestScheduler GetRequestScheduler ();
+
+    /// <summary>
+    /// Writes the given <paramref name="str"/> directly to the console (rather than the output
+    /// draw buffer).
+    /// </summary>
+    /// <param name="str"></param>
+    public void RawWrite (string str);
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
index 7318815c0f..d1034e339b 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
@@ -13,6 +13,13 @@ public interface IOutputBuffer
     /// </summary>
     Cell [,] Contents { get; set; }
 
+    /// <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 Region? Clip { get; set; }
+
     /// <summary>
     /// The <see cref="Attribute"/> that will be used for the next AddRune or AddStr call.
     /// </summary>
@@ -36,6 +43,10 @@ public interface IOutputBuffer
     ///     <see cref="AddRune(Rune)"/> and <see cref="AddStr"/> to determine where to add content.
     /// </summary>
     public int Col { get; }
+
+    int Left { get; set; }
+    int Top { get; set; }
+
     /// <summary>
     /// Updates the column and row to the specified location in the buffer.
     /// </summary>
@@ -77,4 +88,7 @@ public interface IOutputBuffer
     /// <param name="cols"></param>
     /// <param name="rows"></param>
     void SetWindowSize (int cols, int rows);
+
+    void FillRect (Rectangle rect, Rune rune);
+    void FillRect (Rectangle rect, char rune);
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
index 16a3a4fb68..bf34ee0082 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
@@ -42,7 +42,7 @@ public Attribute CurrentAttribute
     }
 
     /// <summary>The leftmost column in the terminal.</summary>
-    internal virtual int Left { get; set; } = 0;
+    public virtual int Left { get; set; } = 0;
 
     /// <summary>
     ///     Gets the row last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/> are used by
@@ -81,7 +81,7 @@ public int Cols
 
 
     /// <summary>The topmost row in the terminal.</summary>
-    internal virtual int Top { get; set; } = 0;
+    public virtual int Top { get; set; } = 0;
 
 
     // As performance is a concern, we keep track of the dirty lines and only refresh those.
@@ -99,7 +99,7 @@ public int Cols
     ///     to.
     /// </summary>
     /// <value>The rectangle describing the of <see cref="Clip"/> region.</value>
-    internal Region? Clip
+    public Region? Clip
     {
         get => _clip;
         set
@@ -387,6 +387,31 @@ public void SetWindowSize (int cols, int rows)
         ClearContents ();
     }
 
+    /// <inheritdoc />
+    public void FillRect (Rectangle rect, Rune rune)
+    {
+        // BUGBUG: This should be a method on Region
+        rect = Rectangle.Intersect (rect, Clip?.GetBounds () ?? Screen);
+        lock (Contents!)
+        {
+            for (int r = rect.Y; r < rect.Y + rect.Height; r++)
+            {
+                for (int c = rect.X; c < rect.X + rect.Width; c++)
+                {
+                    if (!IsValidLocation (rune, c, r))
+                    {
+                        continue;
+                    }
+                    Contents [r, c] = new Cell
+                    {
+                        Rune = (rune != default ? rune : (Rune)' '),
+                        Attribute = CurrentAttribute, IsDirty = true
+                    };
+                }
+            }
+        }
+    }
+
     // TODO: Make internal once Menu is upgraded
     /// <summary>
     ///     Updates <see cref="Col"/> and <see cref="Row"/> to the specified column and row in <see cref="Contents"/>.
diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index f7923526ed..fe3cffbc07 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -62,7 +62,7 @@
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.OutputBuffer" Collapsed="true">
+  <Class Name="Terminal.Gui.OutputBuffer">
     <Position X="10" Y="8.25" Width="1.5" />
     <Compartments>
       <Compartment Name="Fields" Collapsed="true" />
@@ -146,7 +146,7 @@
   <Interface Name="Terminal.Gui.IOutputBuffer" Collapsed="true">
     <Position X="10" Y="7.25" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AQAAAAAAAIAAAEAIAAAAAIAAAAEERgAAAAAACABAgAA=</HashCode>
+      <HashCode>AQAAAAAAAIAAAEAIAAAAAIAAAAEERgAAAAAAKABAgAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IOutputBuffer.cs</FileName>
     </TypeIdentifier>
   </Interface>

From 90b5836cf4cdea9975f4ed58fdee84b4adedee9d Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 24 Nov 2024 20:31:29 +0000
Subject: [PATCH 022/198] WIP Implement more of facade

---
 Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs       |  4 ----
 .../ConsoleDrivers/V2/ConsoleDriverFacade.cs        | 13 +++++++------
 2 files changed, 7 insertions(+), 10 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
index f64a3a5186..ad4ca9e8b3 100644
--- a/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
@@ -182,10 +182,6 @@ public interface IConsoleDriver
     /// <summary>The event fired when the terminal is resized.</summary>
     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>
-    void Suspend ();
-
     /// <summary>Sets the position of the terminal cursor to <see cref="ConsoleDriver.Col"/> and <see cref="ConsoleDriver.Row"/>.</summary>
     void UpdateCursor ();
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
index 974cec31c7..ac9ea1c907 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
@@ -1,18 +1,19 @@
 namespace Terminal.Gui.ConsoleDrivers.V2;
 class ConsoleDriverFacade<T> : IConsoleDriver
 {
-    
     private readonly IConsoleOutput _output;
     private readonly IConsoleInput<T> _input;
     private readonly InputProcessor<T> _inputProcessor;
     private readonly IOutputBuffer _outputBuffer;
+    private readonly AnsiRequestScheduler _ansiRequestScheduler;
 
-    public ConsoleDriverFacade (IConsoleInput<T> input,InputProcessor<T> inputProcessor,IOutputBuffer outputBuffer, IConsoleOutput output )
+    public ConsoleDriverFacade (IConsoleInput<T> input,InputProcessor<T> inputProcessor,IOutputBuffer outputBuffer, IConsoleOutput output, AnsiRequestScheduler ansiRequestScheduler)
     {
         _output = output;
         _input = input;
         _inputProcessor = inputProcessor;
         _outputBuffer = outputBuffer;
+        _ansiRequestScheduler = ansiRequestScheduler;
     }
     /// <summary>
     /// How long after Esc has been pressed before we give up on getting an Ansi escape sequence
@@ -185,7 +186,7 @@ public void ClearContents ()
 
     /// <summary>Returns the name of the driver and relevant library version information.</summary>
     /// <returns></returns>
-    public string GetVersionInfo ();
+    public string GetVersionInfo () { return GetType ().Name; }
 
     /// <summary>Tests if the specified rune is supported by the driver.</summary>
     /// <param name="rune"></param>
@@ -289,14 +290,14 @@ public void End ()
     /// Queues the given <paramref name="request"/> for execution
     /// </summary>
     /// <param name="request"></param>
-    public void QueueAnsiRequest (AnsiEscapeSequenceRequest request);
+    public void QueueAnsiRequest (AnsiEscapeSequenceRequest request) => _ansiRequestScheduler.SendOrSchedule (request);
 
-    public AnsiRequestScheduler GetRequestScheduler ();
+    public AnsiRequestScheduler GetRequestScheduler () => _ansiRequestScheduler;
 
     /// <summary>
     /// Writes the given <paramref name="str"/> directly to the console (rather than the output
     /// draw buffer).
     /// </summary>
     /// <param name="str"></param>
-    public void RawWrite (string str);
+    public void RawWrite (string str) => _output.Write (str);
 }

From 68f39a823f10d15ca28d3a7b4a7ba1d7b6c7c4ba Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Mon, 25 Nov 2024 10:34:09 +0000
Subject: [PATCH 023/198] Building with new facacde

---
 .../Application/Application.Keyboard.cs       | 10 ----
 Terminal.Gui/Application/Application.Run.cs   | 26 ++--------
 Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs | 10 +---
 Terminal.Gui/ConsoleDrivers/NetDriver.cs      |  1 -
 .../ConsoleDrivers/V2/ConsoleDriverFacade.cs  | 50 +++++++++++++------
 .../ConsoleDrivers/V2/IConsoleOutput.cs       |  2 +
 Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs   |  9 ++++
 .../ConsoleDrivers/V2/OutputBuffer.cs         | 14 ++++++
 .../ConsoleDrivers/V2/WindowsOutput.cs        | 17 +++++++
 UICatalog/Scenario.cs                         |  9 ----
 10 files changed, 84 insertions(+), 64 deletions(-)

diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs
index 16ddcf66c3..54c03349d2 100644
--- a/Terminal.Gui/Application/Application.Keyboard.cs
+++ b/Terminal.Gui/Application/Application.Keyboard.cs
@@ -170,16 +170,6 @@ internal static void AddApplicationKeyBindings ()
                     }
                    );
 
-        AddCommand (
-                    Command.Suspend,
-                    static () =>
-                    {
-                        Driver?.Suspend ();
-
-                        return true;
-                    }
-                   );
-
         AddCommand (
                     Command.NextTabStop,
                     static () => Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop));
diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs
index cf70ff5e32..d428ef31c5 100644
--- a/Terminal.Gui/Application/Application.Run.cs
+++ b/Terminal.Gui/Application/Application.Run.cs
@@ -232,12 +232,7 @@ internal static bool PositionCursor ()
         if (mostFocused is null || !mostFocused.Visible || !mostFocused.Enabled)
         {
             CursorVisibility current = CursorVisibility.Invisible;
-            Driver?.GetCursorVisibility (out current);
-
-            if (current != CursorVisibility.Invisible)
-            {
-                Driver?.SetCursorVisibility (CursorVisibility.Invisible);
-            }
+            Driver?.SetCursorVisibility (CursorVisibility.Invisible);
 
             return false;
         }
@@ -253,7 +248,6 @@ internal static bool PositionCursor ()
 
         Point? cursor = mostFocused.PositionCursor ();
 
-        Driver!.GetCursorVisibility (out CursorVisibility currentCursorVisibility);
 
         if (cursor is { })
         {
@@ -263,27 +257,18 @@ internal static bool PositionCursor ()
             // If the cursor is not in a visible location in the SuperView, hide it
             if (!superViewViewport.Contains (cursor.Value))
             {
-                if (currentCursorVisibility != CursorVisibility.Invisible)
-                {
-                    Driver.SetCursorVisibility (CursorVisibility.Invisible);
-                }
+                Driver.SetCursorVisibility (CursorVisibility.Invisible);
 
                 return false;
             }
 
             // Show it
-            if (currentCursorVisibility == CursorVisibility.Invisible)
-            {
-                Driver.SetCursorVisibility (mostFocused.CursorVisibility);
-            }
+            Driver.SetCursorVisibility (mostFocused.CursorVisibility);
 
             return true;
         }
 
-        if (currentCursorVisibility != CursorVisibility.Invisible)
-        {
-            Driver.SetCursorVisibility (CursorVisibility.Invisible);
-        }
+        Driver.SetCursorVisibility (CursorVisibility.Invisible);
 
         return false;
     }
@@ -512,8 +497,7 @@ public static void LayoutAndDraw (bool forceDraw = false)
 
         View.SetClipToScreen ();
         View.Draw (TopLevels, neededLayout || forceDraw);
-        View.SetClipToScreen ();
-
+        View.SetClipToScreen (); 
         Driver?.Refresh ();
     }
 
diff --git a/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
index ad4ca9e8b3..fd374139a1 100644
--- a/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
@@ -132,10 +132,6 @@ public interface IConsoleDriver
     /// <param name="c"></param>
     void FillRect (Rectangle rect, char c);
 
-    /// <summary>Gets the terminal cursor visibility.</summary>
-    /// <param name="visibility">The current <see cref="CursorVisibility"/></param>
-    /// <returns><see langword="true"/> upon success</returns>
-    bool GetCursorVisibility (out CursorVisibility visibility);
 
     /// <summary>Returns the name of the driver and relevant library version information.</summary>
     /// <returns></returns>
@@ -185,10 +181,6 @@ public interface IConsoleDriver
     /// <summary>Sets the position of the terminal cursor to <see cref="ConsoleDriver.Col"/> and <see cref="ConsoleDriver.Row"/>.</summary>
     void UpdateCursor ();
 
-    /// <summary>Redraws the physical screen with the contents that have been queued up via any of the printing commands.</summary>
-    /// <returns><see langword="true"/> if any updates to the screen were made.</returns>
-    bool UpdateScreen ();
-
     /// <summary>Initializes the driver</summary>
     /// <returns>Returns an instance of <see cref="MainLoop"/> using the <see cref="IMainLoopDriver"/> for the driver.</returns>
     MainLoop Init ();
@@ -247,4 +239,6 @@ public interface IConsoleDriver
     /// </summary>
     /// <param name="str"></param>
     void RawWrite (string str);
+
+    void Refresh ();
 }
diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs
index 162de5f8a3..e9feac8ce4 100644
--- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs
@@ -811,7 +811,6 @@ public override void Suspend ()
             //Enable alternative screen buffer.
             Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
 
-            SetContentsAsDirty ();
             Refresh ();
         }
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
index ac9ea1c907..25573c6fac 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
@@ -179,10 +179,7 @@ public void ClearContents ()
     /// <param name="c"></param>
     public void FillRect (Rectangle rect, char c) => _outputBuffer.FillRect (rect, c);
 
-    /// <summary>Gets the terminal cursor visibility.</summary>
-    /// <param name="visibility">The current <see cref="CursorVisibility"/></param>
-    /// <returns><see langword="true"/> upon success</returns>
-    public bool GetCursorVisibility (out CursorVisibility visibility);
+
 
     /// <summary>Returns the name of the driver and relevant library version information.</summary>
     /// <returns></returns>
@@ -226,23 +223,28 @@ public void ClearContents ()
     /// <summary>Sets the terminal cursor visibility.</summary>
     /// <param name="visibility">The wished <see cref="CursorVisibility"/></param>
     /// <returns><see langword="true"/> upon success</returns>
-    public bool SetCursorVisibility (CursorVisibility visibility);
+    public bool SetCursorVisibility (CursorVisibility visibility)
+    {
+        _output.SetCursorVisibility (visibility);
+
+        return true;
+    }
 
     /// <summary>The event fired when the terminal is resized.</summary>
     public event EventHandler<SizeChangedEventArgs> SizeChanged;
 
-    // TODO: probably output
-
     /// <summary>Sets the position of the terminal cursor to <see cref="ConsoleDriver.Col"/> and <see cref="ConsoleDriver.Row"/>.</summary>
-    public void UpdateCursor ();
-
-    /// <summary>Redraws the physical screen with the contents that have been queued up via any of the printing commands.</summary>
-    /// <returns><see langword="true"/> if any updates to the screen were made.</returns>
-    public bool UpdateScreen ();
+    public void UpdateCursor ()
+    {
+        _output.SetCursorPosition (Col, Row);
+    }
 
     /// <summary>Initializes the driver</summary>
     /// <returns>Returns an instance of <see cref="MainLoop"/> using the <see cref="IMainLoopDriver"/> for the driver.</returns>
-    public MainLoop Init ();
+    public MainLoop Init ()
+    {
+        throw new NotSupportedException ();
+    }
 
     /// <summary>Ends the execution of the console driver.</summary>
     public void End ()
@@ -263,7 +265,15 @@ public void End ()
     /// <param name="foreground">The foreground color.</param>
     /// <param name="background">The background color.</param>
     /// <returns>The attribute for the foreground and background colors.</returns>
-    public Attribute MakeColor (in Color foreground, in Color background);
+    public Attribute MakeColor (in Color foreground, in Color background)
+    {
+        // TODO: what even is this? why Attribute constructor wants to call Driver method which must return an instance of Attribute? ?!?!?!
+        return new Attribute (
+                              -1, // only used by cursesdriver!
+                              foreground,
+                              background
+                             );
+    }
 
     /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="ConsoleDriver.KeyUp"/>.</summary>
     public event EventHandler<Key> KeyDown;
@@ -284,7 +294,11 @@ public void End ()
     /// <param name="shift">If <see langword="true"/> simulates the Shift key being pressed.</param>
     /// <param name="alt">If <see langword="true"/> simulates the Alt key being pressed.</param>
     /// <param name="ctrl">If <see langword="true"/> simulates the Ctrl key being pressed.</param>
-    public void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl);
+    public void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl)
+    {
+        // TODO: implement
+
+    }
 
     /// <summary>
     /// Queues the given <paramref name="request"/> for execution
@@ -300,4 +314,10 @@ public void End ()
     /// </summary>
     /// <param name="str"></param>
     public void RawWrite (string str) => _output.Write (str);
+
+    /// <inheritdoc />
+    public void Refresh ()
+    {
+        // No need we will always draw when dirty
+    }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
index cd2fb62cc4..1019a944f5 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
@@ -4,4 +4,6 @@ public interface IConsoleOutput : IDisposable
     void Write(string text);
     void Write (IOutputBuffer buffer);
     public Size GetWindowSize ();
+    void SetCursorVisibility (CursorVisibility visibility);
+    void SetCursorPosition (int col, int row);
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
index dbaad89be6..932b1ccc14 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
@@ -217,4 +217,13 @@ public void Dispose ()
         Console.Clear ();
     }
 
+    void IConsoleOutput.SetCursorVisibility (CursorVisibility visibility)
+    {
+        Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
+    }
+
+    void IConsoleOutput.SetCursorPosition (int col, int row)
+    {
+        Console.SetCursorPosition (col, row);
+    }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
index bf34ee0082..c5b1643413 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
@@ -412,6 +412,20 @@ public void FillRect (Rectangle rect, Rune rune)
         }
     }
 
+    /// <inheritdoc />
+    public void FillRect (Rectangle rect, char rune)
+    {
+        for (int y = rect.Top; y < rect.Top + rect.Height; y++)
+        {
+            for (int x = rect.Left; x < rect.Left + rect.Width; x++)
+            {
+                Move (x, y);
+                AddRune (rune);
+            }
+        }
+    }
+
+
     // TODO: Make internal once Menu is upgraded
     /// <summary>
     ///     Updates <see cref="Col"/> and <see cref="Row"/> to the specified column and row in <see cref="Contents"/>.
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
index c23b9b16e6..30fc4f9d67 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
@@ -50,6 +50,9 @@ private enum DesiredAccess : uint
     [DllImport ("kernel32.dll", SetLastError = true)]
     private static extern bool SetConsoleActiveScreenBuffer (nint Handle);
 
+    [DllImport ("kernel32.dll")]
+    private static extern bool SetConsoleCursorPosition (nint hConsoleOutput, Coord dwCursorPosition);
+
     private nint _screenBuffer;
 
     public WindowsConsole WinConsole { get; private set; } = new WindowsConsole ();
@@ -192,6 +195,20 @@ public Size GetWindowSize ()
         return sz;
     }
 
+    /// <inheritdoc />
+    public void SetCursorVisibility (CursorVisibility visibility)
+    {
+        var sb = new StringBuilder ();
+        sb.Append (visibility != CursorVisibility.Invisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
+        WinConsole?.WriteANSI (sb.ToString ());
+    }
+
+    /// <inheritdoc />
+    public void SetCursorPosition (int col, int row)
+    {
+        SetConsoleCursorPosition (_screenBuffer, new Coord ((short)col, (short)row));
+    }
+
     /// <inheritdoc />
     public void Dispose ()
     {
diff --git a/UICatalog/Scenario.cs b/UICatalog/Scenario.cs
index 1d62c4ad7a..06635f04d6 100644
--- a/UICatalog/Scenario.cs
+++ b/UICatalog/Scenario.cs
@@ -192,15 +192,6 @@ private void OnApplicationOnInitializedChanged (object? s, EventArgs<bool> a)
 
             Application.Iteration += OnApplicationOnIteration;
             Application.Driver!.ClearedContents += (sender, args) => BenchmarkResults.ClearedContentCount++;
-            Application.Driver!.Refreshed += (sender, args) =>
-            {
-                BenchmarkResults.RefreshedCount++;
-
-                if (args.CurrentValue)
-                {
-                    BenchmarkResults.UpdatedCount++;
-                }
-            };
             Application.NotifyNewRunState += OnApplicationNotifyNewRunState;
 
 

From e46b2a5bedfd196ffd92d7ddb1d728cf21873974 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Mon, 25 Nov 2024 10:53:47 +0000
Subject: [PATCH 024/198] Tests passing

---
 Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs |  1 +
 .../ConsoleDrivers/V2/ConsoleDriverFacade.cs  | 10 ++++++++
 UICatalog/Scenario.cs                         | 13 +++++++++++
 .../ConsoleDrivers/ConsoleDriverTests.cs      |  3 ++-
 UnitTests/UICatalog/ScenarioTests.cs          | 23 +++++++++++--------
 5 files changed, 40 insertions(+), 10 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
index fd374139a1..ca4646dad1 100644
--- a/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
@@ -241,4 +241,5 @@ public interface IConsoleDriver
     void RawWrite (string str);
 
     void Refresh ();
+    bool GetCursorVisibility (out CursorVisibility current);
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
index 25573c6fac..6d50eea0de 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
@@ -6,6 +6,7 @@ class ConsoleDriverFacade<T> : IConsoleDriver
     private readonly InputProcessor<T> _inputProcessor;
     private readonly IOutputBuffer _outputBuffer;
     private readonly AnsiRequestScheduler _ansiRequestScheduler;
+    private CursorVisibility _lastCursor = CursorVisibility.Default;
 
     public ConsoleDriverFacade (IConsoleInput<T> input,InputProcessor<T> inputProcessor,IOutputBuffer outputBuffer, IConsoleOutput output, AnsiRequestScheduler ansiRequestScheduler)
     {
@@ -225,11 +226,19 @@ public void ClearContents ()
     /// <returns><see langword="true"/> upon success</returns>
     public bool SetCursorVisibility (CursorVisibility visibility)
     {
+        _lastCursor = visibility;
         _output.SetCursorVisibility (visibility);
 
         return true;
     }
 
+    /// <inheritdoc />
+    public bool GetCursorVisibility (out CursorVisibility current)
+    {
+        current = _lastCursor;
+
+        return true;
+    }
     /// <summary>The event fired when the terminal is resized.</summary>
     public event EventHandler<SizeChangedEventArgs> SizeChanged;
 
@@ -320,4 +329,5 @@ public void Refresh ()
     {
         // No need we will always draw when dirty
     }
+
 }
diff --git a/UICatalog/Scenario.cs b/UICatalog/Scenario.cs
index 06635f04d6..0b55606d69 100644
--- a/UICatalog/Scenario.cs
+++ b/UICatalog/Scenario.cs
@@ -192,6 +192,19 @@ private void OnApplicationOnInitializedChanged (object? s, EventArgs<bool> a)
 
             Application.Iteration += OnApplicationOnIteration;
             Application.Driver!.ClearedContents += (sender, args) => BenchmarkResults.ClearedContentCount++;
+
+            if (Application.Driver is ConsoleDriver cd)
+            {
+                cd.Refreshed += (sender, args) =>
+                                                 {
+                                                     BenchmarkResults.RefreshedCount++;
+                                                     if (args.CurrentValue)
+                                                     {
+                                                         BenchmarkResults.UpdatedCount++;
+                                                     }
+                                                 };
+
+            }
             Application.NotifyNewRunState += OnApplicationNotifyNewRunState;
 
 
diff --git a/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs b/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs
index d75a56639d..cd957af483 100644
--- a/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs
+++ b/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs
@@ -221,7 +221,8 @@ public void TerminalResized_Simulation (Type driverType)
 
         driver.Cols = 120;
         driver.Rows = 40;
-        driver.OnSizeChanged (new SizeChangedEventArgs (new (driver.Cols, driver.Rows)));
+
+        ((ConsoleDriver)driver).OnSizeChanged (new SizeChangedEventArgs (new (driver.Cols, driver.Rows)));
         Assert.Equal (120, driver.Cols);
         Assert.Equal (40, driver.Rows);
         Assert.True (wasTerminalResized);
diff --git a/UnitTests/UICatalog/ScenarioTests.cs b/UnitTests/UICatalog/ScenarioTests.cs
index aeac22357a..c16a2549ff 100644
--- a/UnitTests/UICatalog/ScenarioTests.cs
+++ b/UnitTests/UICatalog/ScenarioTests.cs
@@ -222,15 +222,20 @@ void OnApplicationOnInitializedChanged (object s, EventArgs<bool> a)
                 initialized = true;
                 Application.Iteration += OnApplicationOnIteration;
                 Application.Driver!.ClearedContents += (sender, args) => clearedContentCount++;
-                Application.Driver!.Refreshed += (sender, args) =>
-                                                 {
-                                                     refreshedCount++;
-
-                                                     if (args.CurrentValue)
-                                                     {
-                                                         updatedCount++;
-                                                     }
-                                                 };
+
+                if (Application.Driver is ConsoleDriver cd)
+                {
+                    cd!.Refreshed += (sender, args) =>
+                                     {
+                                         refreshedCount++;
+
+                                         if (args.CurrentValue)
+                                         {
+                                             updatedCount++;
+                                         }
+                                     };
+                }
+
                 Application.NotifyNewRunState += OnApplicationNotifyNewRunState;
 
                 stopwatch = Stopwatch.StartNew ();

From 02d1a7c2e1f28af7d9a1dbcb503d5b31432f12f2 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Mon, 25 Nov 2024 11:14:34 +0000
Subject: [PATCH 025/198] Set Application.Driver to facade when building main
 loop via coordinator

---
 .../ConsoleDrivers/V2/ConsoleDriverFacade.cs  |  6 +--
 .../ConsoleDrivers/V2/IInputProcessor.cs      |  1 +
 Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs   |  4 +-
 .../ConsoleDrivers/V2/InputProcessor.cs       |  2 +
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    | 15 +++++++
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  | 41 +++++++++++++++----
 6 files changed, 55 insertions(+), 14 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
index 6d50eea0de..852637d0d5 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
@@ -2,17 +2,13 @@
 class ConsoleDriverFacade<T> : IConsoleDriver
 {
     private readonly IConsoleOutput _output;
-    private readonly IConsoleInput<T> _input;
-    private readonly InputProcessor<T> _inputProcessor;
     private readonly IOutputBuffer _outputBuffer;
     private readonly AnsiRequestScheduler _ansiRequestScheduler;
     private CursorVisibility _lastCursor = CursorVisibility.Default;
 
-    public ConsoleDriverFacade (IConsoleInput<T> input,InputProcessor<T> inputProcessor,IOutputBuffer outputBuffer, IConsoleOutput output, AnsiRequestScheduler ansiRequestScheduler)
+    public ConsoleDriverFacade (IOutputBuffer outputBuffer, IConsoleOutput output, AnsiRequestScheduler ansiRequestScheduler)
     {
         _output = output;
-        _input = input;
-        _inputProcessor = inputProcessor;
         _outputBuffer = outputBuffer;
         _ansiRequestScheduler = ansiRequestScheduler;
     }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
index 2f28f2068f..9ee751921b 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
@@ -43,4 +43,5 @@ public interface IInputProcessor
     /// Drains the input buffer, processing all available keystrokes
     /// </summary>
     void ProcessQueue ();
+    public IAnsiResponseParser GetParser ();
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
index 67208eb6a7..fb616785e8 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
@@ -4,9 +4,11 @@ namespace Terminal.Gui;
 
 public interface IMainLoop<T> : IDisposable
 {
-
+    public IOutputBuffer OutputBuffer { get; }
     public IInputProcessor InputProcessor { get; }
 
+    public AnsiRequestScheduler AnsiRequestScheduler { get; }
+
     /// <summary>
     /// Initializes the loop with a buffer from which data can be read
     /// </summary>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index 52fe0b3a96..3d7007005c 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -7,6 +7,8 @@ public abstract class InputProcessor<T> : IInputProcessor
     public AnsiResponseParser<T> Parser { get; } = new ();
     public ConcurrentQueue<T> InputBuffer { get; }
 
+    public IAnsiResponseParser GetParser () => Parser;
+
     /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="KeyUp"/>.</summary>
     public event EventHandler<Key>? KeyDown;
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 6b15bedc02..ab70fca2fb 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -11,6 +11,7 @@ public class MainLoop<T> : IMainLoop<T>
     public IOutputBuffer OutputBuffer { get; private set; } = new OutputBuffer();
 
     public IConsoleOutput Out { get;private set; }
+    public AnsiRequestScheduler AnsiRequestScheduler { get; private set; }
 
 
     // TODO: Remove later
@@ -23,6 +24,8 @@ public void Initialize (ConcurrentQueue<T> inputBuffer, IInputProcessor inputPro
         Out = consoleOutput;
         InputProcessor = inputProcessor;
 
+        AnsiRequestScheduler = new AnsiRequestScheduler (InputProcessor.GetParser ());
+
         // TODO: Remove later
         InputProcessor.KeyDown += (s,k) => sb.Append ((char)k);
         InputProcessor.MouseEvent += (s, e) => { _lastMousePos = e.Position; };
@@ -59,6 +62,18 @@ public void Iteration ()
         var size = Out.GetWindowSize ();
 
         OutputBuffer.SetWindowSize (size.Width, size.Height);
+        // TODO: Test only
+
+        var w = new Window
+        {
+            Title = "Hello World",
+            Width = 30,
+            Height = 5
+        };
+        w.Add (new Button { Text = "OMG!", X = 5, Y = 2 ,Width = Dim.Auto ()});
+        w.Layout ();
+        w.Draw ();
+
 
         OutputBuffer.CurrentAttribute = new Attribute (Color.White, Color.Black);
         OutputBuffer.Move (_lastMousePos.X,_lastMousePos.Y);
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index 51ee8addbb..5fa0c9c48d 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -1,4 +1,5 @@
 using System.Collections.Concurrent;
+using Terminal.Gui.ConsoleDrivers.V2;
 
 namespace Terminal.Gui;
 
@@ -10,6 +11,10 @@ public class MainLoopCoordinator<T> : IMainLoopCoordinator
     private readonly IMainLoop<T> _loop;
     private CancellationTokenSource tokenSource = new ();
     private readonly Func<IConsoleOutput> _outputFactory;
+    private IConsoleInput<T> _input;
+    private IConsoleOutput _output;
+    object oLockInitialization = new ();
+    private ConsoleDriverFacade<T> _facade;
 
     /// <summary>
     /// Creates a new coordinator
@@ -51,25 +56,36 @@ public void StartBlocking ()
     }
     private void RunInput ()
     {
-        // Instance must be constructed on the thread in which it is used.
-        IConsoleInput<T> input = _inputFactory.Invoke ();
-        input.Initialize (_inputBuffer);
+        lock (oLockInitialization)
+        {
+            // Instance must be constructed on the thread in which it is used.
+            _input = _inputFactory.Invoke ();
+            _input.Initialize (_inputBuffer);
+
+            BuildFacadeIfPossible ();
+        }
+
         try
         {
-            input.Run (tokenSource.Token);
+            _input.Run (tokenSource.Token);
         }
         catch (OperationCanceledException)
         {
         }
-        input.Dispose ();
+        _input.Dispose ();
     }
 
     private void RunLoop ()
     {
-        // Instance must be constructed on the thread in which it is used.
-        IConsoleOutput output = _outputFactory.Invoke ();
 
-        _loop.Initialize (_inputBuffer, _inputProcessor, output);
+        lock (oLockInitialization)
+        {
+            // Instance must be constructed on the thread in which it is used.
+            _output = _outputFactory.Invoke ();
+            _loop.Initialize (_inputBuffer, _inputProcessor, _output);
+
+            BuildFacadeIfPossible ();
+        }
 
         try
         {
@@ -81,6 +97,15 @@ private void RunLoop ()
         _loop.Dispose ();
     }
 
+    private void BuildFacadeIfPossible ()
+    {
+        if (_input != null && _output != null)
+        {
+            _facade = new ConsoleDriverFacade<T> (_loop.OutputBuffer,_output,_loop.AnsiRequestScheduler);
+            Application.Driver = _facade;
+        }
+    }
+
     public void Stop ()
     {
         tokenSource.Cancel();

From d0ae141255ef646b62b363a6d4ff348b51fc8e0c Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Mon, 25 Nov 2024 14:45:21 +0000
Subject: [PATCH 026/198] Start working towards integrating View/Application
 with v2

---
 Drivers2/Program.cs                           | 33 ++++++++++++++++++-
 .../Application/Application.Toplevel.cs       |  2 +-
 .../ConsoleDrivers/V2/ConsoleDriverFacade.cs  |  8 ++++-
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    | 23 +++----------
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  |  4 ++-
 5 files changed, 47 insertions(+), 23 deletions(-)

diff --git a/Drivers2/Program.cs b/Drivers2/Program.cs
index 11b18be28c..2699ed897f 100644
--- a/Drivers2/Program.cs
+++ b/Drivers2/Program.cs
@@ -28,6 +28,9 @@ static void Main (string [] args)
 
         // Required to set up colors etc?
         Application.Init ();
+
+        var top = CreateTestWindow ();
+
         IMainLoopCoordinator coordinator;
         if (win)
         {
@@ -44,7 +47,6 @@ static void Main (string [] args)
         }
         else
         {
-
             var inputBuffer = new ConcurrentQueue<ConsoleKeyInfo> ();
             var loop = new MainLoop<ConsoleKeyInfo> ();
             coordinator = new MainLoopCoordinator<ConsoleKeyInfo> (()=>new NetInput (),
@@ -61,6 +63,35 @@ static void Main (string [] args)
                                       coordinator.Stop ();
                                   };
 
+        BeginTestWindow (top);
+
+
+
         coordinator.StartBlocking ();
     }
+
+    private static void BeginTestWindow (Toplevel top)
+    {
+        Application.Top = top;
+    }
+
+    private static Toplevel CreateTestWindow ()
+    {
+        var w = new Window
+        {
+            Title = "Hello World",
+            Width = 30,
+            Height = 5
+        };
+
+        var tf = new TextField { X = 5, Y = 0, Width = 10 };
+        w.AdvanceFocus (NavigationDirection.Forward, null);
+        w.Add (tf);
+
+        var tf2 = new TextField { X = 5, Y = 2, Width = 10 };
+        w.AdvanceFocus (NavigationDirection.Forward, null);
+        w.Add (tf2);
+
+        return w;
+    }
 }
diff --git a/Terminal.Gui/Application/Application.Toplevel.cs b/Terminal.Gui/Application/Application.Toplevel.cs
index 3403d1eda7..904b067673 100644
--- a/Terminal.Gui/Application/Application.Toplevel.cs
+++ b/Terminal.Gui/Application/Application.Toplevel.cs
@@ -10,5 +10,5 @@ public static partial class Application // Toplevel handling
 
     /// <summary>The <see cref="Toplevel"/> that is currently active.</summary>
     /// <value>The top.</value>
-    public static Toplevel? Top { get; internal set; }
+    public static Toplevel? Top { get; set; }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
index 852637d0d5..b2c072e967 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
@@ -1,16 +1,22 @@
 namespace Terminal.Gui.ConsoleDrivers.V2;
 class ConsoleDriverFacade<T> : IConsoleDriver
 {
+    private readonly IInputProcessor _inputProcessor;
     private readonly IConsoleOutput _output;
     private readonly IOutputBuffer _outputBuffer;
     private readonly AnsiRequestScheduler _ansiRequestScheduler;
     private CursorVisibility _lastCursor = CursorVisibility.Default;
 
-    public ConsoleDriverFacade (IOutputBuffer outputBuffer, IConsoleOutput output, AnsiRequestScheduler ansiRequestScheduler)
+    public ConsoleDriverFacade (IInputProcessor inputProcessor, IOutputBuffer outputBuffer, IConsoleOutput output, AnsiRequestScheduler ansiRequestScheduler)
     {
+        _inputProcessor = inputProcessor;
         _output = output;
         _outputBuffer = outputBuffer;
         _ansiRequestScheduler = ansiRequestScheduler;
+
+        _inputProcessor.KeyDown += (s, e) => KeyDown?.Invoke (s, e);
+        _inputProcessor.KeyUp += (s, e) => KeyUp?.Invoke (s, e);
+        _inputProcessor.MouseEvent += (s, e) => MouseEvent?.Invoke (s, e);
     }
     /// <summary>
     /// How long after Esc has been pressed before we give up on getting an Ansi escape sequence
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index ab70fca2fb..1ab9cb7052 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -56,32 +56,17 @@ public void Iteration ()
     {
         InputProcessor.ProcessQueue ();
 
-        Random r = new Random ();
-
         // TODO: throttle this
         var size = Out.GetWindowSize ();
 
         OutputBuffer.SetWindowSize (size.Width, size.Height);
         // TODO: Test only
 
-        var w = new Window
-        {
-            Title = "Hello World",
-            Width = 30,
-            Height = 5
-        };
-        w.Add (new Button { Text = "OMG!", X = 5, Y = 2 ,Width = Dim.Auto ()});
-        w.Layout ();
-        w.Draw ();
-
-
-        OutputBuffer.CurrentAttribute = new Attribute (Color.White, Color.Black);
-        OutputBuffer.Move (_lastMousePos.X,_lastMousePos.Y);
-
-        foreach (var ch in sb.ToString())
+        if (Application.Top != null)
         {
-            OutputBuffer.CurrentAttribute = new Attribute (new Color (r.Next (255), r.Next (255), r.Next (255)), Color.Black);
-            OutputBuffer.AddRune (ch);
+            Application.Top.NeedsDraw = true;
+            Application.Top.Layout ();
+            Application.Top.Draw ();
         }
 
         Out.Write (OutputBuffer);
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index 5fa0c9c48d..d9237a82c0 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -101,8 +101,10 @@ private void BuildFacadeIfPossible ()
     {
         if (_input != null && _output != null)
         {
-            _facade = new ConsoleDriverFacade<T> (_loop.OutputBuffer,_output,_loop.AnsiRequestScheduler);
+            _facade = new ConsoleDriverFacade<T> (_inputProcessor, _loop.OutputBuffer,_output,_loop.AnsiRequestScheduler);
             Application.Driver = _facade;
+
+            Application.Driver.KeyDown += (s, e) => Application.Top?.NewKeyDownEvent (e);
         }
     }
 

From f1f9c95ba9db242bab2d1d69f5949cda47675374 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Mon, 25 Nov 2024 14:50:49 +0000
Subject: [PATCH 027/198] Add key up and mouse handler

---
 Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index d9237a82c0..9c70bfd640 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -105,6 +105,8 @@ private void BuildFacadeIfPossible ()
             Application.Driver = _facade;
 
             Application.Driver.KeyDown += (s, e) => Application.Top?.NewKeyDownEvent (e);
+            Application.Driver.KeyUp += (s, e) => Application.Top?.NewKeyUpEvent (e);
+            Application.Driver.MouseEvent += (s, e) => Application.Top?.NewMouseEvent (e);
         }
     }
 

From e7b40a3ffe3703b589b7cac075a74349f7a98fbb Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Mon, 25 Nov 2024 17:34:26 +0000
Subject: [PATCH 028/198] Update class diagram to show scheduler and facade

---
 Terminal.Gui/ConsoleDrivers/V2/V2.cd | 60 +++++++++++++++++++++-------
 1 file changed, 46 insertions(+), 14 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index fe3cffbc07..b6ff391a66 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -7,7 +7,10 @@
     <Position X="10.083" Y="3.813" Height="0.479" Width="5.325" />
   </Comment>
   <Comment CommentText="Orchestrates the 2 main threads in Terminal.Gui">
-    <Position X="5" Y="0.5" Height="0.291" Width="2.929" />
+    <Position X="5.5" Y="1.25" Height="0.291" Width="2.929" />
+  </Comment>
+  <Comment CommentText="Allows Views to work with new architecture without having to be rewritten.">
+    <Position X="7.604" Y="6.771" Height="0.75" Width="1.7" />
   </Comment>
   <Class Name="Terminal.Gui.WindowsInput" Collapsed="true">
     <Position X="10.5" Y="3" Width="1.75" />
@@ -33,21 +36,31 @@
   </Class>
   <Class Name="Terminal.Gui.MainLoop&lt;T&gt;" Collapsed="true" BaseTypeListCollapsed="true">
     <Position X="10" Y="4.75" Width="1.5" />
-    <TypeIdentifier>
-      <HashCode>QQQAAAAAACAAAQQCAAAAAAAAAAAAAAACAAAAAAABAAI=</HashCode>
+    <AssociationLine Name="AnsiRequestScheduler" Type="Terminal.Gui.AnsiRequestScheduler" ManuallyRouted="true">
+      <Path>
+        <Point X="10.75" Y="4.75" />
+        <Point X="10.75" Y="4.406" />
+        <Point X="17.879" Y="4.406" />
+        <Point X="17.879" Y="4.219" />
+        <Point X="18.75" Y="4.219" />
+      </Path>
+    </AssociationLine>
+    <TypeIdentifier>
+      <HashCode>QQQAAAAAACABAQQCAAAAAAAAAAAAAAACAAAAAAABAAI=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoop.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
       <Property Name="InputProcessor" />
       <Property Name="OutputBuffer" />
       <Property Name="Out" />
+      <Property Name="AnsiRequestScheduler" />
     </ShowAsAssociation>
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.MainLoopCoordinator&lt;T&gt;">
-    <Position X="5" Y="1.25" Width="2" />
+    <Position X="5.5" Y="2" Width="2" />
     <TypeIdentifier>
-      <HashCode>AAAAJAEAAAAAAAAAABAAAAAAAAAQIAQAIQAAAAAAAgg=</HashCode>
+      <HashCode>AAAAJAEAAAIAAAAAABQAAAAAABAQIAQAIQAABAAAAgw=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoopCoordinator.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -69,7 +82,7 @@
       <Compartment Name="Methods" Collapsed="true" />
     </Compartments>
     <TypeIdentifier>
-      <HashCode>AwAAAAAAAIAAAECIBgAEAIAAAAEMRgAACAAAKABAgAA=</HashCode>
+      <HashCode>AwAAAAAAAIAAAECIBgAEQIAAAAEMRgAACAAAKABAgAA=</HashCode>
       <FileName>ConsoleDrivers\V2\OutputBuffer.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
@@ -77,7 +90,7 @@
   <Class Name="Terminal.Gui.NetOutput" Collapsed="true">
     <Position X="13.5" Y="8.25" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AEAAAAAAACAAAAAAAAAAAAAAAQAAQAAAMACAAAEAAAA=</HashCode>
+      <HashCode>AEAAAAAAACEAAAAAAAAAAAAAAQAAQAAAMACAAAkAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\NetOutput.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
@@ -85,7 +98,7 @@
   <Class Name="Terminal.Gui.WindowsOutput" Collapsed="true">
     <Position X="12" Y="8.25" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAAAABAAACAAhAAACAAAACAAAAgAQAAIAAAAAAEAAAQ=</HashCode>
+      <HashCode>AAAAABACACAAhAAACAAAACAAAAgAQAAIMAAAAAEAAAQ=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsOutput.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
@@ -93,7 +106,7 @@
   <Class Name="Terminal.Gui.InputProcessor&lt;T&gt;" Collapsed="true">
     <Position X="15.75" Y="4.75" Width="2" />
     <TypeIdentifier>
-      <HashCode>AQAkAAAAAACAgAAAAggAAAAABAIAAAAAAAAAAAAAAAA=</HashCode>
+      <HashCode>AQAkAAAAAACAgAAAAgggAAAABAIAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\InputProcessor.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -122,6 +135,25 @@
       <FileName>ConsoleDrivers\AnsiResponseParser\MouseParser.cs</FileName>
     </TypeIdentifier>
   </Class>
+  <Class Name="Terminal.Gui.ConsoleDrivers.V2.ConsoleDriverFacade&lt;T&gt;">
+    <Position X="5.5" Y="6.75" Width="2" />
+    <Compartments>
+      <Compartment Name="Methods" Collapsed="true" />
+      <Compartment Name="Fields" Collapsed="true" />
+    </Compartments>
+    <TypeIdentifier>
+      <HashCode>AQMgAAAAAKBAgFEIBBggQJEAAjkaQgIAGAADKABDigQ=</HashCode>
+      <FileName>ConsoleDrivers\V2\ConsoleDriverFacade.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Class Name="Terminal.Gui.AnsiRequestScheduler" Collapsed="true">
+    <Position X="18.75" Y="4" Width="2" />
+    <TypeIdentifier>
+      <HashCode>AAQAACAAIAAAIAACAESQAAQAACGAAAAAAAAAAAAAQQA=</HashCode>
+      <FileName>ConsoleDrivers\AnsiResponseParser\AnsiRequestScheduler.cs</FileName>
+    </TypeIdentifier>
+  </Class>
   <Interface Name="Terminal.Gui.IConsoleInput&lt;T&gt;" Collapsed="true">
     <Position X="11.5" Y="1" Width="1.5" />
     <TypeIdentifier>
@@ -130,30 +162,30 @@
     </TypeIdentifier>
   </Interface>
   <Interface Name="Terminal.Gui.IMainLoop&lt;T&gt;" Collapsed="true">
-    <Position X="8.25" Y="3.75" Width="1.5" />
+    <Position X="8.25" Y="4.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAQAAAAAAAAAAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAI=</HashCode>
+      <HashCode>AAQAAAAAAAABAQQAAAAAAAAAAAAAAAACAAAAAAAAAAI=</HashCode>
       <FileName>ConsoleDrivers\V2\IMainLoop.cs</FileName>
     </TypeIdentifier>
   </Interface>
   <Interface Name="Terminal.Gui.IConsoleOutput" Collapsed="true">
     <Position X="12.75" Y="7.25" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAEAAAA=</HashCode>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAMAAAAAEAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IConsoleOutput.cs</FileName>
     </TypeIdentifier>
   </Interface>
   <Interface Name="Terminal.Gui.IOutputBuffer" Collapsed="true">
     <Position X="10" Y="7.25" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AQAAAAAAAIAAAEAIAAAAAIAAAAEERgAAAAAAKABAgAA=</HashCode>
+      <HashCode>AQAAAAAAAIAAAEAIAAAAQIAAAAEMRgAACAAAKABAgAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IOutputBuffer.cs</FileName>
     </TypeIdentifier>
   </Interface>
   <Interface Name="Terminal.Gui.IInputProcessor">
     <Position X="13" Y="4.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAAkAAAAAACAgAAAAAgAAAAABAIAAAAAAAAAAAAAAAA=</HashCode>
+      <HashCode>AAAkAAAAAACAgAAAAAggAAAABAIAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Interface>

From 04c467eee7dfd07cefba1ea45824ba97ef2e718f Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Mon, 25 Nov 2024 20:59:51 +0000
Subject: [PATCH 029/198] WIP mouse state machine - commented out for now

---
 Terminal.Gui/ConsoleDrivers/V2/MouseState.cs | 168 +++++++++++++++++++
 1 file changed, 168 insertions(+)
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/MouseState.cs

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseState.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseState.cs
new file mode 100644
index 0000000000..a63dbd005f
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseState.cs
@@ -0,0 +1,168 @@
+#nullable enable
+
+namespace Terminal.Gui;
+/*
+public class MouseStateManager
+{
+    /// <summary>
+    /// Function for returning the current time. Use in unit tests to
+    /// ensure repeatable tests.
+    /// </summary>
+    private Func<DateTime> Now { get; set; }
+
+    /// <summary>
+    /// How long to wait for a second click after the first before giving up and
+    /// releasing event as a 'click'
+    /// </summary>
+    public TimeSpan DoubleClickThreshold { get; set; }
+
+    /// <summary>
+    /// How long to wait for a third click after the second before giving up and
+    /// releasing event as a 'double click'
+    /// </summary>
+    public TimeSpan TripleClickThreshold { get; set; }
+
+    /// <summary>
+    /// How far between a mouse down and mouse up before it is considered a 'drag' rather
+    /// than a 'click'. Console row counts for 2 units while column counts for only 1. Distance is
+    /// measured in Euclidean distance.
+    /// </summary>
+    public double DragThreshold { get; set; }
+
+    public MouseState CurrentState { get; private set; }
+
+    private ButtonNarrative? [] _ongoingNarratives = new ButtonNarrative? [4];
+
+    public MouseStateManager (
+        Func<DateTime>? now = null,
+        TimeSpan? doubleClickThreshold = null,
+        TimeSpan? tripleClickThreshold = null,
+        int dragThreshold = 5
+    )
+    {
+        Now = now ?? (() => DateTime.Now);
+        DoubleClickThreshold = doubleClickThreshold ?? TimeSpan.FromMilliseconds (500);
+        TripleClickThreshold = tripleClickThreshold ?? TimeSpan.FromMilliseconds (1000);
+        DragThreshold = dragThreshold;
+    }
+
+
+    /// <summary>
+    /// 
+    /// </summary>
+    /// <param name="e"></param>
+    /// <returns></returns>
+    public IEnumerable<ButtonNarrative> UpdateState (MouseEventArgs e)
+    {
+        // TODO: manage transitions
+
+        for (int i = 0; i < 4; i++)
+        {
+           // Update narratives
+
+           // Release stale or naturally complete ones based on thresholds
+        }
+    }
+
+    /// <summary>
+    /// If user double clicks and we are waiting for a triple click
+    /// we should give up after a short time and just assume no more
+    /// clicks are coming. Call this method if the state for a given button
+    /// has not changed in a while.
+    /// </summary>
+    /// <returns></returns>
+    public ButtonNarrative? ReleaseState (int button)
+    {
+        
+    }
+
+    private void PromoteSingleToDoubleClick ()
+    {
+
+    }
+    private void PromoteDoubleToTripleClick ()
+    {
+
+    }
+
+    public static double DistanceTo (Point p1, Point p2)
+    {
+        int deltaX = p2.X - p1.X;
+        int deltaY = p2.Y - p1.Y;
+        return Math.Sqrt (deltaX * deltaX + deltaY * deltaY);
+    }
+}
+
+/// <summary>
+/// Describes a completed narrative e.g. 'user triple clicked'.
+/// </summary>
+/// <remarks>Internally we can have a double click narrative that becomes
+/// a triple click narrative. But we will not release both i.e. we don't say
+/// user clicked then user double-clicked then user triple clicked</remarks>
+public class ButtonNarrative
+{
+    public int Button { get; set; }
+    public int NumberOfClicks { get; set; }
+
+    /// <summary>
+    /// Mouse states during which click was generated.
+    /// N = 2x<see cref="NumberOfClicks"/>
+    /// </summary>
+    public List<ButtonState> MouseStates { get; set; }
+
+    /// <summary>
+    /// <see langword="true"/> if distance between first mouse down and all
+    /// subsequent events is greater than a given threshold.
+    /// </summary>
+    public bool IsDrag { get; set; }
+}
+
+public class MouseState
+{
+    public ButtonState[] ButtonStates = new ButtonState? [4];
+
+    public Point Position;
+}
+
+public class ButtonState
+{
+    /// <summary>
+    ///     When the button entered its current state.
+    /// </summary>
+    public DateTime At { get; set; }
+
+    /// <summary>
+    /// <see langword="true"/> if the button is currently down
+    /// </summary>
+    private bool Depressed { get; set; }
+
+    /// <summary>
+    /// The screen location when the mouse button entered its current state
+    /// (became depressed or was released)
+    /// </summary>
+    public Point Position { get; set; }
+
+    /// <summary>
+    /// The <see cref="View"/> (if any) that was at the <see cref="Position"/>
+    /// when the button entered its current state.
+    /// </summary>
+    public View? View { get; set; }
+
+    /// <summary>
+    /// True if shift was provided by the console at the time the mouse
+    ///  button entered its current state.
+    /// </summary>
+    public bool Shift { get; set; }
+
+    /// <summary>
+    /// True if control was provided by the console at the time the mouse
+    /// button entered its current state.
+    /// </summary>
+    public bool Ctrl { get; set; }
+
+    /// <summary>
+    /// True if alt was held down at the time the mouse
+    /// button entered its current state.
+    /// </summary>
+    public bool Alt { get; set; }
+}*/
\ No newline at end of file

From 231270d0f9c0dbb88c2ac4df1de7a366f874b90d Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Tue, 26 Nov 2024 19:56:41 +0000
Subject: [PATCH 030/198] Click event getting raised

---
 .../{MouseParser.cs => AnsiMouseParser.cs}    |   2 +-
 .../AnsiResponseParser/AnsiResponseParser.cs  |   2 +-
 .../ConsoleDrivers/V2/IInputProcessor.cs      |   6 +
 .../ConsoleDrivers/V2/InputProcessor.cs       |  21 ++-
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  |   2 +
 Terminal.Gui/ConsoleDrivers/V2/MouseState.cs  | 144 +++++++++++++-----
 Terminal.Gui/ConsoleDrivers/V2/V2.cd          |  45 ++++--
 ...ParserTests.cs => AnsiMouseParserTests.cs} |   6 +-
 8 files changed, 169 insertions(+), 59 deletions(-)
 rename Terminal.Gui/ConsoleDrivers/AnsiResponseParser/{MouseParser.cs => AnsiMouseParser.cs} (99%)
 rename UnitTests/ConsoleDrivers/{MouseParserTests.cs => AnsiMouseParserTests.cs} (94%)

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/MouseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs
similarity index 99%
rename from Terminal.Gui/ConsoleDrivers/AnsiResponseParser/MouseParser.cs
rename to Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs
index 97146b939f..87ea4ba56e 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/MouseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs
@@ -7,7 +7,7 @@ namespace Terminal.Gui;
 ///     Parses mouse ansi escape sequences into <see cref="MouseEventArgs"/>
 ///     including support for pressed, released and mouse wheel.
 /// </summary>
-public class MouseParser
+public class AnsiMouseParser
 {
     // Regex patterns for button press/release, wheel scroll, and mouse position reporting
     private readonly Regex _mouseEventPattern = new (@"\u001b\[<(\d+);(\d+);(\d+)(M|m)", RegexOptions.Compiled);
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
index 4a57b0ad3a..d9f146b650 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
@@ -4,7 +4,7 @@ namespace Terminal.Gui;
 
 public abstract class AnsiResponseParserBase : IAnsiResponseParser
 {
-    private readonly MouseParser _mouseParser = new  ();
+    private readonly AnsiMouseParser _mouseParser = new  ();
     protected object lockExpectedResponses = new ();
 
     protected object lockState = new ();
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
index 9ee751921b..580068ad49 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
@@ -15,6 +15,11 @@ public interface IInputProcessor
 
     /// <summary>Event fired when a mouse event occurs.</summary>
     event EventHandler<MouseEventArgs>? MouseEvent;
+    
+    /// <summary>
+    /// Process low level mouse events into atomic operations / sequences
+    /// </summary>
+    MouseInterpreter MouseInterpreter { get; }
 
     /// <summary>
     ///     Called when a key is pressed down. Fires the <see cref="KeyDown"/> event. This is a precursor to
@@ -44,4 +49,5 @@ public interface IInputProcessor
     /// </summary>
     void ProcessQueue ();
     public IAnsiResponseParser GetParser ();
+
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index 3d7007005c..a29a201ad0 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -9,6 +9,8 @@ public abstract class InputProcessor<T> : IInputProcessor
 
     public IAnsiResponseParser GetParser () => Parser;
 
+    public MouseInterpreter MouseInterpreter { get; } = new ();
+
     /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="KeyUp"/>.</summary>
     public event EventHandler<Key>? KeyDown;
 
@@ -44,7 +46,24 @@ public void OnMouseEvent (MouseEventArgs a)
         // Ensure ScreenPosition is set
         a.ScreenPosition = a.Position;
 
-        MouseEvent?.Invoke (this, a);
+        foreach (var narrative in MouseInterpreter.Process (a))
+        {
+            ResolveNarrative (narrative);
+        }
+    }
+
+    private void ResolveNarrative (ButtonNarrative narrative)
+    {
+        if (narrative.NumberOfClicks == 1)
+        {
+            // its a click
+            MouseEvent?.Invoke (this, new MouseEventArgs
+            {
+                Handled = false,
+                Flags = MouseFlags.Button1Clicked,
+                ScreenPosition = narrative.MouseStates.Last().Position
+            });
+        }
     }
 
     public InputProcessor (ConcurrentQueue<T> inputBuffer)
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index 9c70bfd640..0288f7b841 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -107,6 +107,8 @@ private void BuildFacadeIfPossible ()
             Application.Driver.KeyDown += (s, e) => Application.Top?.NewKeyDownEvent (e);
             Application.Driver.KeyUp += (s, e) => Application.Top?.NewKeyUpEvent (e);
             Application.Driver.MouseEvent += (s, e) => Application.Top?.NewMouseEvent (e);
+
+            
         }
     }
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseState.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseState.cs
index a63dbd005f..5c91fe7b28 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MouseState.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseState.cs
@@ -1,8 +1,8 @@
 #nullable enable
 
 namespace Terminal.Gui;
-/*
-public class MouseStateManager
+
+public class MouseInterpreter
 {
     /// <summary>
     /// Function for returning the current time. Use in unit tests to
@@ -33,7 +33,9 @@ public class MouseStateManager
 
     private ButtonNarrative? [] _ongoingNarratives = new ButtonNarrative? [4];
 
-    public MouseStateManager (
+    public Action<ButtonNarrative> Click { get; set; }
+
+    public MouseInterpreter (
         Func<DateTime>? now = null,
         TimeSpan? doubleClickThreshold = null,
         TimeSpan? tripleClickThreshold = null,
@@ -46,51 +48,83 @@ public MouseStateManager (
         DragThreshold = dragThreshold;
     }
 
-
-    /// <summary>
-    /// 
-    /// </summary>
-    /// <param name="e"></param>
-    /// <returns></returns>
-    public IEnumerable<ButtonNarrative> UpdateState (MouseEventArgs e)
+    public IEnumerable<ButtonNarrative> Process (MouseEventArgs e)
     {
-        // TODO: manage transitions
+        // Update narratives
+       if (e.Flags.HasFlag (MouseFlags.Button1Pressed))
+       {
+           if (_ongoingNarratives [0] == null)
+           {
+               _ongoingNarratives [0] = BeginPressedNarrative (0,e);
+           }
+           else
+           {
+               _ongoingNarratives [0]?.Process (0,e.Position,true);
+           }
+       }
+       else
+       {
+           _ongoingNarratives [0]?.Process (0,e.Position,false);
+       }
+
+       for (var i = 0; i < _ongoingNarratives.Length; i++)
+       {
+           ButtonNarrative? narrative = _ongoingNarratives [i];
+
+           if (narrative != null)
+           {
+               if (ShouldRelease (narrative))
+               {
+                   yield return narrative;
+                   _ongoingNarratives [i] = null;
+               }
+           }
+       }
+    }
 
-        for (int i = 0; i < 4; i++)
+    private bool ShouldRelease (ButtonNarrative narrative)
+    {
+        // TODO: needs to be way smarter
+        if (narrative.NumberOfClicks > 0)
         {
-           // Update narratives
-
-           // Release stale or naturally complete ones based on thresholds
+            return true;
         }
-    }
 
-    /// <summary>
-    /// If user double clicks and we are waiting for a triple click
-    /// we should give up after a short time and just assume no more
-    /// clicks are coming. Call this method if the state for a given button
-    /// has not changed in a while.
-    /// </summary>
-    /// <returns></returns>
-    public ButtonNarrative? ReleaseState (int button)
-    {
-        
+        return false;
     }
 
-    private void PromoteSingleToDoubleClick ()
-    {
-
-    }
-    private void PromoteDoubleToTripleClick ()
+    private ButtonNarrative BeginPressedNarrative (int buttonIdx, MouseEventArgs e)
     {
-
+        return new ButtonNarrative
+        {
+            NumberOfClicks = 0,
+            Now = Now,
+            MouseStates =
+            [
+                new ButtonState
+                {
+                    Button = buttonIdx,
+                    At = Now(),
+                    Pressed = true,
+                    Position = e.ScreenPosition,
+
+                    /* TODO: Do these too*/
+                    View = null,
+                    Shift = false,
+                    Ctrl = false,
+                    Alt = false
+                }
+            ]
+        };
     }
 
+    /* TODO: Probably need this at some point
     public static double DistanceTo (Point p1, Point p2)
     {
         int deltaX = p2.X - p1.X;
         int deltaY = p2.Y - p1.Y;
         return Math.Sqrt (deltaX * deltaX + deltaY * deltaY);
-    }
+    }*/
 }
 
 /// <summary>
@@ -101,20 +135,47 @@ public static double DistanceTo (Point p1, Point p2)
 /// user clicked then user double-clicked then user triple clicked</remarks>
 public class ButtonNarrative
 {
-    public int Button { get; set; }
     public int NumberOfClicks { get; set; }
 
     /// <summary>
     /// Mouse states during which click was generated.
     /// N = 2x<see cref="NumberOfClicks"/>
     /// </summary>
-    public List<ButtonState> MouseStates { get; set; }
+    public List<ButtonState> MouseStates { get; set; } = new ();
 
     /// <summary>
-    /// <see langword="true"/> if distance between first mouse down and all
-    /// subsequent events is greater than a given threshold.
+    /// Function for returning the current time. Use in unit tests to
+    /// ensure repeatable tests.
     /// </summary>
-    public bool IsDrag { get; set; }
+    public Func<DateTime> Now { get; set; }
+
+    public void Process (int buttonIdx, Point position, bool pressed)
+    {
+        var last = MouseStates.Last ();
+
+        // Still pressed
+        if (last.Pressed && pressed)
+        {
+            // No change
+            return;
+        }
+
+        NumberOfClicks++;
+        MouseStates.Add (new ButtonState
+        {
+            Button = buttonIdx,
+            At = Now(),
+            Pressed = false,
+            Position = position,
+
+            /* TODO: Need these probably*/
+            View = null,
+            Shift = false,
+            Ctrl = false,
+            Alt = false,
+
+        });
+    }
 }
 
 public class MouseState
@@ -126,6 +187,7 @@ public class MouseState
 
 public class ButtonState
 {
+    public required int Button { get; set; }
     /// <summary>
     ///     When the button entered its current state.
     /// </summary>
@@ -134,11 +196,11 @@ public class ButtonState
     /// <summary>
     /// <see langword="true"/> if the button is currently down
     /// </summary>
-    private bool Depressed { get; set; }
+    public bool Pressed { get; set; }
 
     /// <summary>
     /// The screen location when the mouse button entered its current state
-    /// (became depressed or was released)
+    /// (became pressed or was released)
     /// </summary>
     public Point Position { get; set; }
 
@@ -165,4 +227,4 @@ public class ButtonState
     /// button entered its current state.
     /// </summary>
     public bool Alt { get; set; }
-}*/
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index b6ff391a66..efb1a532fa 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -69,7 +69,7 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.AnsiResponseParser&lt;T&gt;" Collapsed="true">
-    <Position X="18.75" Y="4.75" Width="2" />
+    <Position X="18.75" Y="5.75" Width="2" />
     <TypeIdentifier>
       <HashCode>AAQAAAAAAAAACIAAAAAAAAAAAAAgAABAAAAAABAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
@@ -88,7 +88,7 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.NetOutput" Collapsed="true">
-    <Position X="13.5" Y="8.25" Width="1.5" />
+    <Position X="13.75" Y="8.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AEAAAAAAACEAAAAAAAAAAAAAAQAAQAAAMACAAAkAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\NetOutput.cs</FileName>
@@ -96,7 +96,7 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.WindowsOutput" Collapsed="true">
-    <Position X="12" Y="8.25" Width="1.5" />
+    <Position X="12.25" Y="8.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAABACACAAhAAACAAAACAAAAgAQAAIMAAAAAEAAAQ=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsOutput.cs</FileName>
@@ -104,9 +104,9 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.InputProcessor&lt;T&gt;" Collapsed="true">
-    <Position X="15.75" Y="4.75" Width="2" />
+    <Position X="15.75" Y="5.75" Width="2" />
     <TypeIdentifier>
-      <HashCode>AQAkAAAAAACAgAAAAgggAAAABAIAAAAAAAAAAAAAAAA=</HashCode>
+      <HashCode>AQAkAAAAAACAgAAAAgggAAAABAIAAAAAAAgAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\InputProcessor.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -115,24 +115,24 @@
     <Lollipop Position="0.1" />
   </Class>
   <Class Name="Terminal.Gui.NetInputProcessor" Collapsed="true">
-    <Position X="16.75" Y="5.75" Width="2" />
+    <Position X="16.75" Y="6.75" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\NetInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.WindowsInputProcessor" Collapsed="true">
-    <Position X="14.75" Y="5.75" Width="2" />
+    <Position X="14.75" Y="6.75" Width="2" />
     <TypeIdentifier>
       <HashCode>AQAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.MouseParser" Collapsed="true">
-    <Position X="20.75" Y="4.75" Width="1.5" />
+  <Class Name="Terminal.Gui.AnsiMouseParser" Collapsed="true">
+    <Position X="22" Y="5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>BAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\AnsiResponseParser\MouseParser.cs</FileName>
+      <FileName>ConsoleDrivers\AnsiResponseParser\AnsiMouseParser.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.ConsoleDrivers.V2.ConsoleDriverFacade&lt;T&gt;">
@@ -154,6 +154,24 @@
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiRequestScheduler.cs</FileName>
     </TypeIdentifier>
   </Class>
+  <Class Name="Terminal.Gui.AnsiResponseParserBase" Collapsed="true">
+    <Position X="18.75" Y="5" Width="2" />
+    <TypeIdentifier>
+      <HashCode>FAAAQAAAAJCQAJgAQAAACQAAIAIAAgBAAQAAJgAQACQ=</HashCode>
+      <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
+    </TypeIdentifier>
+    <ShowAsAssociation>
+      <Field Name="_mouseParser" />
+    </ShowAsAssociation>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Class Name="Terminal.Gui.MouseInterpreter" Collapsed="true">
+    <Position X="22" Y="6.5" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAQAAAAAAAABAAIAAkAAACAAACgAAAAAAABAAAAAAgA=</HashCode>
+      <FileName>ConsoleDrivers\V2\MouseState.cs</FileName>
+    </TypeIdentifier>
+  </Class>
   <Interface Name="Terminal.Gui.IConsoleInput&lt;T&gt;" Collapsed="true">
     <Position X="11.5" Y="1" Width="1.5" />
     <TypeIdentifier>
@@ -169,7 +187,7 @@
     </TypeIdentifier>
   </Interface>
   <Interface Name="Terminal.Gui.IConsoleOutput" Collapsed="true">
-    <Position X="12.75" Y="7.25" Width="1.5" />
+    <Position X="13" Y="7.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAMAAAAAEAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IConsoleOutput.cs</FileName>
@@ -185,9 +203,12 @@
   <Interface Name="Terminal.Gui.IInputProcessor">
     <Position X="13" Y="4.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAAkAAAAAACAgAAAAAggAAAABAIAAAAAAAAAAAAAAAA=</HashCode>
+      <HashCode>AAAkAAAAAACAgAAAAAggAAAABAIAAAAAAAgAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IInputProcessor.cs</FileName>
     </TypeIdentifier>
+    <ShowAsAssociation>
+      <Property Name="MouseInterpreter" />
+    </ShowAsAssociation>
   </Interface>
   <Font Name="Segoe UI" Size="9" />
 </ClassDiagram>
\ No newline at end of file
diff --git a/UnitTests/ConsoleDrivers/MouseParserTests.cs b/UnitTests/ConsoleDrivers/AnsiMouseParserTests.cs
similarity index 94%
rename from UnitTests/ConsoleDrivers/MouseParserTests.cs
rename to UnitTests/ConsoleDrivers/AnsiMouseParserTests.cs
index 567948ecdf..4cfb55ce39 100644
--- a/UnitTests/ConsoleDrivers/MouseParserTests.cs
+++ b/UnitTests/ConsoleDrivers/AnsiMouseParserTests.cs
@@ -1,10 +1,10 @@
 namespace UnitTests.ConsoleDrivers;
 
-public class MouseParserTests
+public class AnsiMouseParserTests
 {
-    private readonly MouseParser _parser;
+    private readonly AnsiMouseParser _parser;
 
-    public MouseParserTests () { _parser = new (); }
+    public AnsiMouseParserTests () { _parser = new (); }
 
     // Consolidated test for all mouse events: button press/release, wheel scroll, position, modifiers
     [Theory]

From e9b8264db42d0a1c7477768bafe51705e3577cff Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Tue, 26 Nov 2024 20:18:09 +0000
Subject: [PATCH 031/198] Working on hit tracking

---
 Terminal.Gui/ConsoleDrivers/V2/IViewFinder.cs | 25 +++++++++++++
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  |  2 --
 Terminal.Gui/ConsoleDrivers/V2/MouseState.cs  | 36 ++++++++++++++++---
 3 files changed, 56 insertions(+), 7 deletions(-)
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/IViewFinder.cs

diff --git a/Terminal.Gui/ConsoleDrivers/V2/IViewFinder.cs b/Terminal.Gui/ConsoleDrivers/V2/IViewFinder.cs
new file mode 100644
index 0000000000..23d32ac9c3
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/IViewFinder.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Terminal.Gui.ConsoleDrivers.V2;
+
+public interface IViewFinder
+{
+    View GetViewAt (Point screenPosition, out Point viewportPoint);
+}
+
+class StaticViewFinder : IViewFinder
+{
+    /// <inheritdoc />
+    public View GetViewAt (Point screenPosition, out Point viewportPoint)
+    {
+        var hit = View.GetViewsUnderMouse (screenPosition).LastOrDefault ();
+
+        viewportPoint = hit?.ScreenToViewport (screenPosition) ?? Point.Empty;
+
+        return hit;
+    }
+}
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index 0288f7b841..9c70bfd640 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -107,8 +107,6 @@ private void BuildFacadeIfPossible ()
             Application.Driver.KeyDown += (s, e) => Application.Top?.NewKeyDownEvent (e);
             Application.Driver.KeyUp += (s, e) => Application.Top?.NewKeyUpEvent (e);
             Application.Driver.MouseEvent += (s, e) => Application.Top?.NewMouseEvent (e);
-
-            
         }
     }
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseState.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseState.cs
index 5c91fe7b28..78f357305a 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MouseState.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseState.cs
@@ -1,9 +1,13 @@
 #nullable enable
 
+using Terminal.Gui.ConsoleDrivers.V2;
+
 namespace Terminal.Gui;
 
 public class MouseInterpreter
 {
+    private readonly IViewFinder _viewFinder;
+
     /// <summary>
     /// Function for returning the current time. Use in unit tests to
     /// ensure repeatable tests.
@@ -37,11 +41,13 @@ public class MouseInterpreter
 
     public MouseInterpreter (
         Func<DateTime>? now = null,
+        IViewFinder viewFinder = null,
         TimeSpan? doubleClickThreshold = null,
         TimeSpan? tripleClickThreshold = null,
         int dragThreshold = 5
     )
     {
+        _viewFinder = viewFinder ?? new StaticViewFinder ();
         Now = now ?? (() => DateTime.Now);
         DoubleClickThreshold = doubleClickThreshold ?? TimeSpan.FromMilliseconds (500);
         TripleClickThreshold = tripleClickThreshold ?? TimeSpan.FromMilliseconds (1000);
@@ -95,21 +101,23 @@ private bool ShouldRelease (ButtonNarrative narrative)
 
     private ButtonNarrative BeginPressedNarrative (int buttonIdx, MouseEventArgs e)
     {
-        return new ButtonNarrative
+        var view = _viewFinder.GetViewAt (e.Position, out var viewport);
+
+        return new ButtonNarrative(Now,_viewFinder)
         {
             NumberOfClicks = 0,
-            Now = Now,
             MouseStates =
             [
-                new ButtonState
+                new ButtonState()
                 {
                     Button = buttonIdx,
                     At = Now(),
                     Pressed = true,
                     Position = e.ScreenPosition,
+                    View = view,
+                    ViewportPosition = viewport,
 
                     /* TODO: Do these too*/
-                    View = null,
                     Shift = false,
                     Ctrl = false,
                     Alt = false
@@ -118,6 +126,8 @@ private ButtonNarrative BeginPressedNarrative (int buttonIdx, MouseEventArgs e)
         };
     }
 
+    public Point ViewportPosition { get; set; }
+
     /* TODO: Probably need this at some point
     public static double DistanceTo (Point p1, Point p2)
     {
@@ -135,6 +145,7 @@ public static double DistanceTo (Point p1, Point p2)
 /// user clicked then user double-clicked then user triple clicked</remarks>
 public class ButtonNarrative
 {
+    private readonly IViewFinder _viewFinder;
     public int NumberOfClicks { get; set; }
 
     /// <summary>
@@ -149,6 +160,12 @@ public class ButtonNarrative
     /// </summary>
     public Func<DateTime> Now { get; set; }
 
+    public ButtonNarrative (Func<DateTime> now, IViewFinder viewFinder)
+    {
+        Now = now;
+        _viewFinder = viewFinder;
+    }
+
     public void Process (int buttonIdx, Point position, bool pressed)
     {
         var last = MouseStates.Last ();
@@ -160,6 +177,8 @@ public void Process (int buttonIdx, Point position, bool pressed)
             return;
         }
 
+        var view = _viewFinder.GetViewAt (position, out var viewport);
+
         NumberOfClicks++;
         MouseStates.Add (new ButtonState
         {
@@ -168,8 +187,10 @@ public void Process (int buttonIdx, Point position, bool pressed)
             Pressed = false,
             Position = position,
 
+            View = view,
+            ViewportPosition = viewport,
+
             /* TODO: Need these probably*/
-            View = null,
             Shift = false,
             Ctrl = false,
             Alt = false,
@@ -210,6 +231,11 @@ public class ButtonState
     /// </summary>
     public View? View { get; set; }
 
+    /// <summary>
+    /// Viewport relative position within <see cref="View"/> (if there is one)
+    /// </summary>
+    public Point ViewportPosition { get; set; }
+
     /// <summary>
     /// True if shift was provided by the console at the time the mouse
     ///  button entered its current state.

From 0fd0f153e775876bf23c6344221ac7ddebb5b6bc Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Tue, 26 Nov 2024 20:24:14 +0000
Subject: [PATCH 032/198] Ability to switch focus

---
 .../ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs | 9 +++++++--
 Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs         | 6 +++++-
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs               | 9 ---------
 Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs    | 6 +++++-
 4 files changed, 17 insertions(+), 13 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs
index 87ea4ba56e..fbb9c0984f 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs
@@ -1,4 +1,5 @@
 #nullable enable
+using System.Buffers.Text;
 using System.Text.RegularExpressions;
 
 namespace Terminal.Gui;
@@ -26,8 +27,12 @@ public class AnsiMouseParser
         if (match.Success)
         {
             int buttonCode = int.Parse (match.Groups [1].Value);
-            int x = int.Parse (match.Groups [2].Value);
-            int y = int.Parse (match.Groups [3].Value);
+
+            // The top-left corner of the terminal corresponds to (1, 1) for both X (column) and Y (row) coordinates.
+            // ANSI standards and terminal conventions historically treat screen positions as 1 - based.
+
+            int x = int.Parse (match.Groups [2].Value) - 1;
+            int y = int.Parse (match.Groups [3].Value) - 1;
             char terminator = match.Groups [4].Value.Single ();
 
             return new()
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index a29a201ad0..51ab656a4a 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -56,12 +56,16 @@ private void ResolveNarrative (ButtonNarrative narrative)
     {
         if (narrative.NumberOfClicks == 1)
         {
+            var last = narrative.MouseStates.Last ();
+
             // its a click
             MouseEvent?.Invoke (this, new MouseEventArgs
             {
                 Handled = false,
                 Flags = MouseFlags.Button1Clicked,
-                ScreenPosition = narrative.MouseStates.Last().Position
+                ScreenPosition = last.Position,
+                Position = last.ViewportPosition,
+                View = last.View,
             });
         }
     }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 1ab9cb7052..b037f6a3ec 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -13,11 +13,6 @@ public class MainLoop<T> : IMainLoop<T>
     public IConsoleOutput Out { get;private set; }
     public AnsiRequestScheduler AnsiRequestScheduler { get; private set; }
 
-
-    // TODO: Remove later
-    StringBuilder sb = new StringBuilder ("*");
-    private Point _lastMousePos = new Point(0,0);
-
     public void Initialize (ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput)
     {
         InputBuffer = inputBuffer;
@@ -26,12 +21,8 @@ public void Initialize (ConcurrentQueue<T> inputBuffer, IInputProcessor inputPro
 
         AnsiRequestScheduler = new AnsiRequestScheduler (InputProcessor.GetParser ());
 
-        // TODO: Remove later
-        InputProcessor.KeyDown += (s,k) => sb.Append ((char)k);
-        InputProcessor.MouseEvent += (s, e) => { _lastMousePos = e.Position; };
     }
 
-
     public void Run (CancellationToken token)
     {
         do
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index 9c70bfd640..e7fd334a22 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -106,7 +106,11 @@ private void BuildFacadeIfPossible ()
 
             Application.Driver.KeyDown += (s, e) => Application.Top?.NewKeyDownEvent (e);
             Application.Driver.KeyUp += (s, e) => Application.Top?.NewKeyUpEvent (e);
-            Application.Driver.MouseEvent += (s, e) => Application.Top?.NewMouseEvent (e);
+
+            Application.Driver.MouseEvent += (s, e) =>
+                                             {
+                                                 e.View?.NewMouseEvent (e);
+                                             };
         }
     }
 

From 7dead873fcf3fcc444ff4159e72cb2ad07ef7d3d Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Tue, 26 Nov 2024 20:28:48 +0000
Subject: [PATCH 033/198] Update class diagram

---
 Terminal.Gui/ConsoleDrivers/V2/V2.cd | 43 ++++++++++++++++++++++++----
 1 file changed, 38 insertions(+), 5 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index efb1a532fa..6912a96154 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -46,7 +46,7 @@
       </Path>
     </AssociationLine>
     <TypeIdentifier>
-      <HashCode>QQQAAAAAACABAQQCAAAAAAAAAAAAAAACAAAAAAABAAI=</HashCode>
+      <HashCode>QQQAAAAAACABAQQAAAAAAAAAAAAAAAACAAAAAAAAAAI=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoop.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -106,7 +106,7 @@
   <Class Name="Terminal.Gui.InputProcessor&lt;T&gt;" Collapsed="true">
     <Position X="15.75" Y="5.75" Width="2" />
     <TypeIdentifier>
-      <HashCode>AQAkAAAAAACAgAAAAgggAAAABAIAAAAAAAgAAAAAAAA=</HashCode>
+      <HashCode>AQAkAAAAAACAgAAAAgggAAAABAoAAAAAAAgAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\InputProcessor.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -165,10 +165,36 @@
     </ShowAsAssociation>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.MouseInterpreter" Collapsed="true">
-    <Position X="22" Y="6.5" Width="1.5" />
+  <Class Name="Terminal.Gui.MouseInterpreter">
+    <Position X="19.25" Y="6.75" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAQAAAAAAAABAAIAAkAAACAAACgAAAAAAABAAAAAAgA=</HashCode>
+      <HashCode>AAQAAAAAAAABAAIAAkAAACAAICgIAAAAAABAAAAAAgA=</HashCode>
+      <FileName>ConsoleDrivers\V2\MouseState.cs</FileName>
+    </TypeIdentifier>
+    <ShowAsAssociation>
+      <Field Name="_viewFinder" />
+    </ShowAsAssociation>
+    <ShowAsCollectionAssociation>
+      <Field Name="_ongoingNarratives" />
+    </ShowAsCollectionAssociation>
+  </Class>
+  <Class Name="Terminal.Gui.ButtonNarrative">
+    <Position X="22.25" Y="8.25" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>IAAAAAAAAAAAAAAAAgAAAAAAICAAAAQAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\MouseState.cs</FileName>
+    </TypeIdentifier>
+    <ShowAsAssociation>
+      <Field Name="_viewFinder" />
+    </ShowAsAssociation>
+    <ShowAsCollectionAssociation>
+      <Property Name="MouseStates" />
+    </ShowAsCollectionAssociation>
+  </Class>
+  <Class Name="Terminal.Gui.ButtonState">
+    <Position X="25" Y="8.25" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AEAAAAAAAAAAAAggAAAAAAAAAACJAAAAAoAAAAAEAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\MouseState.cs</FileName>
     </TypeIdentifier>
   </Class>
@@ -210,5 +236,12 @@
       <Property Name="MouseInterpreter" />
     </ShowAsAssociation>
   </Interface>
+  <Interface Name="Terminal.Gui.ConsoleDrivers.V2.IViewFinder">
+    <Position X="22.25" Y="6.5" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\IViewFinder.cs</FileName>
+    </TypeIdentifier>
+  </Interface>
   <Font Name="Segoe UI" Size="9" />
 </ClassDiagram>
\ No newline at end of file

From f280048e747c0b7d8e8f570c33930d292cfebcdb Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 28 Nov 2024 14:30:12 +0000
Subject: [PATCH 034/198] fix for 0 based seq

---
 .../ConsoleDrivers/AnsiMouseParserTests.cs    | 22 +++++++++----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/UnitTests/ConsoleDrivers/AnsiMouseParserTests.cs b/UnitTests/ConsoleDrivers/AnsiMouseParserTests.cs
index 4cfb55ce39..c7bb046210 100644
--- a/UnitTests/ConsoleDrivers/AnsiMouseParserTests.cs
+++ b/UnitTests/ConsoleDrivers/AnsiMouseParserTests.cs
@@ -8,17 +8,17 @@ public class AnsiMouseParserTests
 
     // Consolidated test for all mouse events: button press/release, wheel scroll, position, modifiers
     [Theory]
-    [InlineData ("\u001b[<0;100;200M", 100, 200, MouseFlags.Button1Pressed)] // Button 1 Pressed
-    [InlineData ("\u001b[<0;150;250m", 150, 250, MouseFlags.Button1Released)] // Button 1 Released
-    [InlineData ("\u001b[<1;120;220M", 120, 220, MouseFlags.Button2Pressed)] // Button 2 Pressed
-    [InlineData ("\u001b[<1;180;280m", 180, 280, MouseFlags.Button2Released)] // Button 2 Released
-    [InlineData ("\u001b[<2;200;300M", 200, 300, MouseFlags.Button3Pressed)] // Button 3 Pressed
-    [InlineData ("\u001b[<2;250;350m", 250, 350, MouseFlags.Button3Released)] // Button 3 Released
-    [InlineData ("\u001b[<64;100;200M", 100, 200, MouseFlags.WheeledUp)] // Wheel Scroll Up
-    [InlineData ("\u001b[<65;150;250m", 150, 250, MouseFlags.WheeledDown)] // Wheel Scroll Down
-    [InlineData ("\u001b[<39;100;200m", 100, 200, MouseFlags.ButtonShift | MouseFlags.ReportMousePosition)] // Mouse Position (No Button)
-    [InlineData ("\u001b[<43;120;240m", 120, 240, MouseFlags.ButtonAlt | MouseFlags.ReportMousePosition)] // Mouse Position (No Button)
-    [InlineData ("\u001b[<8;100;200M", 100, 200, MouseFlags.Button1Pressed | MouseFlags.ButtonAlt)] // Button 1 Pressed + Alt
+    [InlineData ("\u001b[<0;100;200M", 99, 199, MouseFlags.Button1Pressed)] // Button 1 Pressed
+    [InlineData ("\u001b[<0;150;250m", 149, 249, MouseFlags.Button1Released)] // Button 1 Released
+    [InlineData ("\u001b[<1;120;220M", 119, 219, MouseFlags.Button2Pressed)] // Button 2 Pressed
+    [InlineData ("\u001b[<1;180;280m", 179, 279, MouseFlags.Button2Released)] // Button 2 Released
+    [InlineData ("\u001b[<2;200;300M", 199, 299, MouseFlags.Button3Pressed)] // Button 3 Pressed
+    [InlineData ("\u001b[<2;250;350m", 249, 349, MouseFlags.Button3Released)] // Button 3 Released
+    [InlineData ("\u001b[<64;100;200M", 99, 199, MouseFlags.WheeledUp)] // Wheel Scroll Up
+    [InlineData ("\u001b[<65;150;250m", 149, 249, MouseFlags.WheeledDown)] // Wheel Scroll Down
+    [InlineData ("\u001b[<39;100;200m", 99, 199, MouseFlags.ButtonShift | MouseFlags.ReportMousePosition)] // Mouse Position (No Button)
+    [InlineData ("\u001b[<43;120;240m", 119, 239, MouseFlags.ButtonAlt | MouseFlags.ReportMousePosition)] // Mouse Position (No Button)
+    [InlineData ("\u001b[<8;100;200M", 99, 199, MouseFlags.Button1Pressed | MouseFlags.ButtonAlt)] // Button 1 Pressed + Alt
     [InlineData ("\u001b[<invalid;100;200M", 0, 0, MouseFlags.None)] // Invalid Input (Expecting null)
     [InlineData ("\u001b[<100;200;300Z", 0, 0, MouseFlags.None)] // Invalid Input (Expecting null)
     [InlineData ("\u001b[<invalidInput>", 0, 0, MouseFlags.None)] // Invalid Input (Expecting null)

From 59a7479ec3bbf2225ce8bfee31240fd0a4956149 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 28 Nov 2024 15:05:42 +0000
Subject: [PATCH 035/198] Update class diagram

---
 Terminal.Gui/ConsoleDrivers/V2/V2.cd | 105 ++++++++++++++++++++++-----
 1 file changed, 87 insertions(+), 18 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index 6912a96154..1b676bf06d 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -12,6 +12,12 @@
   <Comment CommentText="Allows Views to work with new architecture without having to be rewritten.">
     <Position X="7.604" Y="6.771" Height="0.75" Width="1.7" />
   </Comment>
+  <Comment CommentText="Ansi Escape Sequence - Request / Response">
+    <Position X="18.458" Y="3.562" Height="0.396" Width="2.825" />
+  </Comment>
+  <Comment CommentText="Mouse interpretation subsystem">
+    <Position X="16.125" Y="8.833" Height="0.396" Width="2.825" />
+  </Comment>
   <Class Name="Terminal.Gui.WindowsInput" Collapsed="true">
     <Position X="10.5" Y="3" Width="1.75" />
     <TypeIdentifier>
@@ -36,13 +42,12 @@
   </Class>
   <Class Name="Terminal.Gui.MainLoop&lt;T&gt;" Collapsed="true" BaseTypeListCollapsed="true">
     <Position X="10" Y="4.75" Width="1.5" />
-    <AssociationLine Name="AnsiRequestScheduler" Type="Terminal.Gui.AnsiRequestScheduler" ManuallyRouted="true">
+    <AssociationLine Name="AnsiRequestScheduler" Type="Terminal.Gui.AnsiRequestScheduler" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
       <Path>
         <Point X="10.75" Y="4.75" />
-        <Point X="10.75" Y="4.406" />
-        <Point X="17.879" Y="4.406" />
-        <Point X="17.879" Y="4.219" />
-        <Point X="18.75" Y="4.219" />
+        <Point X="10.75" Y="4.39" />
+        <Point X="19.625" Y="4.39" />
+        <Point X="19.625" Y="4.5" />
       </Path>
     </AssociationLine>
     <TypeIdentifier>
@@ -69,7 +74,7 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.AnsiResponseParser&lt;T&gt;" Collapsed="true">
-    <Position X="18.75" Y="5.75" Width="2" />
+    <Position X="19.5" Y="7.75" Width="2" />
     <TypeIdentifier>
       <HashCode>AAQAAAAAAAAACIAAAAAAAAAAAAAgAABAAAAAABAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
@@ -104,7 +109,17 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.InputProcessor&lt;T&gt;" Collapsed="true">
-    <Position X="15.75" Y="5.75" Width="2" />
+    <Position X="15.5" Y="4.75" Width="2" />
+    <AssociationLine Name="Parser" Type="Terminal.Gui.AnsiResponseParser&lt;T&gt;" FixedFromPoint="true" FixedToPoint="true">
+      <Path>
+        <Point X="17.5" Y="5.031" />
+        <Point X="18.125" Y="5.031" />
+        <Point X="18.125" Y="5.5" />
+        <Point X="19" Y="5.5" />
+        <Point X="19" Y="8" />
+        <Point X="19.5" Y="8" />
+      </Path>
+    </AssociationLine>
     <TypeIdentifier>
       <HashCode>AQAkAAAAAACAgAAAAgggAAAABAoAAAAAAAgAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\InputProcessor.cs</FileName>
@@ -115,21 +130,21 @@
     <Lollipop Position="0.1" />
   </Class>
   <Class Name="Terminal.Gui.NetInputProcessor" Collapsed="true">
-    <Position X="16.75" Y="6.75" Width="2" />
+    <Position X="16.75" Y="5.75" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\NetInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.WindowsInputProcessor" Collapsed="true">
-    <Position X="14.75" Y="6.75" Width="2" />
+    <Position X="14.75" Y="5.75" Width="2" />
     <TypeIdentifier>
       <HashCode>AQAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.AnsiMouseParser" Collapsed="true">
-    <Position X="22" Y="5" Width="1.5" />
+    <Position X="22.5" Y="9.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>BAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiMouseParser.cs</FileName>
@@ -142,31 +157,35 @@
       <Compartment Name="Fields" Collapsed="true" />
     </Compartments>
     <TypeIdentifier>
-      <HashCode>AQMgAAAAAKBAgFEIBBggQJEAAjkaQgIAGAADKABDigQ=</HashCode>
+      <HashCode>AQMgAAAAAKBAgFEIBBggQJEAAjkaQgIAGQADKABDggQ=</HashCode>
       <FileName>ConsoleDrivers\V2\ConsoleDriverFacade.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.AnsiRequestScheduler" Collapsed="true">
-    <Position X="18.75" Y="4" Width="2" />
+    <Position X="18.75" Y="4.5" Width="2" />
     <TypeIdentifier>
       <HashCode>AAQAACAAIAAAIAACAESQAAQAACGAAAAAAAAAAAAAQQA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiRequestScheduler.cs</FileName>
     </TypeIdentifier>
+    <ShowAsCollectionAssociation>
+      <Property Name="QueuedRequests" />
+    </ShowAsCollectionAssociation>
   </Class>
   <Class Name="Terminal.Gui.AnsiResponseParserBase" Collapsed="true">
-    <Position X="18.75" Y="5" Width="2" />
+    <Position X="19.5" Y="7" Width="2" />
     <TypeIdentifier>
-      <HashCode>FAAAQAAAAJCQAJgAQAAACQAAIAIAAgBAAQAAJgAQACQ=</HashCode>
+      <HashCode>UACASAAAEICQALAAQAAACAAAIAIAAABAAQIAJgAQACQ=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
       <Field Name="_mouseParser" />
+      <Field Name="_heldContent" />
     </ShowAsAssociation>
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.MouseInterpreter">
-    <Position X="19.25" Y="6.75" Width="1.5" />
+    <Position X="16.25" Y="9.5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAQAAAAAAAABAAIAAkAAACAAICgIAAAAAABAAAAAAgA=</HashCode>
       <FileName>ConsoleDrivers\V2\MouseState.cs</FileName>
@@ -179,7 +198,7 @@
     </ShowAsCollectionAssociation>
   </Class>
   <Class Name="Terminal.Gui.ButtonNarrative">
-    <Position X="22.25" Y="8.25" Width="1.5" />
+    <Position X="19.25" Y="11" Width="1.5" />
     <TypeIdentifier>
       <HashCode>IAAAAAAAAAAAAAAAAgAAAAAAICAAAAQAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\MouseState.cs</FileName>
@@ -192,12 +211,42 @@
     </ShowAsCollectionAssociation>
   </Class>
   <Class Name="Terminal.Gui.ButtonState">
-    <Position X="25" Y="8.25" Width="1.5" />
+    <Position X="22" Y="11" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AEAAAAAAAAAAAAggAAAAAAAAAACJAAAAAoAAAAAEAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\MouseState.cs</FileName>
     </TypeIdentifier>
   </Class>
+  <Class Name="Terminal.Gui.StringHeld" Collapsed="true">
+    <Position X="22.5" Y="8.5" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAIAACAAAAAAAIAAAAAAAACAAAAAAAgAAAA=</HashCode>
+      <FileName>ConsoleDrivers\AnsiResponseParser\StringHeld.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Class Name="Terminal.Gui.GenericHeld&lt;T&gt;" Collapsed="true">
+    <Position X="24" Y="8.5" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAgAIAACAAAAAAAIAAAAAAAACAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\AnsiResponseParser\GenericHeld.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Class Name="Terminal.Gui.AnsiEscapeSequenceRequest">
+    <Position X="22.25" Y="4.5" Width="2.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAEAAAAAAAEAAAAACAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\AnsiEscapeSequenceRequest.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="Terminal.Gui.AnsiEscapeSequence" Collapsed="true">
+    <Position X="22.25" Y="3.75" Width="2.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAgAAEAAAA=</HashCode>
+      <FileName>ConsoleDrivers\AnsiEscapeSequence.cs</FileName>
+    </TypeIdentifier>
+  </Class>
   <Interface Name="Terminal.Gui.IConsoleInput&lt;T&gt;" Collapsed="true">
     <Position X="11.5" Y="1" Width="1.5" />
     <TypeIdentifier>
@@ -228,6 +277,19 @@
   </Interface>
   <Interface Name="Terminal.Gui.IInputProcessor">
     <Position X="13" Y="4.5" Width="1.5" />
+    <AssociationLine Name="MouseInterpreter" Type="Terminal.Gui.MouseInterpreter" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
+      <Path>
+        <Point X="14.5" Y="6.875" />
+        <Point X="14.62" Y="6.875" />
+        <Point X="14.62" Y="7.953" />
+        <Point X="15.807" Y="7.953" />
+        <Point X="15.807" Y="11.143" />
+        <Point X="16.25" Y="11.143" />
+      </Path>
+      <MemberNameLabel ManuallyPlaced="true">
+        <Position X="0.123" Y="0.152" />
+      </MemberNameLabel>
+    </AssociationLine>
     <TypeIdentifier>
       <HashCode>AAAkAAAAAACAgAAAAAggAAAABAIAAAAAAAgAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IInputProcessor.cs</FileName>
@@ -237,11 +299,18 @@
     </ShowAsAssociation>
   </Interface>
   <Interface Name="Terminal.Gui.ConsoleDrivers.V2.IViewFinder">
-    <Position X="22.25" Y="6.5" Width="1.5" />
+    <Position X="19.25" Y="9.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IViewFinder.cs</FileName>
     </TypeIdentifier>
   </Interface>
+  <Interface Name="Terminal.Gui.IHeld">
+    <Position X="23" Y="6.5" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAIAACAAAAAAAIAAAAAAAACAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\AnsiResponseParser\IHeld.cs</FileName>
+    </TypeIdentifier>
+  </Interface>
   <Font Name="Segoe UI" Size="9" />
 </ClassDiagram>
\ No newline at end of file

From 889e4268d8557c3faf3a6b8b3f8041ab7423b40a Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 28 Nov 2024 15:12:53 +0000
Subject: [PATCH 036/198] Update class diagram

---
 Terminal.Gui/ConsoleDrivers/V2/V2.cd | 62 ++++++++++++++++++++--------
 1 file changed, 44 insertions(+), 18 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index 1b676bf06d..a7bcfd779e 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -16,7 +16,7 @@
     <Position X="18.458" Y="3.562" Height="0.396" Width="2.825" />
   </Comment>
   <Comment CommentText="Mouse interpretation subsystem">
-    <Position X="16.125" Y="8.833" Height="0.396" Width="2.825" />
+    <Position X="12.625" Y="9.833" Height="0.396" Width="2.825" />
   </Comment>
   <Class Name="Terminal.Gui.WindowsInput" Collapsed="true">
     <Position X="10.5" Y="3" Width="1.75" />
@@ -74,7 +74,7 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.AnsiResponseParser&lt;T&gt;" Collapsed="true">
-    <Position X="19.5" Y="7.75" Width="2" />
+    <Position X="18.75" Y="10" Width="2" />
     <TypeIdentifier>
       <HashCode>AAQAAAAAAAAACIAAAAAAAAAAAAAgAABAAAAAABAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
@@ -116,8 +116,10 @@
         <Point X="18.125" Y="5.031" />
         <Point X="18.125" Y="5.5" />
         <Point X="19" Y="5.5" />
-        <Point X="19" Y="8" />
-        <Point X="19.5" Y="8" />
+        <Point X="19" Y="9.75" />
+        <Point X="18.5" Y="9.75" />
+        <Point X="18.5" Y="10.25" />
+        <Point X="18.75" Y="10.25" />
       </Path>
     </AssociationLine>
     <TypeIdentifier>
@@ -143,8 +145,8 @@
       <FileName>ConsoleDrivers\V2\WindowsInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.AnsiMouseParser" Collapsed="true">
-    <Position X="22.5" Y="9.25" Width="1.5" />
+  <Class Name="Terminal.Gui.AnsiMouseParser">
+    <Position X="23.5" Y="9" Width="1.75" />
     <TypeIdentifier>
       <HashCode>BAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiMouseParser.cs</FileName>
@@ -173,7 +175,7 @@
     </ShowAsCollectionAssociation>
   </Class>
   <Class Name="Terminal.Gui.AnsiResponseParserBase" Collapsed="true">
-    <Position X="19.5" Y="7" Width="2" />
+    <Position X="19.5" Y="9" Width="2" />
     <TypeIdentifier>
       <HashCode>UACASAAAEICQALAAQAAACAAAIAIAAABAAQIAJgAQACQ=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
@@ -185,7 +187,7 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.MouseInterpreter">
-    <Position X="16.25" Y="9.5" Width="1.5" />
+    <Position X="12.75" Y="10.5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAQAAAAAAAABAAIAAkAAACAAICgIAAAAAABAAAAAAgA=</HashCode>
       <FileName>ConsoleDrivers\V2\MouseState.cs</FileName>
@@ -198,7 +200,7 @@
     </ShowAsCollectionAssociation>
   </Class>
   <Class Name="Terminal.Gui.ButtonNarrative">
-    <Position X="19.25" Y="11" Width="1.5" />
+    <Position X="15.75" Y="12" Width="1.5" />
     <TypeIdentifier>
       <HashCode>IAAAAAAAAAAAAAAAAgAAAAAAICAAAAQAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\MouseState.cs</FileName>
@@ -211,14 +213,14 @@
     </ShowAsCollectionAssociation>
   </Class>
   <Class Name="Terminal.Gui.ButtonState">
-    <Position X="22" Y="11" Width="1.5" />
+    <Position X="18.5" Y="12" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AEAAAAAAAAAAAAggAAAAAAAAAACJAAAAAoAAAAAEAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\MouseState.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.StringHeld" Collapsed="true">
-    <Position X="22.5" Y="8.5" Width="1.5" />
+    <Position X="21" Y="11" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAIAACAAAAAAAIAAAAAAAACAAAAAAAgAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\StringHeld.cs</FileName>
@@ -226,7 +228,7 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.GenericHeld&lt;T&gt;" Collapsed="true">
-    <Position X="24" Y="8.5" Width="1.5" />
+    <Position X="19.25" Y="11" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAgAIAACAAAAAAAIAAAAAAAACAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\GenericHeld.cs</FileName>
@@ -247,6 +249,13 @@
       <FileName>ConsoleDrivers\AnsiEscapeSequence.cs</FileName>
     </TypeIdentifier>
   </Class>
+  <Class Name="Terminal.Gui.AnsiResponseParser" Collapsed="true">
+    <Position X="20.75" Y="10" Width="1.75" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAgACBAAAAAABAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
+    </TypeIdentifier>
+  </Class>
   <Interface Name="Terminal.Gui.IConsoleInput&lt;T&gt;" Collapsed="true">
     <Position X="11.5" Y="1" Width="1.5" />
     <TypeIdentifier>
@@ -280,11 +289,11 @@
     <AssociationLine Name="MouseInterpreter" Type="Terminal.Gui.MouseInterpreter" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
       <Path>
         <Point X="14.5" Y="6.875" />
-        <Point X="14.62" Y="6.875" />
-        <Point X="14.62" Y="7.953" />
-        <Point X="15.807" Y="7.953" />
-        <Point X="15.807" Y="11.143" />
-        <Point X="16.25" Y="11.143" />
+        <Point X="15.5" Y="6.875" />
+        <Point X="15.5" Y="10.125" />
+        <Point X="12.375" Y="10.125" />
+        <Point X="12.375" Y="12.143" />
+        <Point X="12.75" Y="12.143" />
       </Path>
       <MemberNameLabel ManuallyPlaced="true">
         <Position X="0.123" Y="0.152" />
@@ -299,7 +308,7 @@
     </ShowAsAssociation>
   </Interface>
   <Interface Name="Terminal.Gui.ConsoleDrivers.V2.IViewFinder">
-    <Position X="19.25" Y="9.25" Width="1.5" />
+    <Position X="15.75" Y="10.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IViewFinder.cs</FileName>
@@ -312,5 +321,22 @@
       <FileName>ConsoleDrivers\AnsiResponseParser\IHeld.cs</FileName>
     </TypeIdentifier>
   </Interface>
+  <Interface Name="Terminal.Gui.IAnsiResponseParser">
+    <Position X="19.5" Y="5.25" Width="2" />
+    <TypeIdentifier>
+      <HashCode>AAAAQAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAJAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\AnsiResponseParser\IAnsiResponseParser.cs</FileName>
+    </TypeIdentifier>
+    <ShowAsAssociation>
+      <Property Name="State" />
+    </ShowAsAssociation>
+  </Interface>
+  <Enum Name="Terminal.Gui.AnsiResponseParserState">
+    <Position X="19.5" Y="7.25" Width="2" />
+    <TypeIdentifier>
+      <HashCode>AAAAAEAAAAAAAAAAAAAAAAAAAAAIAAIAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParserState.cs</FileName>
+    </TypeIdentifier>
+  </Enum>
   <Font Name="Segoe UI" Size="9" />
 </ClassDiagram>
\ No newline at end of file

From e2fd4fe130947fec881a760e1700ae6bc12013a0 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Tue, 3 Dec 2024 19:56:47 +0000
Subject: [PATCH 037/198] More conservative version of IApplication

---
 .../Application/Application.Initialization.cs |  28 ++-
 Terminal.Gui/Application/Application.Run.cs   |  86 +------
 Terminal.Gui/Application/Application.cd       |  91 ++++++++
 Terminal.Gui/Application/ApplicationImpl.cs   | 209 ++++++++++++++++++
 Terminal.Gui/Application/IApplication.cs      | 138 ++++++++++++
 5 files changed, 455 insertions(+), 97 deletions(-)
 create mode 100644 Terminal.Gui/Application/Application.cd
 create mode 100644 Terminal.Gui/Application/ApplicationImpl.cs
 create mode 100644 Terminal.Gui/Application/IApplication.cs

diff --git a/Terminal.Gui/Application/Application.Initialization.cs b/Terminal.Gui/Application/Application.Initialization.cs
index c58e1bf92a..b78bd37eb8 100644
--- a/Terminal.Gui/Application/Application.Initialization.cs
+++ b/Terminal.Gui/Application/Application.Initialization.cs
@@ -37,7 +37,10 @@ public static partial class Application // Initialization (Init/Shutdown)
     /// </param>
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
-    public static void Init (IConsoleDriver? driver = null, string? driverName = null) { InternalInit (driver, driverName); }
+    public static void Init (IConsoleDriver? driver = null, string? driverName = null)
+    {
+        ApplicationImpl.Instance.Init (driver, driverName);
+    }
 
     internal static int MainThreadId { get; set; } = -1;
 
@@ -210,20 +213,7 @@ internal static void InternalInit (
     ///     up (Disposed)
     ///     and terminal settings are restored.
     /// </remarks>
-    public static void Shutdown ()
-    {
-        // TODO: Throw an exception if Init hasn't been called.
-
-        bool wasInitialized = Initialized;
-        ResetState ();
-        PrintJsonErrors ();
-
-        if (wasInitialized)
-        {
-            bool init = Initialized;
-            InitializedChanged?.Invoke (null, new (in init));
-        }
-    }
+    public static void Shutdown () => ApplicationImpl.Instance.Shutdown ();
 
     /// <summary>
     ///     Gets whether the application has been initialized with <see cref="Init"/> and not yet shutdown with <see cref="Shutdown"/>.
@@ -242,4 +232,12 @@ public static void Shutdown ()
     ///     Intended to support unit tests that need to know when the application has been initialized.
     /// </remarks>
     public static event EventHandler<EventArgs<bool>>? InitializedChanged;
+
+    /// <summary>
+    ///  Raises the <see cref="InitializedChanged"/> event.
+    /// </summary>
+    internal static void OnInitializedChanged (object sender, EventArgs<bool> e)
+    {
+        Application.InitializedChanged?.Invoke (sender,e);
+    }
 }
diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs
index 64f7960079..4ee5ceb6b8 100644
--- a/Terminal.Gui/Application/Application.Run.cs
+++ b/Terminal.Gui/Application/Application.Run.cs
@@ -305,7 +305,8 @@ internal static bool PositionCursor ()
     /// <returns>The created <see cref="Toplevel"/> object. The caller is responsible for disposing this object.</returns>
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
-    public static Toplevel Run (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null) { return Run<Toplevel> (errorHandler, driver); }
+    public static Toplevel Run (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null) =>
+        ApplicationImpl.Instance.Run (errorHandler, driver);
 
     /// <summary>
     ///     Runs the application by creating a <see cref="Toplevel"/>-derived object of type <c>T</c> and calling
@@ -331,20 +332,7 @@ internal static bool PositionCursor ()
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
     public static T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
-        where T : Toplevel, new()
-    {
-        if (!Initialized)
-        {
-            // Init() has NOT been called.
-            InternalInit (driver, null, true);
-        }
-
-        var top = new T ();
-
-        Run (top, errorHandler);
-
-        return top;
-    }
+        where T : Toplevel, new() => ApplicationImpl.Instance.Run<T> (errorHandler, driver);
 
     /// <summary>Runs the Application using the provided <see cref="Toplevel"/> view.</summary>
     /// <remarks>
@@ -385,73 +373,7 @@ public static T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriv
     ///     rethrows when null).
     /// </param>
     public static void Run (Toplevel view, Func<Exception, bool>? errorHandler = null)
-    {
-        ArgumentNullException.ThrowIfNull (view);
-
-        if (Initialized)
-        {
-            if (Driver is null)
-            {
-                // Disposing before throwing
-                view.Dispose ();
-
-                // This code path should be impossible because Init(null, null) will select the platform default driver
-                throw new InvalidOperationException (
-                                                     "Init() completed without a driver being set (this should be impossible); Run<T>() cannot be called."
-                                                    );
-            }
-        }
-        else
-        {
-            // Init() has NOT been called.
-            throw new InvalidOperationException (
-                                                 "Init() has not been called. Only Run() or Run<T>() can be used without calling Init()."
-                                                );
-        }
-
-        var resume = true;
-
-        while (resume)
-        {
-#if !DEBUG
-            try
-            {
-#endif
-            resume = false;
-            RunState runState = Begin (view);
-
-            // If EndAfterFirstIteration is true then the user must dispose of the runToken
-            // by using NotifyStopRunState event.
-            RunLoop (runState);
-
-            if (runState.Toplevel is null)
-            {
-#if DEBUG_IDISPOSABLE
-                Debug.Assert (TopLevels.Count == 0);
-#endif
-                runState.Dispose ();
-
-                return;
-            }
-
-            if (!EndAfterFirstIteration)
-            {
-                End (runState);
-            }
-#if !DEBUG
-            }
-            catch (Exception error)
-            {
-                if (errorHandler is null)
-                {
-                    throw;
-                }
-
-                resume = errorHandler (error);
-            }
-#endif
-        }
-    }
+        => ApplicationImpl.Instance.Run (view, errorHandler);
 
     /// <summary>Adds a timeout to the application.</summary>
     /// <remarks>
diff --git a/Terminal.Gui/Application/Application.cd b/Terminal.Gui/Application/Application.cd
new file mode 100644
index 0000000000..9c22dd77ba
--- /dev/null
+++ b/Terminal.Gui/Application/Application.cd
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ClassDiagram MajorVersion="1" MinorVersion="1">
+  <Class Name="Terminal.Gui.Application">
+    <Position X="2.25" Y="1.5" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>hEI4FAgAqARIspQfBQo0gTGiACNL0AICESJKoggBSg8=</HashCode>
+      <FileName>Application\Application.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="Terminal.Gui.ApplicationNavigation" Collapsed="true">
+    <Position X="13.75" Y="1.75" Width="2" />
+    <TypeIdentifier>
+      <HashCode>AABAAAAAAABCAAAAAAAAAAAAAAAAIgIAAAAAAAAAAAA=</HashCode>
+      <FileName>Application\ApplicationNavigation.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="Terminal.Gui.IterationEventArgs" Collapsed="true">
+    <Position X="16" Y="2" Width="2" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>Application\IterationEventArgs.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="Terminal.Gui.MainLoop" Collapsed="true" BaseTypeListCollapsed="true">
+    <Position X="10.25" Y="2.75" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>CAAAIAAAASAAAQAQAAAAAIBADQAAEAAYIgIIwAAAAAI=</HashCode>
+      <FileName>Application\MainLoop.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" Collapsed="true" />
+  </Class>
+  <Class Name="Terminal.Gui.MainLoopSyncContext" Collapsed="true">
+    <Position X="12" Y="2.75" Width="2" />
+    <TypeIdentifier>
+      <HashCode>AAAAAgAAAAAAAAAAAEAAAAAACAAAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>Application\MainLoopSyncContext.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="Terminal.Gui.RunState" Collapsed="true" BaseTypeListCollapsed="true">
+    <Position X="14.25" Y="3" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAACACAgAAAAAAAAAAAAAAAAACQAAAAAAAAAA=</HashCode>
+      <FileName>Application\RunState.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" Collapsed="true" />
+  </Class>
+  <Class Name="Terminal.Gui.RunStateEventArgs" Collapsed="true">
+    <Position X="16" Y="3" Width="2" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA=</HashCode>
+      <FileName>Application\RunStateEventArgs.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="Terminal.Gui.Timeout" Collapsed="true">
+    <Position X="10.25" Y="3.75" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAQAA=</HashCode>
+      <FileName>Application\Timeout.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="Terminal.Gui.TimeoutEventArgs" Collapsed="true">
+    <Position X="12" Y="3.75" Width="2" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAACAIAAAAAAAAAAAA=</HashCode>
+      <FileName>Application\TimeoutEventArgs.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="Terminal.Gui.ApplicationImpl" BaseTypeListCollapsed="true">
+    <Position X="5.75" Y="1.75" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAQAACAACAAAI=</HashCode>
+      <FileName>Application\ApplicationImpl.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Interface Name="Terminal.Gui.IMainLoopDriver" Collapsed="true">
+    <Position X="12" Y="5" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAACAAAAAQAAAAABAAAAAAAEAAAAAAAAAAAAAA=</HashCode>
+      <FileName>Application\MainLoop.cs</FileName>
+    </TypeIdentifier>
+  </Interface>
+  <Interface Name="Terminal.Gui.IApplication">
+    <Position X="4" Y="1.75" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAACAAAAAAI=</HashCode>
+      <FileName>Application\IApplication.cs</FileName>
+    </TypeIdentifier>
+  </Interface>
+  <Font Name="Segoe UI" Size="9" />
+</ClassDiagram>
\ No newline at end of file
diff --git a/Terminal.Gui/Application/ApplicationImpl.cs b/Terminal.Gui/Application/ApplicationImpl.cs
new file mode 100644
index 0000000000..ee4da05737
--- /dev/null
+++ b/Terminal.Gui/Application/ApplicationImpl.cs
@@ -0,0 +1,209 @@
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Terminal.Gui;
+
+public class ApplicationImpl : IApplication
+{
+    // Private static readonly Lazy instance of Application
+    private static readonly Lazy<IApplication> lazyInstance = new (() => new ApplicationImpl ());
+
+    // Public static property to access the instance
+    public static IApplication Instance => lazyInstance.Value;
+
+    /// <inheritdoc/>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+    public void Init (IConsoleDriver? driver = null, string? driverName = null)
+    {
+        Application.InternalInit (driver, driverName);
+    }
+
+    /// <summary>
+    ///     Runs the application by creating a <see cref="Toplevel"/> object and calling
+    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>Calling <see cref="Init"/> first is not needed as this function will initialize the application.</para>
+    ///     <para>
+    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has returned) to
+    ///         ensure resources are cleaned up and terminal settings restored.
+    ///     </para>
+    ///     <para>
+    ///         The caller is responsible for disposing the object returned by this method.
+    ///     </para>
+    /// </remarks>
+    /// <returns>The created <see cref="Toplevel"/> object. The caller is responsible for disposing this object.</returns>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+    public Toplevel Run (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null) { return Run<Toplevel> (errorHandler, driver); }
+
+    /// <summary>
+    ///     Runs the application by creating a <see cref="Toplevel"/>-derived object of type <c>T</c> and calling
+    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>Calling <see cref="Init"/> first is not needed as this function will initialize the application.</para>
+    ///     <para>
+    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has returned) to
+    ///         ensure resources are cleaned up and terminal settings restored.
+    ///     </para>
+    ///     <para>
+    ///         The caller is responsible for disposing the object returned by this method.
+    ///     </para>
+    /// </remarks>
+    /// <param name="errorHandler"></param>
+    /// <param name="driver">
+    ///     The <see cref="IConsoleDriver"/> to use. If not specified the default driver for the platform will
+    ///     be used ( <see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, or <see cref="NetDriver"/>). Must be
+    ///     <see langword="null"/> if <see cref="Init"/> has already been called.
+    /// </param>
+    /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+    public T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
+        where T : Toplevel, new()
+    {
+        if (!Application.Initialized)
+        {
+            // Init() has NOT been called.
+            Application.InternalInit (driver, null, true);
+        }
+
+        var top = new T ();
+
+        Run (top, errorHandler);
+
+        return top;
+    }
+
+    /// <summary>Runs the Application using the provided <see cref="Toplevel"/> view.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         This method is used to start processing events for the main application, but it is also used to run other
+    ///         modal <see cref="View"/>s such as <see cref="Dialog"/> boxes.
+    ///     </para>
+    ///     <para>
+    ///         To make a <see cref="Run(Terminal.Gui.Toplevel,System.Func{System.Exception,bool})"/> stop execution, call
+    ///         <see cref="Application.RequestStop"/>.
+    ///     </para>
+    ///     <para>
+    ///         Calling <see cref="Run(Terminal.Gui.Toplevel,System.Func{System.Exception,bool})"/> is equivalent to calling
+    ///         <see cref="Begin(Toplevel)"/>, followed by <see cref="RunLoop(RunState)"/>, and then calling
+    ///         <see cref="End(RunState)"/>.
+    ///     </para>
+    ///     <para>
+    ///         Alternatively, to have a program control the main loop and process events manually, call
+    ///         <see cref="Begin(Toplevel)"/> to set things up manually and then repeatedly call
+    ///         <see cref="RunLoop(RunState)"/> with the wait parameter set to false. By doing this the
+    ///         <see cref="RunLoop(RunState)"/> method will only process any pending events, timers, idle handlers and then
+    ///         return control immediately.
+    ///     </para>
+    ///     <para>When using <see cref="Run{T}"/> or
+    ///         <see cref="Run(System.Func{System.Exception,bool},Terminal.Gui.IConsoleDriver)"/>
+    ///         <see cref="Init"/> will be called automatically.
+    ///     </para>
+    ///     <para>
+    ///         RELEASE builds only: When <paramref name="errorHandler"/> is <see langword="null"/> any exceptions will be
+    ///         rethrown. Otherwise, if <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/>
+    ///         returns <see langword="true"/> the <see cref="RunLoop(RunState)"/> will resume; otherwise this method will
+    ///         exit.
+    ///     </para>
+    /// </remarks>
+    /// <param name="view">The <see cref="Toplevel"/> to run as a modal.</param>
+    /// <param name="errorHandler">
+    ///     RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true,
+    ///     rethrows when null).
+    /// </param>
+    public void Run (Toplevel view, Func<Exception, bool>? errorHandler = null)
+    {
+        ArgumentNullException.ThrowIfNull (view);
+
+        if (Application.Initialized)
+        {
+            if (Application.Driver is null)
+            {
+                // Disposing before throwing
+                view.Dispose ();
+
+                // This code path should be impossible because Init(null, null) will select the platform default driver
+                throw new InvalidOperationException (
+                                                     "Init() completed without a driver being set (this should be impossible); Run<T>() cannot be called."
+                                                    );
+            }
+        }
+        else
+        {
+            // Init() has NOT been called.
+            throw new InvalidOperationException (
+                                                 "Init() has not been called. Only Run() or Run<T>() can be used without calling Init()."
+                                                );
+        }
+
+        var resume = true;
+
+        while (resume)
+        {
+#if !DEBUG
+            try
+            {
+#endif
+            resume = false;
+            RunState runState = Application.Begin (view);
+
+            // If EndAfterFirstIteration is true then the user must dispose of the runToken
+            // by using NotifyStopRunState event.
+            Application.RunLoop (runState);
+
+            if (runState.Toplevel is null)
+            {
+#if DEBUG_IDISPOSABLE
+                Debug.Assert (Application.TopLevels.Count == 0);
+#endif
+                runState.Dispose ();
+
+                return;
+            }
+
+            if (!Application.EndAfterFirstIteration)
+            {
+                Application.End (runState);
+            }
+#if !DEBUG
+            }
+            catch (Exception error)
+            {
+                if (errorHandler is null)
+                {
+                    throw;
+                }
+
+                resume = errorHandler (error);
+            }
+#endif
+        }
+    }
+
+    /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
+    /// <remarks>
+    ///     Shutdown must be called for every call to <see cref="Init"/> or
+    ///     <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to ensure all resources are cleaned
+    ///     up (Disposed)
+    ///     and terminal settings are restored.
+    /// </remarks>
+    public void Shutdown ()
+    {
+        // TODO: Throw an exception if Init hasn't been called.
+
+        bool wasInitialized = Application.Initialized;
+        Application.ResetState ();
+        PrintJsonErrors ();
+
+        if (wasInitialized)
+        {
+            bool init = Application.Initialized;
+
+            Application.OnInitializedChanged(this, new (in init));
+        }
+    }
+}
diff --git a/Terminal.Gui/Application/IApplication.cs b/Terminal.Gui/Application/IApplication.cs
new file mode 100644
index 0000000000..d54e0fccd9
--- /dev/null
+++ b/Terminal.Gui/Application/IApplication.cs
@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Terminal.Gui;
+
+public interface IApplication
+{
+    /// <summary>Initializes a new instance of <see cref="Terminal.Gui"/> Application.</summary>
+    /// <para>Call this method once per instance (or after <see cref="Shutdown"/> has been called).</para>
+    /// <para>
+    ///     This function loads the right <see cref="IConsoleDriver"/> for the platform, Creates a <see cref="Toplevel"/>. and
+    ///     assigns it to <see cref="Top"/>
+    /// </para>
+    /// <para>
+    ///     <see cref="Shutdown"/> must be called when the application is closing (typically after
+    ///     <see cref="Run{T}"/> has returned) to ensure resources are cleaned up and
+    ///     terminal settings
+    ///     restored.
+    /// </para>
+    /// <para>
+    ///     The <see cref="Run{T}"/> function combines
+    ///     <see cref="Init(Terminal.Gui.IConsoleDriver,string)"/> and <see cref="Run(Toplevel, Func{Exception, bool})"/>
+    ///     into a single
+    ///     call. An application cam use <see cref="Run{T}"/> without explicitly calling
+    ///     <see cref="Init(Terminal.Gui.IConsoleDriver,string)"/>.
+    /// </para>
+    /// <param name="driver">
+    ///     The <see cref="IConsoleDriver"/> to use. If neither <paramref name="driver"/> or
+    ///     <paramref name="driverName"/> are specified the default driver for the platform will be used.
+    /// </param>
+    /// <param name="driverName">
+    ///     The short name (e.g. "net", "windows", "ansi", "fake", or "curses") of the
+    ///     <see cref="IConsoleDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are
+    ///     specified the default driver for the platform will be used.
+    /// </param>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+    public void Init (IConsoleDriver? driver = null, string? driverName = null);
+
+
+    /// <summary>
+    ///     Runs the application by creating a <see cref="Toplevel"/> object and calling
+    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>Calling <see cref="Init"/> first is not needed as this function will initialize the application.</para>
+    ///     <para>
+    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has returned) to
+    ///         ensure resources are cleaned up and terminal settings restored.
+    ///     </para>
+    ///     <para>
+    ///         The caller is responsible for disposing the object returned by this method.
+    ///     </para>
+    /// </remarks>
+    /// <returns>The created <see cref="Toplevel"/> object. The caller is responsible for disposing this object.</returns>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+    public Toplevel Run (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null);
+
+    /// <summary>
+    ///     Runs the application by creating a <see cref="Toplevel"/>-derived object of type <c>T</c> and calling
+    ///     <see cref="Run(Toplevel, Func{Exception, bool})"/>.
+    /// </summary>
+    /// <remarks>
+    ///     <para>Calling <see cref="Init"/> first is not needed as this function will initialize the application.</para>
+    ///     <para>
+    ///         <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has returned) to
+    ///         ensure resources are cleaned up and terminal settings restored.
+    ///     </para>
+    ///     <para>
+    ///         The caller is responsible for disposing the object returned by this method.
+    ///     </para>
+    /// </remarks>
+    /// <param name="errorHandler"></param>
+    /// <param name="driver">
+    ///     The <see cref="IConsoleDriver"/> to use. If not specified the default driver for the platform will
+    ///     be used ( <see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, or <see cref="NetDriver"/>). Must be
+    ///     <see langword="null"/> if <see cref="Init"/> has already been called.
+    /// </param>
+    /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
+    public T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
+        where T : Toplevel, new ();
+
+    /// <summary>Runs the Application using the provided <see cref="Toplevel"/> view.</summary>
+    /// <remarks>
+    ///     <para>
+    ///         This method is used to start processing events for the main application, but it is also used to run other
+    ///         modal <see cref="View"/>s such as <see cref="Dialog"/> boxes.
+    ///     </para>
+    ///     <para>
+    ///         To make a <see cref="Run(Terminal.Gui.Toplevel,System.Func{System.Exception,bool})"/> stop execution, call
+    ///         <see cref="Application.RequestStop"/>.
+    ///     </para>
+    ///     <para>
+    ///         Calling <see cref="Run(Terminal.Gui.Toplevel,System.Func{System.Exception,bool})"/> is equivalent to calling
+    ///         <see cref="Begin(Toplevel)"/>, followed by <see cref="RunLoop(RunState)"/>, and then calling
+    ///         <see cref="End(RunState)"/>.
+    ///     </para>
+    ///     <para>
+    ///         Alternatively, to have a program control the main loop and process events manually, call
+    ///         <see cref="Begin(Toplevel)"/> to set things up manually and then repeatedly call
+    ///         <see cref="RunLoop(RunState)"/> with the wait parameter set to false. By doing this the
+    ///         <see cref="RunLoop(RunState)"/> method will only process any pending events, timers, idle handlers and then
+    ///         return control immediately.
+    ///     </para>
+    ///     <para>When using <see cref="Run{T}"/> or
+    ///         <see cref="Run(System.Func{System.Exception,bool},Terminal.Gui.IConsoleDriver)"/>
+    ///         <see cref="Init"/> will be called automatically.
+    ///     </para>
+    ///     <para>
+    ///         RELEASE builds only: When <paramref name="errorHandler"/> is <see langword="null"/> any exceptions will be
+    ///         rethrown. Otherwise, if <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/>
+    ///         returns <see langword="true"/> the <see cref="RunLoop(RunState)"/> will resume; otherwise this method will
+    ///         exit.
+    ///     </para>
+    /// </remarks>
+    /// <param name="view">The <see cref="Toplevel"/> to run as a modal.</param>
+    /// <param name="errorHandler">
+    ///     RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true,
+    ///     rethrows when null).
+    /// </param>
+    public void Run (Toplevel view, Func<Exception, bool>? errorHandler = null);
+
+    /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
+    /// <remarks>
+    ///     Shutdown must be called for every call to <see cref="Init"/> or
+    ///     <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to ensure all resources are cleaned
+    ///     up (Disposed)
+    ///     and terminal settings are restored.
+    /// </remarks>
+    public void Shutdown ();
+}
\ No newline at end of file

From cd65e0dcabd32285e3bc7afc83ed1a59c69fcddf Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Tue, 3 Dec 2024 20:01:15 +0000
Subject: [PATCH 038/198] Add ChangeInstance method

---
 Terminal.Gui/Application/ApplicationImpl.cs | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/Terminal.Gui/Application/ApplicationImpl.cs b/Terminal.Gui/Application/ApplicationImpl.cs
index ee4da05737..0e26bd0a50 100644
--- a/Terminal.Gui/Application/ApplicationImpl.cs
+++ b/Terminal.Gui/Application/ApplicationImpl.cs
@@ -6,11 +6,21 @@ namespace Terminal.Gui;
 public class ApplicationImpl : IApplication
 {
     // Private static readonly Lazy instance of Application
-    private static readonly Lazy<IApplication> lazyInstance = new (() => new ApplicationImpl ());
+    private static Lazy<IApplication> lazyInstance = new (() => new ApplicationImpl ());
 
     // Public static property to access the instance
     public static IApplication Instance => lazyInstance.Value;
 
+    /// <summary>
+    /// Change the singleton implementation, should not be called except before application
+    /// startup.
+    /// </summary>
+    /// <param name="newApplication"></param>
+    public static void ChangeInstance (IApplication newApplication)
+    {
+        lazyInstance = new Lazy<IApplication> (newApplication);
+    }
+
     /// <inheritdoc/>
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]

From b504e0fc6e93c572b455e147dd1e0c2bd412b928 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Tue, 3 Dec 2024 20:12:46 +0000
Subject: [PATCH 039/198] WIP: how to create a v2 application

---
 .../ConsoleDrivers/V2/ApplicationV2.cs        | 29 +++++++++++++++++++
 UICatalog/Properties/launchSettings.json      |  4 +++
 UICatalog/UICatalog.cs                        |  7 +++++
 3 files changed, 40 insertions(+)
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
new file mode 100644
index 0000000000..d5b6c2d9b9
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Terminal.Gui.ConsoleDrivers.V2;
+
+
+public class ApplicationV2 : IApplication
+{
+    /// <inheritdoc />
+    public void Init (IConsoleDriver driver = null, string driverName = null)
+    {
+
+    }
+
+    /// <inheritdoc />
+    public Toplevel Run (Func<Exception, bool> errorHandler = null, IConsoleDriver driver = null) { throw new NotImplementedException (); }
+
+    /// <inheritdoc />
+    public T Run<T> (Func<Exception, bool> errorHandler = null, IConsoleDriver driver = null) where T : Toplevel, new () { throw new NotImplementedException (); }
+
+    /// <inheritdoc />
+    public void Run (Toplevel view, Func<Exception, bool> errorHandler = null) { throw new NotImplementedException (); }
+
+    /// <inheritdoc />
+    public void Shutdown () { throw new NotImplementedException (); }
+}
diff --git a/UICatalog/Properties/launchSettings.json b/UICatalog/Properties/launchSettings.json
index 7ed43499ad..cb9692c474 100644
--- a/UICatalog/Properties/launchSettings.json
+++ b/UICatalog/Properties/launchSettings.json
@@ -11,6 +11,10 @@
       "commandName": "Project",
       "commandLineArgs": "--driver WindowsDriver"
     },
+    "UICatalog --driver v2": {
+      "commandName": "Project",
+      "commandLineArgs": "--driver v2"
+    },
     "WSL: UICatalog": {
       "commandName": "Executable",
       "executablePath": "wsl",
diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs
index fffa989f24..811b8620a8 100644
--- a/UICatalog/UICatalog.cs
+++ b/UICatalog/UICatalog.cs
@@ -21,6 +21,7 @@
 using System.Threading;
 using System.Threading.Tasks;
 using Terminal.Gui;
+using Terminal.Gui.ConsoleDrivers.V2;
 using UICatalog.Scenarios;
 using static Terminal.Gui.ConfigurationManager;
 using Command = Terminal.Gui.Command;
@@ -328,6 +329,12 @@ private static void UICatalogMain (Options options)
     {
         StartConfigFileWatcher ();
 
+        if (options.Driver == "v2")
+        {
+            ApplicationImpl.ChangeInstance (new ApplicationV2 ());
+            options.Driver = string.Empty;
+        }
+
         // By setting _forceDriver we ensure that if the user has specified a driver on the command line, it will be used
         // regardless of what's in a config file.
         Application.ForceDriver = _forceDriver = options.Driver;

From 280f2882b67adab0c18938ebcd63b82735bdc51c Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Fri, 6 Dec 2024 20:06:08 +0000
Subject: [PATCH 040/198] WIP net driver working for Top (but not subviews)

---
 .../ConsoleDrivers/V2/ApplicationV2.cs        | 117 ++++++++++++++++--
 .../ConsoleDrivers/V2/IMainLoopCoordinator.cs |  15 +++
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  |  14 +--
 .../ConsoleDrivers/V2/WindowsOutput.cs        |   8 +-
 .../WindowsDriver/WindowsConsole.cs           |   6 +-
 Terminal.Gui/View/View.Drawing.cs             |   3 +-
 6 files changed, 139 insertions(+), 24 deletions(-)
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index d5b6c2d9b9..17276e5c84 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -1,29 +1,128 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using static Terminal.Gui.WindowsConsole;
 
 namespace Terminal.Gui.ConsoleDrivers.V2;
 
 
 public class ApplicationV2 : IApplication
 {
+    private IMainLoopCoordinator _coordinator;
+
     /// <inheritdoc />
     public void Init (IConsoleDriver driver = null, string driverName = null)
     {
+        Application.Navigation = new ();
+
+        Application.AddApplicationKeyBindings ();
 
+        CreateDriver ();
+
+        Application.Initialized = true;
+    }
+
+    private void CreateDriver ()
+    {
+
+        PlatformID p = Environment.OSVersion.Platform;
+
+        /*if ( p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
+        {
+            var inputBuffer = new ConcurrentQueue<InputRecord> ();
+            var loop = new MainLoop<InputRecord> ();
+            _coordinator = new MainLoopCoordinator<InputRecord> (
+                                                                () => new WindowsInput (),
+                                                                inputBuffer,
+                                                                new WindowsInputProcessor (inputBuffer),
+                                                                () => new WindowsOutput (),
+                                                                loop);
+        }
+        else
+        {*/
+            var inputBuffer = new ConcurrentQueue<ConsoleKeyInfo> ();
+            var loop = new MainLoop<ConsoleKeyInfo> ();
+            _coordinator = new MainLoopCoordinator<ConsoleKeyInfo> (() => new NetInput (),
+                                                                   inputBuffer,
+                                                                   new NetInputProcessor (inputBuffer),
+                                                                   () => new NetOutput (),
+                                                                   loop);
+        //}
+
+        _coordinator.StartAsync ();
+
+        if (!_coordinator.StartupSemaphore.WaitAsync (TimeSpan.FromSeconds (3)).Result)
+        {
+            throw new Exception ("Failed to boot MainLoopCoordinator in sensible timeframe");
+        }
+
+        if (Application.Driver == null)
+        {
+            throw new Exception ("Application.Driver was null even after booting MainLoopCoordinator");
+        }
     }
 
     /// <inheritdoc />
-    public Toplevel Run (Func<Exception, bool> errorHandler = null, IConsoleDriver driver = null) { throw new NotImplementedException (); }
+    public Toplevel Run (Func<Exception, bool> errorHandler = null, IConsoleDriver driver = null)
+    {
+        return Run<Toplevel> (errorHandler, driver);
+    }
 
     /// <inheritdoc />
-    public T Run<T> (Func<Exception, bool> errorHandler = null, IConsoleDriver driver = null) where T : Toplevel, new () { throw new NotImplementedException (); }
+    public T Run<T> (Func<Exception, bool> errorHandler = null, IConsoleDriver driver = null) where T : Toplevel, new ()
+    {
+        var top = new T ();
+
+        Run (top, errorHandler);
+
+        return top;
+    }
 
     /// <inheritdoc />
-    public void Run (Toplevel view, Func<Exception, bool> errorHandler = null) { throw new NotImplementedException (); }
+    public void Run (Toplevel view, Func<Exception, bool> errorHandler = null)
+    {
+        ArgumentNullException.ThrowIfNull (view);
+
+
+        if (!Application.Initialized)
+        {
+            throw new Exception ("App not Initialized");
+        }
+
+        var resume = true;
+
+        while (resume)
+        {
+            try
+            { 
+                resume = false;
+                RunState runState = Application.Begin (view);
+
+                // TODO : how to know when we are done?
+                while (runState.Toplevel is { })
+                {
+                    Task.Delay (100).Wait ();
+                }
+
+                runState.Dispose ();
+                Application.End (runState);
+            
+            }
+            catch (Exception error)
+            {
+                if (errorHandler is null)
+                {
+                    throw;
+                }
+
+                resume = errorHandler (error);
+            }
+        }
+
+    }
 
     /// <inheritdoc />
-    public void Shutdown () { throw new NotImplementedException (); }
+    public void Shutdown ()
+    {
+        _coordinator.Stop ();
+    }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
new file mode 100644
index 0000000000..07860b92ed
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
@@ -0,0 +1,15 @@
+namespace Terminal.Gui;
+
+public interface IMainLoopCoordinator
+{
+    /// <summary>
+    /// Can be waited on after calling <see cref="StartAsync"/> to know when
+    /// boot up threads are running. Do not wait unless you constructed the coordinator.
+    /// Do not wait multiple times on the same coordinator.
+    /// </summary>
+    SemaphoreSlim StartupSemaphore { get; }
+    public void StartAsync ();
+    public void StartBlocking ();
+
+    public void Stop ();
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index e7fd334a22..4eb6b54c0a 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -16,6 +16,8 @@ public class MainLoopCoordinator<T> : IMainLoopCoordinator
     object oLockInitialization = new ();
     private ConsoleDriverFacade<T> _facade;
 
+    public SemaphoreSlim StartupSemaphore { get; } = new (0, 1);
+
     /// <summary>
     /// Creates a new coordinator
     /// </summary>
@@ -111,6 +113,8 @@ private void BuildFacadeIfPossible ()
                                              {
                                                  e.View?.NewMouseEvent (e);
                                              };
+
+            StartupSemaphore.Release ();
         }
     }
 
@@ -118,12 +122,4 @@ public void Stop ()
     {
         tokenSource.Cancel();
     }
-}
-
-public interface IMainLoopCoordinator
-{
-    public void StartAsync ();
-    public void StartBlocking ();
-
-    public void Stop ();
-}
+}
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
index 30fc4f9d67..c82471f819 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
@@ -163,9 +163,13 @@ public void Write (IOutputBuffer buffer)
             Bottom = (short)buffer.Rows,
             Right = (short)buffer.Cols
         };
-
+        //size, ExtendedCharInfo [] charInfoBuffer, Coord , SmallRect window,
         if (WinConsole != null
-            && !WinConsole.WriteToConsole (new (buffer.Cols, buffer.Rows), outputBuffer, bufferCoords, damageRegion, false))
+            && !WinConsole.WriteToConsole (
+                                           size: new (buffer.Cols, buffer.Rows),
+                                           charInfoBuffer: outputBuffer,
+                                           bufferSize: bufferCoords,
+                                           window: damageRegion, false))
         {
             int err = Marshal.GetLastWin32Error ();
 
diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
index c782d30694..020652c4af 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
@@ -211,11 +211,11 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord
 
             // TODO: requires extensive testing if we go down this route
             // If console output has changed
-            if (s != _lastWrite)
-            {
+ //           if (s != _lastWrite)
+       //     {
                 // supply console with the new content
                 result = WriteConsole (_outputHandle, s, (uint)s.Length, out uint _, nint.Zero);
-            }
+ //           }
 
             _lastWrite = s;
 
diff --git a/Terminal.Gui/View/View.Drawing.cs b/Terminal.Gui/View/View.Drawing.cs
index adea35c36a..0967fb7cc9 100644
--- a/Terminal.Gui/View/View.Drawing.cs
+++ b/Terminal.Gui/View/View.Drawing.cs
@@ -742,7 +742,8 @@ public void SetNeedsDraw (Rectangle viewPortRelativeRegion)
             adornment.Parent?.SetSubViewNeedsDraw ();
         }
 
-        foreach (View subview in Subviews)
+        // There was multiple enumeration error here, so calling ToArray - probably a stop gap
+        foreach (View subview in Subviews.ToArray ())
         {
             if (subview.Frame.IntersectsWith (viewPortRelativeRegion))
             {

From d418847d62bb2a725bf73a2c5d19828002db5cdc Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Fri, 6 Dec 2024 20:40:10 +0000
Subject: [PATCH 041/198] Add RequestStop to IApplication

---
 Terminal.Gui/Application/Application.Run.cs   | 27 +------------
 Terminal.Gui/Application/ApplicationImpl.cs   | 22 +++++++++++
 Terminal.Gui/Application/IApplication.cs      | 11 ++++++
 .../ConsoleDrivers/V2/ApplicationV2.cs        | 39 ++++++-------------
 4 files changed, 46 insertions(+), 53 deletions(-)

diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs
index 26d59c1deb..651b4255c2 100644
--- a/Terminal.Gui/Application/Application.Run.cs
+++ b/Terminal.Gui/Application/Application.Run.cs
@@ -518,31 +518,8 @@ public static bool RunIteration (ref RunState state, bool firstIteration = false
     ///         property on the currently running <see cref="Toplevel"/> to false.
     ///     </para>
     /// </remarks>
-    public static void RequestStop (Toplevel? top = null)
-    {
-        if (top is null)
-        {
-            top = Top;
-        }
-
-        if (!top!.Running)
-        {
-            return;
-        }
-
-        var ev = new ToplevelClosingEventArgs (top);
-        top.OnClosing (ev);
-
-        if (ev.Cancel)
-        {
-            return;
-        }
-
-        top.Running = false;
-        OnNotifyStopRunState (top);
-    }
-
-    private static void OnNotifyStopRunState (Toplevel top)
+    public static void RequestStop (Toplevel? top = null) => ApplicationImpl.Instance.RequestStop (top);
+    internal static void OnNotifyStopRunState (Toplevel top)
     {
         if (EndAfterFirstIteration)
         {
diff --git a/Terminal.Gui/Application/ApplicationImpl.cs b/Terminal.Gui/Application/ApplicationImpl.cs
index 0e26bd0a50..e3586ae468 100644
--- a/Terminal.Gui/Application/ApplicationImpl.cs
+++ b/Terminal.Gui/Application/ApplicationImpl.cs
@@ -216,4 +216,26 @@ public void Shutdown ()
             Application.OnInitializedChanged(this, new (in init));
         }
     }
+
+    /// <inheritdoc />
+    public void RequestStop (Toplevel top)
+    {
+        top ??= Application.Top;
+
+        if (!top!.Running)
+        {
+            return;
+        }
+
+        var ev = new ToplevelClosingEventArgs (top);
+        top.OnClosing (ev);
+
+        if (ev.Cancel)
+        {
+            return;
+        }
+
+        top.Running = false;
+        Application.OnNotifyStopRunState (top);
+    }
 }
diff --git a/Terminal.Gui/Application/IApplication.cs b/Terminal.Gui/Application/IApplication.cs
index d54e0fccd9..100aaa59ee 100644
--- a/Terminal.Gui/Application/IApplication.cs
+++ b/Terminal.Gui/Application/IApplication.cs
@@ -135,4 +135,15 @@ public T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? dri
     ///     and terminal settings are restored.
     /// </remarks>
     public void Shutdown ();
+
+    /// <summary>Stops the provided <see cref="Toplevel"/>, causing or the <paramref name="top"/> if provided.</summary>
+    /// <param name="top">The <see cref="Toplevel"/> to stop.</param>
+    /// <remarks>
+    ///     <para>This will cause <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to return.</para>
+    ///     <para>
+    ///         Calling <see cref="RequestStop(Terminal.Gui.Toplevel)"/> is equivalent to setting the <see cref="Toplevel.Running"/>
+    ///         property on the currently running <see cref="Toplevel"/> to false.
+    ///     </para>
+    /// </remarks>
+    void RequestStop (Toplevel top);
 }
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index 17276e5c84..2409f23128 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -82,42 +82,19 @@ public void Run (Toplevel view, Func<Exception, bool> errorHandler = null)
     {
         ArgumentNullException.ThrowIfNull (view);
 
-
         if (!Application.Initialized)
         {
             throw new Exception ("App not Initialized");
         }
 
-        var resume = true;
+        Application.Top = view;
 
-        while (resume)
+        Application.Begin (view);
+        // TODO : how to know when we are done?
+        while (Application.Top != null)
         {
-            try
-            { 
-                resume = false;
-                RunState runState = Application.Begin (view);
-
-                // TODO : how to know when we are done?
-                while (runState.Toplevel is { })
-                {
-                    Task.Delay (100).Wait ();
-                }
-
-                runState.Dispose ();
-                Application.End (runState);
-            
-            }
-            catch (Exception error)
-            {
-                if (errorHandler is null)
-                {
-                    throw;
-                }
-
-                resume = errorHandler (error);
-            }
+            Task.Delay (100).Wait ();
         }
-
     }
 
     /// <inheritdoc />
@@ -125,4 +102,10 @@ public void Shutdown ()
     {
         _coordinator.Stop ();
     }
+
+    /// <inheritdoc />
+    public void RequestStop (Toplevel top)
+    {
+        Application.Top = null;
+    }
 }

From 4fe25e3db4f138aca51955c90c33b0a6dabd43e3 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 7 Dec 2024 09:50:02 +0000
Subject: [PATCH 042/198] Add IsLegacy field to IApplication so we can turn off
 janky code in a more modular way

---
 Drivers2/Drivers2.csproj                      | 14 ---
 Drivers2/Program.cs                           | 97 -------------------
 Drivers2/Properties/launchSettings.json       |  7 --
 Terminal.Gui/Application/Application.Run.cs   | 12 +--
 Terminal.Gui/Application/Application.cs       |  2 +
 Terminal.Gui/Application/ApplicationImpl.cs   | 16 +++
 Terminal.Gui/Application/IApplication.cs      |  6 ++
 .../ConsoleDrivers/V2/ApplicationV2.cs        | 11 ++-
 Terminal.Gui/View/View.cs                     |  7 +-
 Terminal.sln                                  |  6 --
 10 files changed, 39 insertions(+), 139 deletions(-)
 delete mode 100644 Drivers2/Drivers2.csproj
 delete mode 100644 Drivers2/Program.cs
 delete mode 100644 Drivers2/Properties/launchSettings.json

diff --git a/Drivers2/Drivers2.csproj b/Drivers2/Drivers2.csproj
deleted file mode 100644
index d36e1d7ac0..0000000000
--- a/Drivers2/Drivers2.csproj
+++ /dev/null
@@ -1,14 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-  <PropertyGroup>
-    <OutputType>Exe</OutputType>
-    <TargetFramework>net8.0</TargetFramework>
-    <ImplicitUsings>enable</ImplicitUsings>
-    <Nullable>enable</Nullable>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
-  </ItemGroup>
-
-</Project>
diff --git a/Drivers2/Program.cs b/Drivers2/Program.cs
deleted file mode 100644
index 2699ed897f..0000000000
--- a/Drivers2/Program.cs
+++ /dev/null
@@ -1,97 +0,0 @@
-using System.Collections.Concurrent;
-using Terminal.Gui;
-using static Terminal.Gui.WindowsConsole;
-
-namespace Drivers2;
-
-class Program
-{
-    static void Main (string [] args)
-    {
-        bool win = false;
-
-        if (args.Length > 0)
-        {
-            if (args [0] == "net")
-            {
-                // default
-            }
-            else if(args [0] == "win")
-            {
-                win = true;
-            }
-            else
-            {
-                Console.WriteLine("Arg must be 'win' or 'net' or blank to use default");
-            }
-        }
-
-        // Required to set up colors etc?
-        Application.Init ();
-
-        var top = CreateTestWindow ();
-
-        IMainLoopCoordinator coordinator;
-        if (win)
-        {
-            // TODO: We will need a nice factory for this constructor, it's getting a bit epic
-
-            var inputBuffer = new ConcurrentQueue<InputRecord> ();
-            var loop = new MainLoop<InputRecord> ();
-            coordinator = new MainLoopCoordinator<InputRecord> (
-                                                                ()=>new WindowsInput (),
-                                                                inputBuffer,
-                                                                new WindowsInputProcessor (inputBuffer),
-                                                                ()=>new WindowsOutput (),
-                                                                loop);
-        }
-        else
-        {
-            var inputBuffer = new ConcurrentQueue<ConsoleKeyInfo> ();
-            var loop = new MainLoop<ConsoleKeyInfo> ();
-            coordinator = new MainLoopCoordinator<ConsoleKeyInfo> (()=>new NetInput (),
-                                                                   inputBuffer,
-                                                                   new NetInputProcessor (inputBuffer),
-                                                                   ()=>new NetOutput (),
-                                                                   loop);
-        }
-
-        // Register the event handler for Ctrl+C
-        Console.CancelKeyPress += (s,e)=>
-                                  {
-                                      e.Cancel = true;
-                                      coordinator.Stop ();
-                                  };
-
-        BeginTestWindow (top);
-
-
-
-        coordinator.StartBlocking ();
-    }
-
-    private static void BeginTestWindow (Toplevel top)
-    {
-        Application.Top = top;
-    }
-
-    private static Toplevel CreateTestWindow ()
-    {
-        var w = new Window
-        {
-            Title = "Hello World",
-            Width = 30,
-            Height = 5
-        };
-
-        var tf = new TextField { X = 5, Y = 0, Width = 10 };
-        w.AdvanceFocus (NavigationDirection.Forward, null);
-        w.Add (tf);
-
-        var tf2 = new TextField { X = 5, Y = 2, Width = 10 };
-        w.AdvanceFocus (NavigationDirection.Forward, null);
-        w.Add (tf2);
-
-        return w;
-    }
-}
diff --git a/Drivers2/Properties/launchSettings.json b/Drivers2/Properties/launchSettings.json
deleted file mode 100644
index c8c054cdba..0000000000
--- a/Drivers2/Properties/launchSettings.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "profiles": {
-    "Drivers2": {
-      "commandName": "Project"
-    }
-  }
-}
\ No newline at end of file
diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs
index 651b4255c2..e3db07a273 100644
--- a/Terminal.Gui/Application/Application.Run.cs
+++ b/Terminal.Gui/Application/Application.Run.cs
@@ -385,17 +385,7 @@ public static void Run (Toplevel view, Func<Exception, bool>? errorHandler = nul
 
     /// <summary>Runs <paramref name="action"/> on the thread that is processing events</summary>
     /// <param name="action">the action to be invoked on the main processing thread.</param>
-    public static void Invoke (Action action)
-    {
-        MainLoop?.AddIdle (
-                           () =>
-                           {
-                               action ();
-
-                               return false;
-                           }
-                          );
-    }
+    public static void Invoke (Action action) => ApplicationImpl.Instance.Invoke (action);
 
     // TODO: Determine if this is really needed. The only code that calls WakeUp I can find
     // is ProgressBarStyles, and it's not clear it needs to.
diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs
index 7c0ff55296..1d49d5cbd0 100644
--- a/Terminal.Gui/Application/Application.cs
+++ b/Terminal.Gui/Application/Application.cs
@@ -23,6 +23,8 @@ namespace Terminal.Gui;
 /// <remarks></remarks>
 public static partial class Application
 {
+    internal static bool IsLegacy => ApplicationImpl.Instance.IsLegacy;
+
     /// <summary>Gets all cultures supported by the application without the invariant language.</summary>
     public static List<CultureInfo>? SupportedCultures { get; private set; }
 
diff --git a/Terminal.Gui/Application/ApplicationImpl.cs b/Terminal.Gui/Application/ApplicationImpl.cs
index e3586ae468..5c2cc99f3d 100644
--- a/Terminal.Gui/Application/ApplicationImpl.cs
+++ b/Terminal.Gui/Application/ApplicationImpl.cs
@@ -238,4 +238,20 @@ public void RequestStop (Toplevel top)
         top.Running = false;
         Application.OnNotifyStopRunState (top);
     }
+
+    /// <inheritdoc />
+    public void Invoke (Action action)
+    {
+        Application.MainLoop?.AddIdle (
+                           () =>
+                           {
+                               action ();
+
+                               return false;
+                           }
+                          );
+    }
+
+    /// <inheritdoc />
+    public bool IsLegacy => true;
 }
diff --git a/Terminal.Gui/Application/IApplication.cs b/Terminal.Gui/Application/IApplication.cs
index 100aaa59ee..0c33c35c09 100644
--- a/Terminal.Gui/Application/IApplication.cs
+++ b/Terminal.Gui/Application/IApplication.cs
@@ -146,4 +146,10 @@ public T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? dri
     ///     </para>
     /// </remarks>
     void RequestStop (Toplevel top);
+
+    /// <summary>Runs <paramref name="action"/> on the main UI loop thread</summary>
+    /// <param name="action">the action to be invoked on the main processing thread.</param>
+    void Invoke (Action action);
+
+    bool IsLegacy { get; }
 }
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index 2409f23128..5a0a737ec4 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -1,6 +1,4 @@
 using System.Collections.Concurrent;
-using System.Diagnostics;
-using static Terminal.Gui.WindowsConsole;
 
 namespace Terminal.Gui.ConsoleDrivers.V2;
 
@@ -108,4 +106,13 @@ public void RequestStop (Toplevel top)
     {
         Application.Top = null;
     }
+
+    /// <inheritdoc />
+    public void Invoke (Action action)
+    {
+        // TODO
+    }
+
+    /// <inheritdoc />
+    public bool IsLegacy => false;
 }
diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs
index 46689dba01..6dc893bd85 100644
--- a/Terminal.Gui/View/View.cs
+++ b/Terminal.Gui/View/View.cs
@@ -250,8 +250,11 @@ public virtual void EndInit ()
             }
         }
 
-        // TODO: Figure out how to move this out of here and just depend on LayoutNeeded in Mainloop
-        Layout (); // the EventLog in AllViewsTester fails to layout correctly if this is not here (convoluted Dim.Fill(Func)).
+        if (ApplicationImpl.Instance.IsLegacy)
+        {
+            // TODO: Figure out how to move this out of here and just depend on LayoutNeeded in Mainloop
+            Layout (); // the EventLog in AllViewsTester fails to layout correctly if this is not here (convoluted Dim.Fill(Func)).
+        }
         SetNeedsLayout ();
 
         Initialized?.Invoke (this, EventArgs.Empty);
diff --git a/Terminal.sln b/Terminal.sln
index 405d3a507b..5f2eda9e8a 100644
--- a/Terminal.sln
+++ b/Terminal.sln
@@ -48,8 +48,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SelfContained", "SelfContai
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeAot", "NativeAot\NativeAot.csproj", "{E6D716C6-AC94-4150-B10A-44AE13F79344}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Drivers2", "Drivers2\Drivers2.csproj", "{3221C618-E35C-4F38-A90F-A220CD4DD8B0}"
-EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -88,10 +86,6 @@ Global
 		{E6D716C6-AC94-4150-B10A-44AE13F79344}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{E6D716C6-AC94-4150-B10A-44AE13F79344}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{E6D716C6-AC94-4150-B10A-44AE13F79344}.Release|Any CPU.Build.0 = Release|Any CPU
-		{3221C618-E35C-4F38-A90F-A220CD4DD8B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{3221C618-E35C-4F38-A90F-A220CD4DD8B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{3221C618-E35C-4F38-A90F-A220CD4DD8B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{3221C618-E35C-4F38-A90F-A220CD4DD8B0}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

From 3a09c2fccb14336c409745b07fa5cda285ea666e Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 7 Dec 2024 11:15:51 +0000
Subject: [PATCH 043/198] WIP trying to get exit to work

---
 Terminal.Gui/Application/Application.cs       |  1 +
 Terminal.Gui/Application/ApplicationImpl.cs   |  7 ++++
 Terminal.Gui/Application/IApplication.cs      |  1 +
 .../ConsoleDrivers/V2/ApplicationV2.cs        | 37 ++++++++++++++++++-
 .../ConsoleDrivers/V2/ConsoleDriverFacade.cs  |  1 -
 .../ConsoleDrivers/V2/IMainLoopCoordinator.cs |  1 -
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  | 21 +++++------
 Terminal.Gui/Views/Menu/MenuBar.cs            |  2 +-
 8 files changed, 55 insertions(+), 16 deletions(-)

diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs
index 1d49d5cbd0..2d13a53619 100644
--- a/Terminal.Gui/Application/Application.cs
+++ b/Terminal.Gui/Application/Application.cs
@@ -226,4 +226,5 @@ internal static void ResetState (bool ignoreDisposed = false)
     }
 
     // Only return true if the Current has changed.
+    public static void AddIdle (Func<bool> func) => ApplicationImpl.Instance.AddIdle (func);
 }
diff --git a/Terminal.Gui/Application/ApplicationImpl.cs b/Terminal.Gui/Application/ApplicationImpl.cs
index 5c2cc99f3d..92cbdfdd1a 100644
--- a/Terminal.Gui/Application/ApplicationImpl.cs
+++ b/Terminal.Gui/Application/ApplicationImpl.cs
@@ -254,4 +254,11 @@ public void Invoke (Action action)
 
     /// <inheritdoc />
     public bool IsLegacy => true;
+
+    /// <inheritdoc />
+    public void AddIdle (Func<bool> func)
+    {
+        Application.MainLoop.AddIdle (func);
+
+    }
 }
diff --git a/Terminal.Gui/Application/IApplication.cs b/Terminal.Gui/Application/IApplication.cs
index 0c33c35c09..e195f20487 100644
--- a/Terminal.Gui/Application/IApplication.cs
+++ b/Terminal.Gui/Application/IApplication.cs
@@ -152,4 +152,5 @@ public T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? dri
     void Invoke (Action action);
 
     bool IsLegacy { get; }
+    void AddIdle (Func<bool> func);
 }
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index 5a0a737ec4..c30d0b2a0f 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -99,12 +99,39 @@ public void Run (Toplevel view, Func<Exception, bool> errorHandler = null)
     public void Shutdown ()
     {
         _coordinator.Stop ();
+
+        bool wasInitialized = Application.Initialized;
+        Application.ResetState ();
+        PrintJsonErrors ();
+
+        if (wasInitialized)
+        {
+            bool init = Application.Initialized;
+
+            Application.OnInitializedChanged (this, new (in init));
+        }
     }
 
     /// <inheritdoc />
     public void RequestStop (Toplevel top)
     {
-        Application.Top = null;
+        top ??= Application.Top;
+
+        if (top == null)
+        {
+            return;
+        }
+
+        var ev = new ToplevelClosingEventArgs (top);
+        top.OnClosing (ev);
+
+        if (ev.Cancel)
+        {
+            return;
+        }
+
+        top.Running = false;
+        Application.OnNotifyStopRunState (top);
     }
 
     /// <inheritdoc />
@@ -115,4 +142,12 @@ public void Invoke (Action action)
 
     /// <inheritdoc />
     public bool IsLegacy => false;
+
+    /// <inheritdoc />
+    public void AddIdle (Func<bool> func)
+    {
+        // TODO properly
+
+        func.Invoke ();
+    }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
index 80a7dd0628..ff37f010f4 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
@@ -334,5 +334,4 @@ public void Refresh ()
     {
         // No need we will always draw when dirty
     }
-
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
index 07860b92ed..a9210fa00b 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
@@ -9,7 +9,6 @@ public interface IMainLoopCoordinator
     /// </summary>
     SemaphoreSlim StartupSemaphore { get; }
     public void StartAsync ();
-    public void StartBlocking ();
 
     public void Stop ();
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index 4eb6b54c0a..7319019633 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -15,9 +15,12 @@ public class MainLoopCoordinator<T> : IMainLoopCoordinator
     private IConsoleOutput _output;
     object oLockInitialization = new ();
     private ConsoleDriverFacade<T> _facade;
+    private Task _inputTask;
+    private Task _loopTask;
 
     public SemaphoreSlim StartupSemaphore { get; } = new (0, 1);
 
+
     /// <summary>
     /// Creates a new coordinator
     /// </summary>
@@ -44,18 +47,10 @@ public MainLoopCoordinator (Func<IConsoleInput<T>> inputFactory, ConcurrentQueue
     /// </summary>
     public void StartAsync ()
     {
-        Task.Run (RunInput);
-        Task.Run (RunLoop);
-    }
-    /// <summary>
-    /// Starts the input thread and then enters the main loop in the current thread
-    /// (method only exits when application ends).
-    /// </summary>
-    public void StartBlocking ()
-    {
-        Task.Run (RunInput);
-        RunLoop();
+        _inputTask = Task.Run (RunInput);
+        _loopTask = Task.Run (RunLoop);
     }
+
     private void RunInput ()
     {
         lock (oLockInitialization)
@@ -79,7 +74,6 @@ private void RunInput ()
 
     private void RunLoop ()
     {
-
         lock (oLockInitialization)
         {
             // Instance must be constructed on the thread in which it is used.
@@ -121,5 +115,8 @@ private void BuildFacadeIfPossible ()
     public void Stop ()
     {
         tokenSource.Cancel();
+
+        // Wait for both tasks to complete
+        Task.WhenAll (_inputTask, _loopTask).Wait ();
     }
 }
\ No newline at end of file
diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs
index 21c1a5b9f4..a8b413ebcb 100644
--- a/Terminal.Gui/Views/Menu/MenuBar.cs
+++ b/Terminal.Gui/Views/Menu/MenuBar.cs
@@ -1023,7 +1023,7 @@ internal bool Run (Action? action)
             return false;
         }
 
-        Application.MainLoop!.AddIdle (
+        Application.AddIdle (
                                        () =>
                                        {
                                            action ();

From 9744041624868847fd396859ef99de53891291ec Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 7 Dec 2024 11:57:40 +0000
Subject: [PATCH 044/198] Update class diagram

---
 Terminal.Gui/ConsoleDrivers/V2/V2.cd | 174 ++++++++++++++++++---------
 1 file changed, 117 insertions(+), 57 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index a7bcfd779e..be9a21714b 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -1,39 +1,48 @@
 <?xml version="1.0" encoding="utf-8"?>
 <ClassDiagram MajorVersion="1" MinorVersion="1">
   <Comment CommentText="Thread 1 - Input thread, populates input buffer.  This thread is hidden, nobody gets to interact directly with these classes)">
-    <Position X="10" Y="0.5" Height="0.5" Width="5.325" />
+    <Position X="11" Y="0.5" Height="0.5" Width="5.325" />
   </Comment>
   <Comment CommentText="Thread 2 - Main Loop which does everything else including output.  Deals with input exclusively through the input buffer. Is accessible externally e.g. to Application">
-    <Position X="10.083" Y="3.813" Height="0.479" Width="5.325" />
+    <Position X="11.083" Y="3.813" Height="0.479" Width="5.325" />
   </Comment>
   <Comment CommentText="Orchestrates the 2 main threads in Terminal.Gui">
-    <Position X="5.5" Y="1.25" Height="0.291" Width="2.929" />
+    <Position X="6.5" Y="1.25" Height="0.291" Width="2.929" />
   </Comment>
   <Comment CommentText="Allows Views to work with new architecture without having to be rewritten.">
-    <Position X="7.604" Y="6.771" Height="0.75" Width="1.7" />
+    <Position X="8.604" Y="6.771" Height="0.75" Width="1.7" />
   </Comment>
   <Comment CommentText="Ansi Escape Sequence - Request / Response">
-    <Position X="18.458" Y="3.562" Height="0.396" Width="2.825" />
+    <Position X="19.458" Y="3.562" Height="0.396" Width="2.825" />
   </Comment>
   <Comment CommentText="Mouse interpretation subsystem">
-    <Position X="12.625" Y="9.833" Height="0.396" Width="2.825" />
+    <Position X="13.625" Y="9.833" Height="0.396" Width="2.825" />
+  </Comment>
+  <Comment CommentText="In Terminal.Gui views get things done almost exclusively by calling static methods on Application e.g. RequestStop, Run, Refresh etc">
+    <Position X="0.5" Y="3.75" Height="1.146" Width="1.7" />
+  </Comment>
+  <Comment CommentText="Static record of system state and static gateway API for everything you ever need.">
+    <Position X="0.5" Y="1.417" Height="0.875" Width="1.7" />
+  </Comment>
+  <Comment CommentText="Forwarded subset of gateway functionality. These exist to allow ''subclassing' Application.  Note that most methods 'ping pong' a lot back to main gateway submethods e.g. to manipulate TopLevel etc">
+    <Position X="3.083" Y="5.292" Height="1.063" Width="2.992" />
   </Comment>
   <Class Name="Terminal.Gui.WindowsInput" Collapsed="true">
-    <Position X="10.5" Y="3" Width="1.75" />
+    <Position X="11.5" Y="3" Width="1.75" />
     <TypeIdentifier>
       <HashCode>QAAAAAAAACAEAAAAAAAAAAAkAAAAAAAAAgAAAAAAABA=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsInput.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.NetInput" Collapsed="true">
-    <Position X="12.25" Y="3" Width="2" />
+    <Position X="13.25" Y="3" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAACAEAAAAQAAAAAAgAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\NetInput.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.ConsoleInput&lt;T&gt;" Collapsed="true">
-    <Position X="11.5" Y="2" Width="1.5" />
+    <Position X="12.5" Y="2" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAACAEAQAAAAAAAAAgAAAAAAAAAAAAAAAAAAo=</HashCode>
       <FileName>ConsoleDrivers\V2\ConsoleInput.cs</FileName>
@@ -41,13 +50,13 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.MainLoop&lt;T&gt;" Collapsed="true" BaseTypeListCollapsed="true">
-    <Position X="10" Y="4.75" Width="1.5" />
+    <Position X="11" Y="4.75" Width="1.5" />
     <AssociationLine Name="AnsiRequestScheduler" Type="Terminal.Gui.AnsiRequestScheduler" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
       <Path>
-        <Point X="10.75" Y="4.75" />
-        <Point X="10.75" Y="4.39" />
-        <Point X="19.625" Y="4.39" />
-        <Point X="19.625" Y="4.5" />
+        <Point X="11.75" Y="4.75" />
+        <Point X="11.75" Y="4.39" />
+        <Point X="20.625" Y="4.39" />
+        <Point X="20.625" Y="4.5" />
       </Path>
     </AssociationLine>
     <TypeIdentifier>
@@ -63,9 +72,9 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.MainLoopCoordinator&lt;T&gt;">
-    <Position X="5.5" Y="2" Width="2" />
+    <Position X="6.5" Y="2" Width="2" />
     <TypeIdentifier>
-      <HashCode>AAAAJAEAAAIAAAAAABQAAAAAABAQIAQAIQAABAAAAgw=</HashCode>
+      <HashCode>AAAAJAEAAAJAAAAAABQAAAAQABAQAAQAIQAABAAACgw=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoopCoordinator.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -74,14 +83,14 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.AnsiResponseParser&lt;T&gt;" Collapsed="true">
-    <Position X="18.75" Y="10" Width="2" />
+    <Position X="19.75" Y="10" Width="2" />
     <TypeIdentifier>
       <HashCode>AAQAAAAAAAAACIAAAAAAAAAAAAAgAABAAAAAABAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.OutputBuffer">
-    <Position X="10" Y="8.25" Width="1.5" />
+    <Position X="11" Y="8.25" Width="1.5" />
     <Compartments>
       <Compartment Name="Fields" Collapsed="true" />
       <Compartment Name="Methods" Collapsed="true" />
@@ -93,7 +102,7 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.NetOutput" Collapsed="true">
-    <Position X="13.75" Y="8.25" Width="1.5" />
+    <Position X="14.75" Y="8.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AEAAAAAAACEAAAAAAAAAAAAAAQAAQAAAMACAAAkAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\NetOutput.cs</FileName>
@@ -101,7 +110,7 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.WindowsOutput" Collapsed="true">
-    <Position X="12.25" Y="8.25" Width="1.5" />
+    <Position X="13.25" Y="8.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAABACACAAhAAACAAAACAAAAgAQAAIMAAAAAEAAAQ=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsOutput.cs</FileName>
@@ -109,17 +118,17 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.InputProcessor&lt;T&gt;" Collapsed="true">
-    <Position X="15.5" Y="4.75" Width="2" />
+    <Position X="16.5" Y="4.75" Width="2" />
     <AssociationLine Name="Parser" Type="Terminal.Gui.AnsiResponseParser&lt;T&gt;" FixedFromPoint="true" FixedToPoint="true">
       <Path>
-        <Point X="17.5" Y="5.031" />
-        <Point X="18.125" Y="5.031" />
-        <Point X="18.125" Y="5.5" />
-        <Point X="19" Y="5.5" />
-        <Point X="19" Y="9.75" />
-        <Point X="18.5" Y="9.75" />
-        <Point X="18.5" Y="10.25" />
-        <Point X="18.75" Y="10.25" />
+        <Point X="18.5" Y="5.031" />
+        <Point X="19.125" Y="5.031" />
+        <Point X="19.125" Y="5.5" />
+        <Point X="20" Y="5.5" />
+        <Point X="20" Y="9.75" />
+        <Point X="19.5" Y="9.75" />
+        <Point X="19.5" Y="10.25" />
+        <Point X="19.75" Y="10.25" />
       </Path>
     </AssociationLine>
     <TypeIdentifier>
@@ -132,28 +141,28 @@
     <Lollipop Position="0.1" />
   </Class>
   <Class Name="Terminal.Gui.NetInputProcessor" Collapsed="true">
-    <Position X="16.75" Y="5.75" Width="2" />
+    <Position X="17.75" Y="5.75" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\NetInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.WindowsInputProcessor" Collapsed="true">
-    <Position X="14.75" Y="5.75" Width="2" />
+    <Position X="15.75" Y="5.75" Width="2" />
     <TypeIdentifier>
       <HashCode>AQAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.AnsiMouseParser">
-    <Position X="23.5" Y="9" Width="1.75" />
+    <Position X="24.5" Y="9" Width="1.75" />
     <TypeIdentifier>
       <HashCode>BAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiMouseParser.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.ConsoleDrivers.V2.ConsoleDriverFacade&lt;T&gt;">
-    <Position X="5.5" Y="6.75" Width="2" />
+    <Position X="6.5" Y="7.25" Width="2" />
     <Compartments>
       <Compartment Name="Methods" Collapsed="true" />
       <Compartment Name="Fields" Collapsed="true" />
@@ -165,7 +174,7 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.AnsiRequestScheduler" Collapsed="true">
-    <Position X="18.75" Y="4.5" Width="2" />
+    <Position X="19.75" Y="4.5" Width="2" />
     <TypeIdentifier>
       <HashCode>AAQAACAAIAAAIAACAESQAAQAACGAAAAAAAAAAAAAQQA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiRequestScheduler.cs</FileName>
@@ -175,7 +184,7 @@
     </ShowAsCollectionAssociation>
   </Class>
   <Class Name="Terminal.Gui.AnsiResponseParserBase" Collapsed="true">
-    <Position X="19.5" Y="9" Width="2" />
+    <Position X="20.5" Y="9" Width="2" />
     <TypeIdentifier>
       <HashCode>UACASAAAEICQALAAQAAACAAAIAIAAABAAQIAJgAQACQ=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
@@ -187,7 +196,7 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.MouseInterpreter">
-    <Position X="12.75" Y="10.5" Width="1.5" />
+    <Position X="13.75" Y="10.5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAQAAAAAAAABAAIAAkAAACAAICgIAAAAAABAAAAAAgA=</HashCode>
       <FileName>ConsoleDrivers\V2\MouseState.cs</FileName>
@@ -200,7 +209,7 @@
     </ShowAsCollectionAssociation>
   </Class>
   <Class Name="Terminal.Gui.ButtonNarrative">
-    <Position X="15.75" Y="12" Width="1.5" />
+    <Position X="16.75" Y="12" Width="1.5" />
     <TypeIdentifier>
       <HashCode>IAAAAAAAAAAAAAAAAgAAAAAAICAAAAQAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\MouseState.cs</FileName>
@@ -213,14 +222,14 @@
     </ShowAsCollectionAssociation>
   </Class>
   <Class Name="Terminal.Gui.ButtonState">
-    <Position X="18.5" Y="12" Width="1.5" />
+    <Position X="19.5" Y="12" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AEAAAAAAAAAAAAggAAAAAAAAAACJAAAAAoAAAAAEAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\MouseState.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.StringHeld" Collapsed="true">
-    <Position X="21" Y="11" Width="1.5" />
+    <Position X="22" Y="11" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAIAACAAAAAAAIAAAAAAAACAAAAAAAgAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\StringHeld.cs</FileName>
@@ -228,7 +237,7 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.GenericHeld&lt;T&gt;" Collapsed="true">
-    <Position X="19.25" Y="11" Width="1.5" />
+    <Position X="20.25" Y="11" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAgAIAACAAAAAAAIAAAAAAAACAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\GenericHeld.cs</FileName>
@@ -236,64 +245,101 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.AnsiEscapeSequenceRequest">
-    <Position X="22.25" Y="4.5" Width="2.5" />
+    <Position X="23.25" Y="4.5" Width="2.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAEAAAAAAAEAAAAACAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiEscapeSequenceRequest.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.AnsiEscapeSequence" Collapsed="true">
-    <Position X="22.25" Y="3.75" Width="2.5" />
+    <Position X="23.25" Y="3.75" Width="2.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAgAAEAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiEscapeSequence.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.AnsiResponseParser" Collapsed="true">
-    <Position X="20.75" Y="10" Width="1.75" />
+    <Position X="21.75" Y="10" Width="1.75" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAgACBAAAAAABAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
     </TypeIdentifier>
   </Class>
+  <Class Name="Terminal.Gui.Application" Collapsed="true">
+    <Position X="0.5" Y="0.5" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>hEK4FAgAqARIspQfBQo0gTGiACNL0AICESJKoggBSw8=</HashCode>
+      <FileName>Application\Application.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="Terminal.Gui.ApplicationImpl" Collapsed="true">
+    <Position X="3" Y="4.5" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AABAAAAAAAAIAgQAAAAAAQAAAAAAAAAAQAACgACAAAI=</HashCode>
+      <FileName>Application\ApplicationImpl.cs</FileName>
+    </TypeIdentifier>
+    <ShowAsAssociation>
+      <Property Name="Instance" />
+    </ShowAsAssociation>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Class Name="Terminal.Gui.ConsoleDrivers.V2.ApplicationV2" Collapsed="true">
+    <Position X="4.5" Y="4.5" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAIAgQAAAAAAQAAAAAAgAAAAAACgIAAAAI=</HashCode>
+      <FileName>ConsoleDrivers\V2\ApplicationV2.cs</FileName>
+    </TypeIdentifier>
+    <ShowAsAssociation>
+      <Field Name="_coordinator" />
+    </ShowAsAssociation>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Class Name="Terminal.Gui.View" Collapsed="true">
+    <Position X="0.5" Y="3" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>3/v2dzPLvbb/5+LOGuv1x0dem3Y5zv/886afz2/e/Y8=</HashCode>
+      <FileName>View\View.Adornments.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
   <Interface Name="Terminal.Gui.IConsoleInput&lt;T&gt;" Collapsed="true">
-    <Position X="11.5" Y="1" Width="1.5" />
+    <Position X="12.5" Y="1" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAI=</HashCode>
       <FileName>ConsoleDrivers\V2\IConsoleInput.cs</FileName>
     </TypeIdentifier>
   </Interface>
   <Interface Name="Terminal.Gui.IMainLoop&lt;T&gt;" Collapsed="true">
-    <Position X="8.25" Y="4.5" Width="1.5" />
+    <Position X="9.25" Y="4.5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAQAAAAAAAABAQQAAAAAAAAAAAAAAAACAAAAAAAAAAI=</HashCode>
       <FileName>ConsoleDrivers\V2\IMainLoop.cs</FileName>
     </TypeIdentifier>
   </Interface>
   <Interface Name="Terminal.Gui.IConsoleOutput" Collapsed="true">
-    <Position X="13" Y="7.25" Width="1.5" />
+    <Position X="14" Y="7.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAMAAAAAEAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IConsoleOutput.cs</FileName>
     </TypeIdentifier>
   </Interface>
   <Interface Name="Terminal.Gui.IOutputBuffer" Collapsed="true">
-    <Position X="10" Y="7.25" Width="1.5" />
+    <Position X="11" Y="7.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AQAAAAAAAIAAAEAIAAAAQIAAAAEMRgAACAAAKABAgAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IOutputBuffer.cs</FileName>
     </TypeIdentifier>
   </Interface>
   <Interface Name="Terminal.Gui.IInputProcessor">
-    <Position X="13" Y="4.5" Width="1.5" />
+    <Position X="14" Y="4.5" Width="1.5" />
     <AssociationLine Name="MouseInterpreter" Type="Terminal.Gui.MouseInterpreter" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
       <Path>
-        <Point X="14.5" Y="6.875" />
         <Point X="15.5" Y="6.875" />
-        <Point X="15.5" Y="10.125" />
-        <Point X="12.375" Y="10.125" />
-        <Point X="12.375" Y="12.143" />
-        <Point X="12.75" Y="12.143" />
+        <Point X="16.5" Y="6.875" />
+        <Point X="16.5" Y="10.125" />
+        <Point X="13.375" Y="10.125" />
+        <Point X="13.375" Y="12.143" />
+        <Point X="13.75" Y="12.143" />
       </Path>
       <MemberNameLabel ManuallyPlaced="true">
         <Position X="0.123" Y="0.152" />
@@ -308,21 +354,21 @@
     </ShowAsAssociation>
   </Interface>
   <Interface Name="Terminal.Gui.ConsoleDrivers.V2.IViewFinder">
-    <Position X="15.75" Y="10.25" Width="1.5" />
+    <Position X="16.75" Y="10.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IViewFinder.cs</FileName>
     </TypeIdentifier>
   </Interface>
   <Interface Name="Terminal.Gui.IHeld">
-    <Position X="23" Y="6.5" Width="1.5" />
+    <Position X="24" Y="6.5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAIAACAAAAAAAIAAAAAAAACAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\IHeld.cs</FileName>
     </TypeIdentifier>
   </Interface>
   <Interface Name="Terminal.Gui.IAnsiResponseParser">
-    <Position X="19.5" Y="5.25" Width="2" />
+    <Position X="20.5" Y="5.25" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAQAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAJAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\IAnsiResponseParser.cs</FileName>
@@ -331,8 +377,22 @@
       <Property Name="State" />
     </ShowAsAssociation>
   </Interface>
+  <Interface Name="Terminal.Gui.IApplication">
+    <Position X="3.25" Y="1.25" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAIAgQAAAAAAQAAAAAAAAAAAAACgAAAAAI=</HashCode>
+      <FileName>Application\IApplication.cs</FileName>
+    </TypeIdentifier>
+  </Interface>
+  <Interface Name="Terminal.Gui.IMainLoopCoordinator" Collapsed="true">
+    <Position X="6.5" Y="0.5" Width="2" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAIQAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\IMainLoopCoordinator.cs</FileName>
+    </TypeIdentifier>
+  </Interface>
   <Enum Name="Terminal.Gui.AnsiResponseParserState">
-    <Position X="19.5" Y="7.25" Width="2" />
+    <Position X="20.5" Y="7.25" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAEAAAAAAAAAAAAAAAAAAAAAIAAIAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParserState.cs</FileName>

From 2360ed870832dbecbf831d4f1d0d138f56be2542 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 7 Dec 2024 15:36:45 +0000
Subject: [PATCH 045/198] WIP can exit with 2x Esc (bug) and can open scenario

---
 .../ConsoleDrivers/V2/ApplicationV2.cs        | 25 ++++++-------------
 UICatalog/UICatalog.cs                        |  5 ++++
 2 files changed, 12 insertions(+), 18 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index c30d0b2a0f..2223a8f9f7 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -7,6 +7,8 @@ public class ApplicationV2 : IApplication
 {
     private IMainLoopCoordinator _coordinator;
 
+    private Stack<View> _stack = new Stack<View> ();
+
     /// <inheritdoc />
     public void Init (IConsoleDriver driver = null, string driverName = null)
     {
@@ -88,8 +90,11 @@ public void Run (Toplevel view, Func<Exception, bool> errorHandler = null)
         Application.Top = view;
 
         Application.Begin (view);
+
+        _stack.Push (view);
+
         // TODO : how to know when we are done?
-        while (Application.Top != null)
+        while (_stack.Any())
         {
             Task.Delay (100).Wait ();
         }
@@ -115,23 +120,7 @@ public void Shutdown ()
     /// <inheritdoc />
     public void RequestStop (Toplevel top)
     {
-        top ??= Application.Top;
-
-        if (top == null)
-        {
-            return;
-        }
-
-        var ev = new ToplevelClosingEventArgs (top);
-        top.OnClosing (ev);
-
-        if (ev.Cancel)
-        {
-            return;
-        }
-
-        top.Running = false;
-        Application.OnNotifyStopRunState (top);
+        _stack.Pop ();
     }
 
     /// <inheritdoc />
diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs
index a543b12497..ad0bdc2eb2 100644
--- a/UICatalog/UICatalog.cs
+++ b/UICatalog/UICatalog.cs
@@ -908,6 +908,11 @@ public UICatalogTopLevel ()
 
         public void ConfigChanged ()
         {
+            if (WasDisposed)
+            {
+                return;
+            }
+
             if (_topLevelColorScheme == null || !Colors.ColorSchemes.ContainsKey (_topLevelColorScheme))
             {
                 _topLevelColorScheme = "Base";

From d8ac1c4e5681bee9df14f716d8df2a289f824966 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 7 Dec 2024 16:42:41 +0000
Subject: [PATCH 046/198] Can switch between apps but have lost keybindings
 somehow

---
 Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index 2223a8f9f7..55bd97f89e 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -7,8 +7,6 @@ public class ApplicationV2 : IApplication
 {
     private IMainLoopCoordinator _coordinator;
 
-    private Stack<View> _stack = new Stack<View> ();
-
     /// <inheritdoc />
     public void Init (IConsoleDriver driver = null, string driverName = null)
     {
@@ -91,10 +89,8 @@ public void Run (Toplevel view, Func<Exception, bool> errorHandler = null)
 
         Application.Begin (view);
 
-        _stack.Push (view);
-
         // TODO : how to know when we are done?
-        while (_stack.Any())
+        while (Application.TopLevels.TryPeek (out var found) && found == view)
         {
             Task.Delay (100).Wait ();
         }
@@ -120,7 +116,16 @@ public void Shutdown ()
     /// <inheritdoc />
     public void RequestStop (Toplevel top)
     {
-        _stack.Pop ();
+        Application.TopLevels.Pop ();
+
+        if(Application.TopLevels.Count>0)
+        {
+            Application.Top = Application.TopLevels.Peek ();
+        }
+        else
+        {
+            Application.Top = null;
+        }
     }
 
     /// <inheritdoc />

From f2497ef5f5776d26030d8bffc21e53e7f06240f9 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 7 Dec 2024 17:14:39 +0000
Subject: [PATCH 047/198] Add esc timeout into InputProcessor

---
 Terminal.Gui/Application/ApplicationImpl.cs   | 16 +++----
 Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs |  5 ---
 .../ConsoleDrivers/NetDriver/NetEvents.cs     |  2 +-
 .../ConsoleDrivers/V2/ApplicationV2.cs        | 42 +++++++------------
 .../ConsoleDrivers/V2/ConsoleDriverFacade.cs  |  4 --
 .../ConsoleDrivers/V2/InputProcessor.cs       | 24 +++++++++++
 .../ConsoleDrivers/V2/NetInputProcessor.cs    | 12 ++++--
 Terminal.Gui/ConsoleDrivers/V2/V2.cd          |  7 ++--
 .../V2/WindowsInputProcessor.cs               | 12 ++++--
 9 files changed, 68 insertions(+), 56 deletions(-)

diff --git a/Terminal.Gui/Application/ApplicationImpl.cs b/Terminal.Gui/Application/ApplicationImpl.cs
index 92cbdfdd1a..d7353c2459 100644
--- a/Terminal.Gui/Application/ApplicationImpl.cs
+++ b/Terminal.Gui/Application/ApplicationImpl.cs
@@ -24,7 +24,7 @@ public static void ChangeInstance (IApplication newApplication)
     /// <inheritdoc/>
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
-    public void Init (IConsoleDriver? driver = null, string? driverName = null)
+    public virtual void Init (IConsoleDriver? driver = null, string? driverName = null)
     {
         Application.InternalInit (driver, driverName);
     }
@@ -71,7 +71,7 @@ public void Init (IConsoleDriver? driver = null, string? driverName = null)
     /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
     [RequiresUnreferencedCode ("AOT")]
     [RequiresDynamicCode ("AOT")]
-    public T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
+    public virtual T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
         where T : Toplevel, new()
     {
         if (!Application.Initialized)
@@ -125,7 +125,7 @@ public T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? dri
     ///     RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true,
     ///     rethrows when null).
     /// </param>
-    public void Run (Toplevel view, Func<Exception, bool>? errorHandler = null)
+    public virtual void Run (Toplevel view, Func<Exception, bool>? errorHandler = null)
     {
         ArgumentNullException.ThrowIfNull (view);
 
@@ -201,7 +201,7 @@ public void Run (Toplevel view, Func<Exception, bool>? errorHandler = null)
     ///     up (Disposed)
     ///     and terminal settings are restored.
     /// </remarks>
-    public void Shutdown ()
+    public virtual void Shutdown ()
     {
         // TODO: Throw an exception if Init hasn't been called.
 
@@ -218,7 +218,7 @@ public void Shutdown ()
     }
 
     /// <inheritdoc />
-    public void RequestStop (Toplevel top)
+    public virtual void RequestStop (Toplevel top)
     {
         top ??= Application.Top;
 
@@ -240,7 +240,7 @@ public void RequestStop (Toplevel top)
     }
 
     /// <inheritdoc />
-    public void Invoke (Action action)
+    public virtual void Invoke (Action action)
     {
         Application.MainLoop?.AddIdle (
                            () =>
@@ -253,10 +253,10 @@ public void Invoke (Action action)
     }
 
     /// <inheritdoc />
-    public bool IsLegacy => true;
+    public bool IsLegacy { get; protected set; } = true;
 
     /// <inheritdoc />
-    public void AddIdle (Func<bool> func)
+    public virtual void AddIdle (Func<bool> func)
     {
         Application.MainLoop.AddIdle (func);
 
diff --git a/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
index 349ca7f1ae..795cd74012 100644
--- a/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
@@ -255,11 +255,6 @@ public interface IConsoleDriver
     /// <param name="ctrl">If <see langword="true"/> simulates the Ctrl key being pressed.</param>
     void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl);
 
-    /// <summary>
-    ///     How long after Esc has been pressed before we give up on getting an Ansi escape sequence
-    /// </summary>
-    public TimeSpan EscTimeout { get; }
-
     /// <summary>
     ///     Queues the given <paramref name="request"/> for execution
     /// </summary>
diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs
index 391eb51961..30cf4a41a1 100644
--- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs
+++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs
@@ -97,7 +97,7 @@ private ConsoleKeyInfo ReadConsoleKeyInfo (bool intercept = true)
     public IEnumerable<ConsoleKeyInfo> ShouldReleaseParserHeldKeys ()
     {
         if (Parser.State == AnsiResponseParserState.ExpectingBracket &&
-            DateTime.Now - Parser.StateChangedAt > _consoleDriver.EscTimeout)
+            DateTime.Now - Parser.StateChangedAt > ((NetDriver)_consoleDriver).EscTimeout)
         {
             return Parser.Release ().Select (o => o.Item2);
         }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index 55bd97f89e..f6f4a7a4ed 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -3,12 +3,17 @@
 namespace Terminal.Gui.ConsoleDrivers.V2;
 
 
-public class ApplicationV2 : IApplication
+public class ApplicationV2 : ApplicationImpl
 {
     private IMainLoopCoordinator _coordinator;
 
+    public ApplicationV2 ()
+    {
+        IsLegacy = false;
+    }
+
     /// <inheritdoc />
-    public void Init (IConsoleDriver driver = null, string driverName = null)
+    public override void Init (IConsoleDriver driver = null, string driverName = null)
     {
         Application.Navigation = new ();
 
@@ -60,13 +65,7 @@ private void CreateDriver ()
     }
 
     /// <inheritdoc />
-    public Toplevel Run (Func<Exception, bool> errorHandler = null, IConsoleDriver driver = null)
-    {
-        return Run<Toplevel> (errorHandler, driver);
-    }
-
-    /// <inheritdoc />
-    public T Run<T> (Func<Exception, bool> errorHandler = null, IConsoleDriver driver = null) where T : Toplevel, new ()
+    public override T Run<T> (Func<Exception, bool> errorHandler = null, IConsoleDriver driver = null)
     {
         var top = new T ();
 
@@ -76,7 +75,7 @@ public Toplevel Run (Func<Exception, bool> errorHandler = null, IConsoleDriver d
     }
 
     /// <inheritdoc />
-    public void Run (Toplevel view, Func<Exception, bool> errorHandler = null)
+    public override void Run (Toplevel view, Func<Exception, bool> errorHandler = null)
     {
         ArgumentNullException.ThrowIfNull (view);
 
@@ -97,24 +96,14 @@ public void Run (Toplevel view, Func<Exception, bool> errorHandler = null)
     }
 
     /// <inheritdoc />
-    public void Shutdown ()
+    public override void Shutdown ()
     {
         _coordinator.Stop ();
-
-        bool wasInitialized = Application.Initialized;
-        Application.ResetState ();
-        PrintJsonErrors ();
-
-        if (wasInitialized)
-        {
-            bool init = Application.Initialized;
-
-            Application.OnInitializedChanged (this, new (in init));
-        }
+        base.Shutdown ();
     }
 
     /// <inheritdoc />
-    public void RequestStop (Toplevel top)
+    public override void RequestStop (Toplevel top)
     {
         Application.TopLevels.Pop ();
 
@@ -129,16 +118,13 @@ public void RequestStop (Toplevel top)
     }
 
     /// <inheritdoc />
-    public void Invoke (Action action)
+    public override void Invoke (Action action)
     {
         // TODO
     }
 
     /// <inheritdoc />
-    public bool IsLegacy => false;
-
-    /// <inheritdoc />
-    public void AddIdle (Func<bool> func)
+    public override void AddIdle (Func<bool> func)
     {
         // TODO properly
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
index ff37f010f4..32e2312bb9 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
@@ -18,10 +18,6 @@ public ConsoleDriverFacade (IInputProcessor inputProcessor, IOutputBuffer output
         _inputProcessor.KeyUp += (s, e) => KeyUp?.Invoke (s, e);
         _inputProcessor.MouseEvent += (s, e) => MouseEvent?.Invoke (s, e);
     }
-    /// <summary>
-    /// How long after Esc has been pressed before we give up on getting an Ansi escape sequence
-    /// </summary>
-    public TimeSpan EscTimeout { get; } = TimeSpan.FromMilliseconds (50);
 
     /// <summary>Gets the location and size of the terminal screen.</summary>
     public Rectangle Screen => new (new (0,0),_output.GetWindowSize ());
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index 51ab656a4a..edf5dd7fb2 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -4,6 +4,12 @@ namespace Terminal.Gui;
 
 public abstract class InputProcessor<T> : IInputProcessor
 {
+
+    /// <summary>
+    /// How long after Esc has been pressed before we give up on getting an Ansi escape sequence
+    /// </summary>
+    TimeSpan _escTimeout = TimeSpan.FromMilliseconds (50);
+
     public AnsiResponseParser<T> Parser { get; } = new ();
     public ConcurrentQueue<T> InputBuffer { get; }
 
@@ -90,7 +96,25 @@ public void ProcessQueue ()
         {
             Process (input);
         }
+
+        foreach (var input in ShouldReleaseParserHeldKeys ())
+        {
+            ProcessAfterParsing (input);
+        }
     }
 
+
+    public IEnumerable<T> ShouldReleaseParserHeldKeys ()
+    {
+        if (Parser.State == AnsiResponseParserState.ExpectingBracket &&
+            DateTime.Now - Parser.StateChangedAt > _escTimeout)
+        {
+            return Parser.Release ().Select (o => o.Item2);
+        }
+
+        return [];
+    }
     protected abstract void Process (T result);
+
+    protected abstract void ProcessAfterParsing (T input);
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
index 5085ba5c2d..22f0057ae2 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
@@ -16,9 +16,15 @@ protected override void Process (ConsoleKeyInfo consoleKeyInfo)
     {
         foreach (Tuple<char, ConsoleKeyInfo> released in Parser.ProcessInput (Tuple.Create (consoleKeyInfo.KeyChar, consoleKeyInfo)))
         {
-            var key = ConsoleKeyMapping.MapKey (released.Item2);
-            OnKeyDown (key);
-            OnKeyUp (key);
+            ProcessAfterParsing (released.Item2);
         }
     }
+
+    /// <inheritdoc />
+    protected override void ProcessAfterParsing (ConsoleKeyInfo input)
+    {
+        var key = ConsoleKeyMapping.MapKey (input);
+        OnKeyDown (key);
+        OnKeyUp (key);
+    }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index be9a21714b..f24f147e84 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -132,7 +132,7 @@
       </Path>
     </AssociationLine>
     <TypeIdentifier>
-      <HashCode>AQAkAAAAAACAgAAAAgggAAAABAoAAAAAAAgAAAAAAAA=</HashCode>
+      <HashCode>AQCkAAAAAASAgAAAAgggAAAABAoAAAAAAAgAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\InputProcessor.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -168,7 +168,7 @@
       <Compartment Name="Fields" Collapsed="true" />
     </Compartments>
     <TypeIdentifier>
-      <HashCode>AQMgAAAAAKBAgFEIBBggQJEAAjkaQgIAGQADKABDggQ=</HashCode>
+      <HashCode>AQMgAAAAAKBAgFEIBBgAQJEAAjkaQgIAGQADKABDggQ=</HashCode>
       <FileName>ConsoleDrivers\V2\ConsoleDriverFacade.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
@@ -286,13 +286,12 @@
   <Class Name="Terminal.Gui.ConsoleDrivers.V2.ApplicationV2" Collapsed="true">
     <Position X="4.5" Y="4.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAIAgQAAAAAAQAAAAAAgAAAAAACgIAAAAI=</HashCode>
+      <HashCode>AAAAAAAAAAAIAgAAAAAAAQAAAAAAgAAAAAACgIAAAAI=</HashCode>
       <FileName>ConsoleDrivers\V2\ApplicationV2.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
       <Field Name="_coordinator" />
     </ShowAsAssociation>
-    <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.View" Collapsed="true">
     <Position X="0.5" Y="3" Width="1.5" />
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
index b810d53509..d57fde2432 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
@@ -29,9 +29,7 @@ protected override void Process (InputRecord inputEvent)
 
                 foreach (Tuple<char, InputRecord> released in Parser.ProcessInput (Tuple.Create (inputEvent.KeyEvent.UnicodeChar, inputEvent)))
                 {
-                    var key = (Key) (released.Item2.KeyEvent.UnicodeChar);
-                    OnKeyDown (key);
-                    OnKeyUp (key);
+                    ProcessAfterParsing (released.Item2);
                 }
 
                 /*
@@ -67,6 +65,14 @@ protected override void Process (InputRecord inputEvent)
         }
     }
 
+    /// <inheritdoc />
+    protected override void ProcessAfterParsing (InputRecord input)
+    {
+        var key = (Key)input.KeyEvent.UnicodeChar;
+        OnKeyDown (key);
+        OnKeyUp (key);
+    }
+
     private MouseEventArgs ToDriverMouse (MouseEventRecord e)
     {
         var result = new MouseEventArgs

From 8405ef165ef773e7b7dcf7420d1e89f9c7ac0c1c Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 7 Dec 2024 19:45:41 +0000
Subject: [PATCH 048/198] Fix event wiring

---
 Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs       | 2 ++
 Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs | 8 --------
 2 files changed, 2 insertions(+), 8 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index f6f4a7a4ed..220e687a47 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -22,6 +22,8 @@ public override void Init (IConsoleDriver driver = null, string driverName = nul
         CreateDriver ();
 
         Application.Initialized = true;
+
+        Application.SubscribeDriverEvents ();
     }
 
     private void CreateDriver ()
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index 7319019633..42dbed0cdb 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -100,14 +100,6 @@ private void BuildFacadeIfPossible ()
             _facade = new ConsoleDriverFacade<T> (_inputProcessor, _loop.OutputBuffer,_output,_loop.AnsiRequestScheduler);
             Application.Driver = _facade;
 
-            Application.Driver.KeyDown += (s, e) => Application.Top?.NewKeyDownEvent (e);
-            Application.Driver.KeyUp += (s, e) => Application.Top?.NewKeyUpEvent (e);
-
-            Application.Driver.MouseEvent += (s, e) =>
-                                             {
-                                                 e.View?.NewMouseEvent (e);
-                                             };
-
             StartupSemaphore.Release ();
         }
     }

From d0dd43a7bc18f6e5fc0f6e65a520cd5cabf31f28 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 7 Dec 2024 20:40:22 +0000
Subject: [PATCH 049/198] Move Idles/Timeouts into their own self contained
 class TimedEvents behind interface ITimedEvents

---
 Terminal.Gui/Application/Application.Run.cs   |   7 +-
 Terminal.Gui/Application/ApplicationImpl.cs   |  12 +
 Terminal.Gui/Application/IApplication.cs      |   2 +
 Terminal.Gui/Application/MainLoop.cs          | 243 +-------------
 Terminal.Gui/Application/TimedEvents.cs       | 301 ++++++++++++++++++
 Terminal.Gui/Application/TimeoutEventArgs.cs  |   2 +-
 .../CursesDriver/UnixMainLoop.cs              |   2 +-
 .../ConsoleDrivers/NetDriver/NetMainLoop.cs   |   4 +-
 .../WindowsDriver/WindowsMainLoop.cs          |   6 +-
 UnitTests/Application/MainLoopTests.cs        | 166 +++++-----
 .../ConsoleDrivers/MainLoopDriverTests.cs     |  28 +-
 UnitTests/UICatalog/ScenarioTests.cs          |   2 +-
 UnitTests/Views/SpinnerViewTests.cs           |  14 +-
 13 files changed, 432 insertions(+), 357 deletions(-)
 create mode 100644 Terminal.Gui/Application/TimedEvents.cs

diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs
index e3db07a273..6680542513 100644
--- a/Terminal.Gui/Application/Application.Run.cs
+++ b/Terminal.Gui/Application/Application.Run.cs
@@ -366,10 +366,7 @@ public static void Run (Toplevel view, Func<Exception, bool>? errorHandler = nul
     ///     reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a
     ///     token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
     /// </remarks>
-    public static object? AddTimeout (TimeSpan time, Func<bool> callback)
-    {
-        return MainLoop?.AddTimeout (time, callback) ?? null;
-    }
+    public static object? AddTimeout (TimeSpan time, Func<bool> callback) => ApplicationImpl.Instance.AddTimeout (time, callback);
 
     /// <summary>Removes a previously scheduled timeout</summary>
     /// <remarks>The token parameter is the value returned by <see cref="AddTimeout"/>.</remarks>
@@ -381,7 +378,7 @@ public static void Run (Toplevel view, Func<Exception, bool>? errorHandler = nul
     /// This method also returns
     /// <c>false</c>
     /// if the timeout is not found.
-    public static bool RemoveTimeout (object token) { return MainLoop?.RemoveTimeout (token) ?? false; }
+    public static bool RemoveTimeout (object token) => ApplicationImpl.Instance.RemoveTimeout (token);
 
     /// <summary>Runs <paramref name="action"/> on the thread that is processing events</summary>
     /// <param name="action">the action to be invoked on the main processing thread.</param>
diff --git a/Terminal.Gui/Application/ApplicationImpl.cs b/Terminal.Gui/Application/ApplicationImpl.cs
index d7353c2459..c459e5855d 100644
--- a/Terminal.Gui/Application/ApplicationImpl.cs
+++ b/Terminal.Gui/Application/ApplicationImpl.cs
@@ -261,4 +261,16 @@ public virtual void AddIdle (Func<bool> func)
         Application.MainLoop.AddIdle (func);
 
     }
+
+    /// <inheritdoc />
+    public virtual object AddTimeout (TimeSpan time, Func<bool> callback)
+    {
+        return Application.MainLoop?.TimedEvents.AddTimeout (time, callback) ?? null;
+    }
+
+    /// <inheritdoc />
+    public virtual bool RemoveTimeout (object token)
+    { 
+        return Application.MainLoop?.TimedEvents.RemoveTimeout (token) ?? false; 
+    }
 }
diff --git a/Terminal.Gui/Application/IApplication.cs b/Terminal.Gui/Application/IApplication.cs
index e195f20487..b46ba20079 100644
--- a/Terminal.Gui/Application/IApplication.cs
+++ b/Terminal.Gui/Application/IApplication.cs
@@ -153,4 +153,6 @@ public T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? dri
 
     bool IsLegacy { get; }
     void AddIdle (Func<bool> func);
+    object AddTimeout (TimeSpan time, Func<bool> callback);
+    bool RemoveTimeout (object token);
 }
\ No newline at end of file
diff --git a/Terminal.Gui/Application/MainLoop.cs b/Terminal.Gui/Application/MainLoop.cs
index 1e6006923d..445848b5c1 100644
--- a/Terminal.Gui/Application/MainLoop.cs
+++ b/Terminal.Gui/Application/MainLoop.cs
@@ -39,13 +39,7 @@ internal interface IMainLoopDriver
 /// </remarks>
 public class MainLoop : IDisposable
 {
-    internal List<Func<bool>> _idleHandlers = new ();
-    internal SortedList<long, Timeout> _timeouts = new ();
-
-    /// <summary>The idle handlers and lock that must be held while manipulating them</summary>
-    private readonly object _idleHandlersLock = new ();
-
-    private readonly object _timeoutsLockToken = new ();
+    public ITimedEvents TimedEvents { get; } = new TimedEvents();
 
     /// <summary>Creates a new MainLoop.</summary>
     /// <remarks>Use <see cref="Dispose"/> to release resources.</remarks>
@@ -59,17 +53,6 @@ internal MainLoop (IMainLoopDriver driver)
         driver.Setup (this);
     }
 
-    /// <summary>Gets a copy of the list of all idle handlers.</summary>
-    internal ReadOnlyCollection<Func<bool>> IdleHandlers
-    {
-        get
-        {
-            lock (_idleHandlersLock)
-            {
-                return new List<Func<bool>> (_idleHandlers).AsReadOnly ();
-            }
-        }
-    }
 
     /// <summary>The current <see cref="IMainLoopDriver"/> in use.</summary>
     /// <value>The main loop driver.</value>
@@ -78,11 +61,6 @@ internal ReadOnlyCollection<Func<bool>> IdleHandlers
     /// <summary>Used for unit tests.</summary>
     internal bool Running { get; set; }
 
-    /// <summary>
-    ///     Gets the list of all timeouts sorted by the <see cref="TimeSpan"/> time ticks. A shorter limit time can be
-    ///     added at the end, but it will be called before an earlier addition that has a longer limit time.
-    /// </summary>
-    internal SortedList<long, Timeout> Timeouts => _timeouts;
 
     /// <inheritdoc/>
     public void Dispose ()
@@ -113,76 +91,13 @@ public void Dispose ()
     // 
     internal Func<bool> AddIdle (Func<bool> idleHandler)
     {
-        lock (_idleHandlersLock)
-        {
-            _idleHandlers.Add (idleHandler);
-        }
+        TimedEvents.AddIdle (idleHandler);
 
         MainLoopDriver?.Wakeup ();
 
         return idleHandler;
     }
 
-    /// <summary>Adds a timeout to the <see cref="MainLoop"/>.</summary>
-    /// <remarks>
-    ///     When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be
-    ///     reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a
-    ///     token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
-    /// </remarks>
-    internal object AddTimeout (TimeSpan time, Func<bool> callback)
-    {
-        ArgumentNullException.ThrowIfNull (callback);
-
-        var timeout = new Timeout { Span = time, Callback = callback };
-        AddTimeout (time, timeout);
-
-        return timeout;
-    }
-
-    /// <summary>
-    ///     Called from <see cref="IMainLoopDriver.EventsPending"/> to check if there are any outstanding timers or idle
-    ///     handlers.
-    /// </summary>
-    /// <param name="waitTimeout">
-    ///     Returns the number of milliseconds remaining in the current timer (if any). Will be -1 if
-    ///     there are no active timers.
-    /// </param>
-    /// <returns><see langword="true"/> if there is a timer or idle handler active.</returns>
-    internal bool CheckTimersAndIdleHandlers (out int waitTimeout)
-    {
-        long now = DateTime.UtcNow.Ticks;
-
-        waitTimeout = 0;
-
-        lock (_timeoutsLockToken)
-        {
-            if (_timeouts.Count > 0)
-            {
-                waitTimeout = (int)((_timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond);
-
-                if (waitTimeout < 0)
-                {
-                    // This avoids 'poll' waiting infinitely if 'waitTimeout < 0' until some action is detected
-                    // This can occur after IMainLoopDriver.Wakeup is executed where the pollTimeout is less than 0
-                    // and no event occurred in elapsed time when the 'poll' is start running again.
-                    waitTimeout = 0;
-                }
-
-                return true;
-            }
-
-            // ManualResetEventSlim.Wait, which is called by IMainLoopDriver.EventsPending, will wait indefinitely if
-            // the timeout is -1.
-            waitTimeout = -1;
-        }
-
-        // There are no timers set, check if there are any idle handlers
-
-        lock (_idleHandlers)
-        {
-            return _idleHandlers.Count > 0;
-        }
-    }
 
     /// <summary>Determines whether there are pending events to be processed.</summary>
     /// <remarks>
@@ -191,50 +106,6 @@ internal bool CheckTimersAndIdleHandlers (out int waitTimeout)
     /// </remarks>
     internal bool EventsPending () { return MainLoopDriver!.EventsPending (); }
 
-    /// <summary>Removes an idle handler added with <see cref="AddIdle(Func{bool})"/> from processing.</summary>
-    /// <param name="token">A token returned by <see cref="AddIdle(Func{bool})"/></param>
-    /// Returns
-    /// <c>true</c>
-    /// if the idle handler is successfully removed; otherwise,
-    /// <c>false</c>
-    /// .
-    /// This method also returns
-    /// <c>false</c>
-    /// if the idle handler is not found.
-    internal bool RemoveIdle (Func<bool> token)
-    {
-        lock (_idleHandlersLock)
-        {
-            return _idleHandlers.Remove (token);
-        }
-    }
-
-    /// <summary>Removes a previously scheduled timeout</summary>
-    /// <remarks>The token parameter is the value returned by AddTimeout.</remarks>
-    /// Returns
-    /// <c>true</c>
-    /// if the timeout is successfully removed; otherwise,
-    /// <c>false</c>
-    /// .
-    /// This method also returns
-    /// <c>false</c>
-    /// if the timeout is not found.
-    internal bool RemoveTimeout (object token)
-    {
-        lock (_timeoutsLockToken)
-        {
-            int idx = _timeouts.IndexOfValue ((token as Timeout)!);
-
-            if (idx == -1)
-            {
-                return false;
-            }
-
-            _timeouts.RemoveAt (idx);
-        }
-
-        return true;
-    }
 
     /// <summary>Runs the <see cref="MainLoop"/>. Used only for unit tests.</summary>
     internal void Run ()
@@ -260,29 +131,13 @@ internal void Run ()
     /// </remarks>
     internal void RunIteration ()
     {
-        lock (_timeoutsLockToken)
-        {
-            if (_timeouts.Count > 0)
-            {
-                RunTimers ();
-            }
-        }
-
         RunAnsiScheduler ();
 
         MainLoopDriver?.Iteration ();
 
-        bool runIdle;
+        TimedEvents.LockAndRunIdles ();
 
-        lock (_idleHandlersLock)
-        {
-            runIdle = _idleHandlers.Count > 0;
-        }
 
-        if (runIdle)
-        {
-            RunIdle ();
-        }
     }
 
     private void RunAnsiScheduler ()
@@ -297,101 +152,9 @@ internal void Stop ()
         Wakeup ();
     }
 
-    /// <summary>
-    ///     Invoked when a new timeout is added. To be used in the case when
-    ///     <see cref="Application.EndAfterFirstIteration"/> is <see langword="true"/>.
-    /// </summary>
-    internal event EventHandler<TimeoutEventArgs>? TimeoutAdded;
 
     /// <summary>Wakes up the <see cref="MainLoop"/> that might be waiting on input.</summary>
     internal void Wakeup () { MainLoopDriver?.Wakeup (); }
 
-    private void AddTimeout (TimeSpan time, Timeout timeout)
-    {
-        lock (_timeoutsLockToken)
-        {
-            long k = (DateTime.UtcNow + time).Ticks;
-            _timeouts.Add (NudgeToUniqueKey (k), timeout);
-            TimeoutAdded?.Invoke (this, new TimeoutEventArgs (timeout, k));
-        }
-    }
 
-    /// <summary>
-    ///     Finds the closest number to <paramref name="k"/> that is not present in <see cref="_timeouts"/>
-    ///     (incrementally).
-    /// </summary>
-    /// <param name="k"></param>
-    /// <returns></returns>
-    private long NudgeToUniqueKey (long k)
-    {
-        lock (_timeoutsLockToken)
-        {
-            while (_timeouts.ContainsKey (k))
-            {
-                k++;
-            }
-        }
-
-        return k;
-    }
-
-    // PERF: This is heavier than it looks.
-    // CONCURRENCY: Potential deadlock city here.
-    // CONCURRENCY: Multiple concurrency pitfalls on the delegates themselves.
-    // INTENT: It looks like the general architecture here is trying to be a form of publisher/consumer pattern.
-    private void RunIdle ()
-    {
-        List<Func<bool>> iterate;
-
-        lock (_idleHandlersLock)
-        {
-            iterate = _idleHandlers;
-            _idleHandlers = new List<Func<bool>> ();
-        }
-
-        foreach (Func<bool> idle in iterate)
-        {
-            if (idle ())
-            {
-                lock (_idleHandlersLock)
-                {
-                    _idleHandlers.Add (idle);
-                }
-            }
-        }
-    }
-
-    private void RunTimers ()
-    {
-        long now = DateTime.UtcNow.Ticks;
-        SortedList<long, Timeout> copy;
-
-        // lock prevents new timeouts being added
-        // after we have taken the copy but before
-        // we have allocated a new list (which would
-        // result in lost timeouts or errors during enumeration)
-        lock (_timeoutsLockToken)
-        {
-            copy = _timeouts;
-            _timeouts = new SortedList<long, Timeout> ();
-        }
-
-        foreach ((long k, Timeout timeout) in copy)
-        {
-            if (k < now)
-            {
-                if (timeout.Callback ())
-                {
-                    AddTimeout (timeout.Span, timeout);
-                }
-            }
-            else
-            {
-                lock (_timeoutsLockToken)
-                {
-                    _timeouts.Add (NudgeToUniqueKey (k), timeout);
-                }
-            }
-        }
-    }
 }
diff --git a/Terminal.Gui/Application/TimedEvents.cs b/Terminal.Gui/Application/TimedEvents.cs
new file mode 100644
index 0000000000..bd493a9ade
--- /dev/null
+++ b/Terminal.Gui/Application/TimedEvents.cs
@@ -0,0 +1,301 @@
+using System.Collections.ObjectModel;
+
+namespace Terminal.Gui;
+
+public class TimedEvents : ITimedEvents
+{
+    internal List<Func<bool>> _idleHandlers = new ();
+    internal SortedList<long, Timeout> _timeouts = new ();
+
+    /// <summary>The idle handlers and lock that must be held while manipulating them</summary>
+    private readonly object _idleHandlersLock = new ();
+
+    private readonly object _timeoutsLockToken = new ();
+
+
+    /// <summary>Gets a copy of the list of all idle handlers.</summary>
+    public ReadOnlyCollection<Func<bool>> IdleHandlers
+    {
+        get
+        {
+            lock (_idleHandlersLock)
+            {
+                return new List<Func<bool>> (_idleHandlers).AsReadOnly ();
+            }
+        }
+    }
+
+    /// <summary>
+    ///     Gets the list of all timeouts sorted by the <see cref="TimeSpan"/> time ticks. A shorter limit time can be
+    ///     added at the end, but it will be called before an earlier addition that has a longer limit time.
+    /// </summary>
+    public SortedList<long, Timeout> Timeouts => _timeouts;
+
+    /// <inheritdoc />
+    public void AddIdle (Func<bool> idleHandler)
+    {
+        lock (_idleHandlersLock)
+        {
+            _idleHandlers.Add (idleHandler);
+        }
+    }
+
+    /// <summary>
+    ///     Invoked when a new timeout is added. To be used in the case when
+    ///     <see cref="Application.EndAfterFirstIteration"/> is <see langword="true"/>.
+    /// </summary>
+    public event EventHandler<TimeoutEventArgs>? TimeoutAdded;
+
+
+    private void AddTimeout (TimeSpan time, Timeout timeout)
+    {
+        lock (_timeoutsLockToken)
+        {
+            long k = (DateTime.UtcNow + time).Ticks;
+            _timeouts.Add (NudgeToUniqueKey (k), timeout);
+            TimeoutAdded?.Invoke (this, new TimeoutEventArgs (timeout, k));
+        }
+    }
+
+    /// <summary>
+    ///     Finds the closest number to <paramref name="k"/> that is not present in <see cref="_timeouts"/>
+    ///     (incrementally).
+    /// </summary>
+    /// <param name="k"></param>
+    /// <returns></returns>
+    private long NudgeToUniqueKey (long k)
+    {
+        lock (_timeoutsLockToken)
+        {
+            while (_timeouts.ContainsKey (k))
+            {
+                k++;
+            }
+        }
+
+        return k;
+    }
+
+
+    // PERF: This is heavier than it looks.
+    // CONCURRENCY: Potential deadlock city here.
+    // CONCURRENCY: Multiple concurrency pitfalls on the delegates themselves.
+    // INTENT: It looks like the general architecture here is trying to be a form of publisher/consumer pattern.
+    private void RunIdle ()
+    {
+        List<Func<bool>> iterate;
+
+        iterate = _idleHandlers;
+        _idleHandlers = new List<Func<bool>> ();
+
+        foreach (Func<bool> idle in iterate)
+        {
+            if (idle ())
+            {
+                _idleHandlers.Add (idle);
+            }
+        }
+    }
+
+
+    public void LockAndRunTimers ()
+    {
+        lock (_timeoutsLockToken)
+        {
+            if (_timeouts.Count > 0)
+            {
+                RunTimers ();
+            }
+        }
+
+    }
+
+    public void LockAndRunIdles ()
+    {
+        bool runIdle;
+
+        lock (_idleHandlersLock)
+        {
+            runIdle = _idleHandlers.Count > 0;
+            if (runIdle)
+            {
+                RunIdle ();
+            }
+        }
+    }
+    private void RunTimers ()
+    {
+        long now = DateTime.UtcNow.Ticks;
+        SortedList<long, Timeout> copy;
+
+        // lock prevents new timeouts being added
+        // after we have taken the copy but before
+        // we have allocated a new list (which would
+        // result in lost timeouts or errors during enumeration)
+        lock (_timeoutsLockToken)
+        {
+            copy = _timeouts;
+            _timeouts = new SortedList<long, Timeout> ();
+        }
+
+        foreach ((long k, Timeout timeout) in copy)
+        {
+            if (k < now)
+            {
+                if (timeout.Callback ())
+                {
+                    AddTimeout (timeout.Span, timeout);
+                }
+            }
+            else
+            {
+                lock (_timeoutsLockToken)
+                {
+                    _timeouts.Add (NudgeToUniqueKey (k), timeout);
+                }
+            }
+        }
+    }
+
+
+    /// <summary>Removes an idle handler added with <see cref="AddIdle(Func{bool})"/> from processing.</summary>
+    /// <param name="token">A token returned by <see cref="AddIdle(Func{bool})"/></param>
+    /// Returns
+    /// <c>true</c>
+    /// if the idle handler is successfully removed; otherwise,
+    /// <c>false</c>
+    /// .
+    /// This method also returns
+    /// <c>false</c>
+    /// if the idle handler is not found.
+    public bool RemoveIdle (Func<bool> token)
+    {
+        lock (_idleHandlersLock)
+        {
+            return _idleHandlers.Remove (token);
+        }
+    }
+
+    /// <summary>Removes a previously scheduled timeout</summary>
+    /// <remarks>The token parameter is the value returned by AddTimeout.</remarks>
+    /// Returns
+    /// <c>true</c>
+    /// if the timeout is successfully removed; otherwise,
+    /// <c>false</c>
+    /// .
+    /// This method also returns
+    /// <c>false</c>
+    /// if the timeout is not found.
+    public bool RemoveTimeout (object token)
+    {
+        lock (_timeoutsLockToken)
+        {
+            int idx = _timeouts.IndexOfValue ((token as Timeout)!);
+
+            if (idx == -1)
+            {
+                return false;
+            }
+
+            _timeouts.RemoveAt (idx);
+        }
+
+        return true;
+    }
+
+
+    /// <summary>Adds a timeout to the <see cref="MainLoop"/>.</summary>
+    /// <remarks>
+    ///     When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be
+    ///     reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a
+    ///     token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
+    /// </remarks>
+    public object AddTimeout (TimeSpan time, Func<bool> callback)
+    {
+        ArgumentNullException.ThrowIfNull (callback);
+
+        var timeout = new Timeout { Span = time, Callback = callback };
+        AddTimeout (time, timeout);
+
+        return timeout;
+    }
+
+    /// <summary>
+    ///     Called from <see cref="IMainLoopDriver.EventsPending"/> to check if there are any outstanding timers or idle
+    ///     handlers.
+    /// </summary>
+    /// <param name="waitTimeout">
+    ///     Returns the number of milliseconds remaining in the current timer (if any). Will be -1 if
+    ///     there are no active timers.
+    /// </param>
+    /// <returns><see langword="true"/> if there is a timer or idle handler active.</returns>
+    public bool CheckTimersAndIdleHandlers (out int waitTimeout)
+    {
+        long now = DateTime.UtcNow.Ticks;
+
+        waitTimeout = 0;
+
+        lock (_timeoutsLockToken)
+        {
+            if (_timeouts.Count > 0)
+            {
+                waitTimeout = (int)((_timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond);
+
+                if (waitTimeout < 0)
+                {
+                    // This avoids 'poll' waiting infinitely if 'waitTimeout < 0' until some action is detected
+                    // This can occur after IMainLoopDriver.Wakeup is executed where the pollTimeout is less than 0
+                    // and no event occurred in elapsed time when the 'poll' is start running again.
+                    waitTimeout = 0;
+                }
+
+                return true;
+            }
+
+            // ManualResetEventSlim.Wait, which is called by IMainLoopDriver.EventsPending, will wait indefinitely if
+            // the timeout is -1.
+            waitTimeout = -1;
+        }
+
+        // There are no timers set, check if there are any idle handlers
+
+        lock (_idleHandlers)
+        {
+            return _idleHandlers.Count > 0;
+        }
+    }
+}
+
+public interface ITimedEvents
+{
+    void AddIdle (Func<bool> idleHandler);
+    void LockAndRunIdles ();
+    bool CheckTimersAndIdleHandlers (out int waitTimeout);
+
+    /// <summary>Adds a timeout to the application.</summary>
+    /// <remarks>
+    ///     When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be
+    ///     reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a
+    ///     token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
+    /// </remarks>
+    object AddTimeout (TimeSpan time, Func<bool> callback);
+
+    /// <summary>Removes a previously scheduled timeout</summary>
+    /// <remarks>The token parameter is the value returned by AddTimeout.</remarks>
+    /// Returns
+    /// <c>true</c>
+    /// if the timeout is successfully removed; otherwise,
+    /// <c>false</c>
+    /// .
+    /// This method also returns
+    /// <c>false</c>
+    /// if the timeout is not found.
+    bool RemoveTimeout (object token);
+
+    ReadOnlyCollection<Func<bool>> IdleHandlers { get;}
+
+    SortedList<long, Timeout> Timeouts { get; }
+    bool RemoveIdle (Func<bool> fnTrue);
+
+    event EventHandler<TimeoutEventArgs>? TimeoutAdded;
+}
diff --git a/Terminal.Gui/Application/TimeoutEventArgs.cs b/Terminal.Gui/Application/TimeoutEventArgs.cs
index 19346e57f1..6a2ca70674 100644
--- a/Terminal.Gui/Application/TimeoutEventArgs.cs
+++ b/Terminal.Gui/Application/TimeoutEventArgs.cs
@@ -1,7 +1,7 @@
 namespace Terminal.Gui;
 
 /// <summary><see cref="EventArgs"/> for timeout events (e.g. <see cref="MainLoop.TimeoutAdded"/>)</summary>
-internal class TimeoutEventArgs : EventArgs
+public class TimeoutEventArgs : EventArgs
 {
     /// <summary>Creates a new instance of the <see cref="TimeoutEventArgs"/> class.</summary>
     /// <param name="timeout"></param>
diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs
index 4b2daea409..cb6a0f5d12 100644
--- a/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs
@@ -102,7 +102,7 @@ bool IMainLoopDriver.EventsPending ()
 
         UpdatePollMap ();
 
-        bool checkTimersResult = _mainLoop!.CheckTimersAndIdleHandlers (out int pollTimeout);
+        bool checkTimersResult = _mainLoop!.TimedEvents.CheckTimersAndIdleHandlers (out int pollTimeout);
 
         int n = poll (_pollMap!, (uint)_pollMap!.Length, pollTimeout);
 
diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs
index 37be0766f6..cf5ee0e767 100644
--- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs
@@ -55,7 +55,7 @@ bool IMainLoopDriver.EventsPending ()
 
         _waitForProbe.Set ();
 
-        if (_resultQueue.Count > 0 || _mainLoop!.CheckTimersAndIdleHandlers (out int waitTimeout))
+        if (_resultQueue.Count > 0 || _mainLoop!.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeout))
         {
             return true;
         }
@@ -82,7 +82,7 @@ bool IMainLoopDriver.EventsPending ()
 
         if (!_eventReadyTokenSource.IsCancellationRequested)
         {
-            return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _);
+            return _resultQueue.Count > 0 || _mainLoop.TimedEvents.CheckTimersAndIdleHandlers (out _);
         }
 
         // If cancellation was requested then always return true
diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs
index fedcf6f732..13fcafbd9a 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsMainLoop.cs
@@ -68,7 +68,7 @@ bool IMainLoopDriver.EventsPending ()
 #if HACK_CHECK_WINCHANGED
         _winChange.Set ();
 #endif
-        if (_resultQueue.Count > 0 || _mainLoop!.CheckTimersAndIdleHandlers (out int waitTimeout))
+        if (_resultQueue.Count > 0 || _mainLoop!.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeout))
         {
             return true;
         }
@@ -97,9 +97,9 @@ bool IMainLoopDriver.EventsPending ()
         if (!_eventReadyTokenSource.IsCancellationRequested)
         {
 #if HACK_CHECK_WINCHANGED
-            return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _) || _winChanged;
+            return _resultQueue.Count > 0 || _mainLoop.TimedEvents.CheckTimersAndIdleHandlers (out _) || _winChanged;
 #else
-            return _resultQueue.Count > 0 || _mainLoop.CheckTimersAndIdleHandlers (out _);
+            return _resultQueue.Count > 0 || _mainLoop.TimedEvents.CheckTimersAndIdleHandlers (out _);
 #endif
         }
 
diff --git a/UnitTests/Application/MainLoopTests.cs b/UnitTests/Application/MainLoopTests.cs
index 47812926d2..221dbda72c 100644
--- a/UnitTests/Application/MainLoopTests.cs
+++ b/UnitTests/Application/MainLoopTests.cs
@@ -55,50 +55,50 @@ public void AddIdle_Adds_And_Removes ()
         Func<bool> fnTrue = () => true;
         Func<bool> fnFalse = () => false;
 
-        ml.AddIdle (fnTrue);
-        ml.AddIdle (fnFalse);
+        ml.TimedEvents.AddIdle (fnTrue);
+        ml.TimedEvents.AddIdle (fnFalse);
 
-        Assert.Equal (2, ml.IdleHandlers.Count);
-        Assert.Equal (fnTrue, ml.IdleHandlers [0]);
-        Assert.NotEqual (fnFalse, ml.IdleHandlers [0]);
+        Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count);
+        Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers [0]);
+        Assert.NotEqual (fnFalse, ml.TimedEvents.IdleHandlers[0]);
 
-        Assert.True (ml.RemoveIdle (fnTrue));
-        Assert.Single (ml.IdleHandlers);
+        Assert.True (ml.TimedEvents.RemoveIdle (fnTrue));
+        Assert.Single (ml.TimedEvents.IdleHandlers);
 
         // BUGBUG: This doesn't throw or indicate an error. Ideally RemoveIdle would either 
         // throw an exception in this case, or return an error.
         // No. Only need to return a boolean.
-        Assert.False (ml.RemoveIdle (fnTrue));
+        Assert.False (ml.TimedEvents.RemoveIdle (fnTrue));
 
-        Assert.True (ml.RemoveIdle (fnFalse));
+        Assert.True (ml.TimedEvents.RemoveIdle (fnFalse));
 
         // BUGBUG: This doesn't throw an exception or indicate an error. Ideally RemoveIdle would either 
         // throw an exception in this case, or return an error.
         // No. Only need to return a boolean.
-        Assert.False (ml.RemoveIdle (fnFalse));
+        Assert.False (ml.TimedEvents.RemoveIdle (fnFalse));
 
         // Add again, but with dupe
-        ml.AddIdle (fnTrue);
-        ml.AddIdle (fnTrue);
+        ml.TimedEvents.AddIdle (fnTrue);
+        ml.TimedEvents.AddIdle (fnTrue);
 
-        Assert.Equal (2, ml.IdleHandlers.Count);
-        Assert.Equal (fnTrue, ml.IdleHandlers [0]);
-        Assert.True (ml.IdleHandlers [0] ());
-        Assert.Equal (fnTrue, ml.IdleHandlers [1]);
-        Assert.True (ml.IdleHandlers [1] ());
+        Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count);
+        Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers[0]);
+        Assert.True (ml.TimedEvents.IdleHandlers[0] ());
+        Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers[1]);
+        Assert.True (ml.TimedEvents.IdleHandlers[1] ());
 
-        Assert.True (ml.RemoveIdle (fnTrue));
-        Assert.Single (ml.IdleHandlers);
-        Assert.Equal (fnTrue, ml.IdleHandlers [0]);
-        Assert.NotEqual (fnFalse, ml.IdleHandlers [0]);
+        Assert.True (ml.TimedEvents.RemoveIdle (fnTrue));
+        Assert.Single (ml.TimedEvents.IdleHandlers);
+        Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers[0]);
+        Assert.NotEqual (fnFalse, ml.TimedEvents.IdleHandlers[0]);
 
-        Assert.True (ml.RemoveIdle (fnTrue));
-        Assert.Empty (ml.IdleHandlers);
+        Assert.True (ml.TimedEvents.RemoveIdle (fnTrue));
+        Assert.Empty (ml.TimedEvents.IdleHandlers);
 
         // BUGBUG: This doesn't throw an exception or indicate an error. Ideally RemoveIdle would either 
         // throw an exception in this case, or return an error.
         // No. Only need to return a boolean.
-        Assert.False (ml.RemoveIdle (fnTrue));
+        Assert.False (ml.TimedEvents.RemoveIdle (fnTrue));
     }
 
     [Fact]
@@ -115,7 +115,7 @@ public void AddIdle_Function_GetsCalled_OnIteration ()
                             return true;
                         };
 
-        ml.AddIdle (fn);
+        ml.TimedEvents.AddIdle (fn);
         ml.RunIteration ();
         Assert.Equal (1, functionCalled);
     }
@@ -149,13 +149,13 @@ public void AddIdle_Twice_Returns_False_Called_Twice ()
                                 return true;
                             };
 
-        ml.AddIdle (fnStop);
-        ml.AddIdle (fn1);
-        ml.AddIdle (fn1);
+        ml.TimedEvents.AddIdle (fnStop);
+        ml.TimedEvents.AddIdle (fn1);
+        ml.TimedEvents.AddIdle (fn1);
         ml.Run ();
-        Assert.True (ml.RemoveIdle (fnStop));
-        Assert.False (ml.RemoveIdle (fn1));
-        Assert.False (ml.RemoveIdle (fn1));
+        Assert.True (ml.TimedEvents.RemoveIdle (fnStop));
+        Assert.False (ml.TimedEvents.RemoveIdle (fn1));
+        Assert.False (ml.TimedEvents.RemoveIdle (fn1));
 
         Assert.Equal (2, functionCalled);
     }
@@ -174,24 +174,24 @@ public void AddIdleTwice_Function_CalledTwice ()
                             return true;
                         };
 
-        ml.AddIdle (fn);
-        ml.AddIdle (fn);
+        ml.TimedEvents.AddIdle (fn);
+        ml.TimedEvents.AddIdle (fn);
         ml.RunIteration ();
         Assert.Equal (2, functionCalled);
-        Assert.Equal (2, ml.IdleHandlers.Count);
+        Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count);
 
         functionCalled = 0;
-        Assert.True (ml.RemoveIdle (fn));
-        Assert.Single (ml.IdleHandlers);
+        Assert.True (ml.TimedEvents.RemoveIdle (fn));
+        Assert.Single (ml.TimedEvents.IdleHandlers);
         ml.RunIteration ();
         Assert.Equal (1, functionCalled);
 
         functionCalled = 0;
-        Assert.True (ml.RemoveIdle (fn));
-        Assert.Empty (ml.IdleHandlers);
+        Assert.True (ml.TimedEvents.RemoveIdle (fn));
+        Assert.Empty (ml.TimedEvents.IdleHandlers);
         ml.RunIteration ();
         Assert.Equal (0, functionCalled);
-        Assert.False (ml.RemoveIdle (fn));
+        Assert.False (ml.TimedEvents.RemoveIdle (fn));
     }
 
     [Fact]
@@ -208,8 +208,8 @@ public void AddThenRemoveIdle_Function_NotCalled ()
                             return true;
                         };
 
-        ml.AddIdle (fn);
-        Assert.True (ml.RemoveIdle (fn));
+        ml.TimedEvents.AddIdle (fn);
+        Assert.True (ml.TimedEvents.RemoveIdle (fn));
         ml.RunIteration ();
         Assert.Equal (0, functionCalled);
     }
@@ -230,13 +230,13 @@ public void AddTimer_Adds_Removes_NoFaults ()
                                   return true;
                               };
 
-        object token = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
+        object token = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
 
-        Assert.True (ml.RemoveTimeout (token));
+        Assert.True (ml.TimedEvents.RemoveTimeout (token));
 
         // BUGBUG: This should probably fault?
         // Must return a boolean.
-        Assert.False (ml.RemoveTimeout (token));
+        Assert.False (ml.TimedEvents.RemoveTimeout (token));
     }
 
     [Fact]
@@ -260,8 +260,8 @@ public async Task AddTimer_Duplicate_Keys_Not_Allowed ()
                                   return true;
                               };
 
-        var task1 = new Task (() => token1 = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback));
-        var task2 = new Task (() => token2 = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback));
+        var task1 = new Task (() => token1 = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback));
+        var task2 = new Task (() => token2 = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback));
         Assert.Null (token1);
         Assert.Null (token2);
         task1.Start ();
@@ -270,8 +270,8 @@ public async Task AddTimer_Duplicate_Keys_Not_Allowed ()
         Assert.NotNull (token1);
         Assert.NotNull (token2);
         await Task.WhenAll (task1, task2);
-        Assert.True (ml.RemoveTimeout (token1));
-        Assert.True (ml.RemoveTimeout (token2));
+        Assert.True (ml.TimedEvents.RemoveTimeout (token1));
+        Assert.True (ml.TimedEvents.RemoveTimeout (token2));
 
         Assert.Equal (2, callbackCount);
     }
@@ -297,13 +297,13 @@ public void AddTimer_EventFired ()
         object sender = null;
         TimeoutEventArgs args = null;
 
-        ml.TimeoutAdded += (s, e) =>
+        ml.TimedEvents.TimeoutAdded += (s, e) =>
                            {
                                sender = s;
                                args = e;
                            };
 
-        object token = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
+        object token = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
 
         Assert.Same (ml, sender);
         Assert.NotNull (args.Timeout);
@@ -332,14 +332,14 @@ public void AddTimer_In_Parallel_Wont_Throw ()
                               };
 
         Parallel.Invoke (
-                         () => token1 = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback),
-                         () => token2 = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback)
+                         () => token1 = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback),
+                         () => token2 = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback)
                         );
         ml.Run ();
         Assert.NotNull (token1);
         Assert.NotNull (token2);
-        Assert.True (ml.RemoveTimeout (token1));
-        Assert.True (ml.RemoveTimeout (token2));
+        Assert.True (ml.TimedEvents.RemoveTimeout (token1));
+        Assert.True (ml.TimedEvents.RemoveTimeout (token2));
 
         Assert.Equal (2, callbackCount);
     }
@@ -364,7 +364,7 @@ public void AddTimer_Remove_NotCalled ()
 
                                 return true;
                             };
-        ml.AddIdle (fnStop);
+        ml.TimedEvents.AddIdle (fnStop);
 
         var callbackCount = 0;
 
@@ -375,8 +375,8 @@ public void AddTimer_Remove_NotCalled ()
                                   return true;
                               };
 
-        object token = ml.AddTimeout (ms, callback);
-        Assert.True (ml.RemoveTimeout (token));
+        object token = ml.TimedEvents.AddTimeout (ms, callback);
+        Assert.True (ml.TimedEvents.RemoveTimeout (token));
         ml.Run ();
         Assert.Equal (0, callbackCount);
     }
@@ -402,7 +402,7 @@ public void AddTimer_ReturnFalse_StopsBeingCalled ()
 
                                 return true;
                             };
-        ml.AddIdle (fnStop);
+        ml.TimedEvents.AddIdle (fnStop);
 
         var callbackCount = 0;
 
@@ -413,11 +413,11 @@ public void AddTimer_ReturnFalse_StopsBeingCalled ()
                                   return false;
                               };
 
-        object token = ml.AddTimeout (ms, callback);
+        object token = ml.TimedEvents.AddTimeout (ms, callback);
         ml.Run ();
         Assert.Equal (1, callbackCount);
         Assert.Equal (10, stopCount);
-        Assert.False (ml.RemoveTimeout (token));
+        Assert.False (ml.TimedEvents.RemoveTimeout (token));
     }
 
     [Fact]
@@ -436,9 +436,9 @@ public void AddTimer_Run_Called ()
                                   return true;
                               };
 
-        object token = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
+        object token = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
         ml.Run ();
-        Assert.True (ml.RemoveTimeout (token));
+        Assert.True (ml.TimedEvents.RemoveTimeout (token));
 
         Assert.Equal (1, callbackCount);
     }
@@ -461,7 +461,7 @@ public void AddTimer_Run_CalledAtApproximatelyRightTime ()
                                   return true;
                               };
 
-        object token = ml.AddTimeout (ms, callback);
+        object token = ml.TimedEvents.AddTimeout (ms, callback);
         watch.Start ();
         ml.Run ();
 
@@ -469,7 +469,7 @@ public void AddTimer_Run_CalledAtApproximatelyRightTime ()
         // https://github.com/xunit/assert.xunit/pull/25
         Assert.Equal (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100));
 
-        Assert.True (ml.RemoveTimeout (token));
+        Assert.True (ml.TimedEvents.RemoveTimeout (token));
         Assert.Equal (1, callbackCount);
     }
 
@@ -495,7 +495,7 @@ public void AddTimer_Run_CalledTwiceApproximatelyRightTime ()
                                   return true;
                               };
 
-        object token = ml.AddTimeout (ms, callback);
+        object token = ml.TimedEvents.AddTimeout (ms, callback);
         watch.Start ();
         ml.Run ();
 
@@ -503,7 +503,7 @@ public void AddTimer_Run_CalledTwiceApproximatelyRightTime ()
         // https://github.com/xunit/assert.xunit/pull/25
         Assert.Equal (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100));
 
-        Assert.True (ml.RemoveTimeout (token));
+        Assert.True (ml.TimedEvents.RemoveTimeout (token));
         Assert.Equal (2, callbackCount);
     }
 
@@ -511,7 +511,7 @@ public void AddTimer_Run_CalledTwiceApproximatelyRightTime ()
     public void CheckTimersAndIdleHandlers_NoTimers_Returns_False ()
     {
         var ml = new MainLoop (new FakeMainLoop ());
-        bool retVal = ml.CheckTimersAndIdleHandlers (out int waitTimeOut);
+        bool retVal = ml.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeOut);
         Assert.False (retVal);
         Assert.Equal (-1, waitTimeOut);
     }
@@ -522,8 +522,8 @@ public void CheckTimersAndIdleHandlers_NoTimers_WithIdle_Returns_True ()
         var ml = new MainLoop (new FakeMainLoop ());
         Func<bool> fnTrue = () => true;
 
-        ml.AddIdle (fnTrue);
-        bool retVal = ml.CheckTimersAndIdleHandlers (out int waitTimeOut);
+        ml.TimedEvents.AddIdle (fnTrue);
+        bool retVal = ml.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeOut);
         Assert.True (retVal);
         Assert.Equal (-1, waitTimeOut);
     }
@@ -536,8 +536,8 @@ public void CheckTimersAndIdleHandlers_With1Timer_Returns_Timer ()
 
         static bool Callback () { return false; }
 
-        _ = ml.AddTimeout (ms, Callback);
-        bool retVal = ml.CheckTimersAndIdleHandlers (out int waitTimeOut);
+        _ = ml.TimedEvents.AddTimeout (ms, Callback);
+        bool retVal = ml.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeOut);
 
         Assert.True (retVal);
 
@@ -553,9 +553,9 @@ public void CheckTimersAndIdleHandlers_With2Timers_Returns_Timer ()
 
         static bool Callback () { return false; }
 
-        _ = ml.AddTimeout (ms, Callback);
-        _ = ml.AddTimeout (ms, Callback);
-        bool retVal = ml.CheckTimersAndIdleHandlers (out int waitTimeOut);
+        _ = ml.TimedEvents.AddTimeout (ms, Callback);
+        _ = ml.TimedEvents.AddTimeout (ms, Callback);
+        bool retVal = ml.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeOut);
 
         Assert.True (retVal);
 
@@ -597,11 +597,11 @@ public void False_Idle_Stops_It_Being_Called_Again ()
                                 return true;
                             };
 
-        ml.AddIdle (fnStop);
-        ml.AddIdle (fn1);
+        ml.TimedEvents.AddIdle (fnStop);
+        ml.TimedEvents.AddIdle (fn1);
         ml.Run ();
-        Assert.True (ml.RemoveIdle (fnStop));
-        Assert.False (ml.RemoveIdle (fn1));
+        Assert.True (ml.TimedEvents.RemoveIdle (fnStop));
+        Assert.False (ml.TimedEvents.RemoveIdle (fn1));
 
         Assert.Equal (10, functionCalled);
         Assert.Equal (20, stopCount);
@@ -612,8 +612,8 @@ public void Internal_Tests ()
     {
         var testMainloop = new TestMainloop ();
         var mainloop = new MainLoop (testMainloop);
-        Assert.Empty (mainloop._timeouts);
-        Assert.Empty (mainloop._idleHandlers);
+        Assert.Empty (mainloop.TimedEvents.Timeouts);
+        Assert.Empty (mainloop.TimedEvents.IdleHandlers);
 
         Assert.NotNull (
                         new Timeout { Span = new TimeSpan (), Callback = () => true }
@@ -748,7 +748,7 @@ public void RemoveIdle_Function_NotCalled ()
                             return true;
                         };
 
-        Assert.False (ml.RemoveIdle (fn));
+        Assert.False (ml.TimedEvents.RemoveIdle (fn));
         ml.RunIteration ();
         Assert.Equal (0, functionCalled);
     }
@@ -772,9 +772,9 @@ public void Run_Runs_Idle_Stop_Stops_Idle ()
                             return true;
                         };
 
-        ml.AddIdle (fn);
+        ml.TimedEvents.AddIdle (fn);
         ml.Run ();
-        Assert.True (ml.RemoveIdle (fn));
+        Assert.True (ml.TimedEvents.RemoveIdle (fn));
 
         Assert.Equal (10, functionCalled);
     }
diff --git a/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs b/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs
index 9720d51b5e..228d9e5726 100644
--- a/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs
+++ b/UnitTests/ConsoleDrivers/MainLoopDriverTests.cs
@@ -52,7 +52,7 @@ public void MainLoop_AddTimeout_ValidParameters_ReturnsToken (Type driverType, T
         var mainLoop = new MainLoop (mainLoopDriver);
         var callbackInvoked = false;
 
-        object token = mainLoop.AddTimeout (
+        object token = mainLoop.TimedEvents.AddTimeout (
                                             TimeSpan.FromMilliseconds (100),
                                             () =>
                                             {
@@ -88,7 +88,7 @@ Type mainLoopDriverType
         var mainLoop = new MainLoop (mainLoopDriver);
 
         mainLoop.AddIdle (() => false);
-        bool result = mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout);
+        bool result = mainLoop.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeout);
 
         Assert.True (result);
         Assert.Equal (-1, waitTimeout);
@@ -111,7 +111,7 @@ Type mainLoopDriverType
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
         var mainLoop = new MainLoop (mainLoopDriver);
 
-        bool result = mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout);
+        bool result = mainLoop.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeout);
 
         Assert.False (result);
         Assert.Equal (-1, waitTimeout);
@@ -134,8 +134,8 @@ Type mainLoopDriverType
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
         var mainLoop = new MainLoop (mainLoopDriver);
 
-        mainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), () => false);
-        bool result = mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout);
+        mainLoop.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (100), () => false);
+        bool result = mainLoop.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeout);
 
         Assert.True (result);
         Assert.True (waitTimeout >= 0);
@@ -158,8 +158,8 @@ public void MainLoop_Constructs_Disposes (Type driverType, Type mainLoopDriverTy
         // Check default values
         Assert.NotNull (mainLoop);
         Assert.Equal (mainLoopDriver, mainLoop.MainLoopDriver);
-        Assert.Empty (mainLoop.IdleHandlers);
-        Assert.Empty (mainLoop.Timeouts);
+        Assert.Empty (mainLoop.TimedEvents.IdleHandlers);
+        Assert.Empty (mainLoop.TimedEvents.Timeouts);
         Assert.False (mainLoop.Running);
 
         // Clean up
@@ -168,8 +168,8 @@ public void MainLoop_Constructs_Disposes (Type driverType, Type mainLoopDriverTy
         // TODO: It'd be nice if we could really verify IMainLoopDriver.TearDown was called
         // and that it was actually cleaned up.
         Assert.Null (mainLoop.MainLoopDriver);
-        Assert.Empty (mainLoop.IdleHandlers);
-        Assert.Empty (mainLoop.Timeouts);
+        Assert.Empty (mainLoop.TimedEvents.IdleHandlers);
+        Assert.Empty (mainLoop.TimedEvents.Timeouts);
         Assert.False (mainLoop.Running);
     }
 
@@ -186,7 +186,7 @@ public void MainLoop_RemoveIdle_InvalidToken_ReturnsFalse (Type driverType, Type
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
         var mainLoop = new MainLoop (mainLoopDriver);
 
-        bool result = mainLoop.RemoveIdle (() => false);
+        bool result = mainLoop.TimedEvents.RemoveIdle (() => false);
 
         Assert.False (result);
         mainLoop.Dispose ();
@@ -208,7 +208,7 @@ public void MainLoop_RemoveIdle_ValidToken_ReturnsTrue (Type driverType, Type ma
         bool IdleHandler () { return false; }
 
         Func<bool> token = mainLoop.AddIdle (IdleHandler);
-        bool result = mainLoop.RemoveIdle (token);
+        bool result = mainLoop.TimedEvents.RemoveIdle (token);
 
         Assert.True (result);
         mainLoop.Dispose ();
@@ -227,7 +227,7 @@ public void MainLoop_RemoveTimeout_InvalidToken_ReturnsFalse (Type driverType, T
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
         var mainLoop = new MainLoop (mainLoopDriver);
 
-        bool result = mainLoop.RemoveTimeout (new object ());
+        bool result = mainLoop.TimedEvents.RemoveTimeout (new object ());
 
         Assert.False (result);
     }
@@ -245,8 +245,8 @@ public void MainLoop_RemoveTimeout_ValidToken_ReturnsTrue (Type driverType, Type
         var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
         var mainLoop = new MainLoop (mainLoopDriver);
 
-        object token = mainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), () => false);
-        bool result = mainLoop.RemoveTimeout (token);
+        object token = mainLoop.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (100), () => false);
+        bool result = mainLoop.TimedEvents.RemoveTimeout (token);
 
         Assert.True (result);
         mainLoop.Dispose ();
diff --git a/UnitTests/UICatalog/ScenarioTests.cs b/UnitTests/UICatalog/ScenarioTests.cs
index c16a2549ff..09ba8b7543 100644
--- a/UnitTests/UICatalog/ScenarioTests.cs
+++ b/UnitTests/UICatalog/ScenarioTests.cs
@@ -805,7 +805,7 @@ public void Run_Generic ()
             if (token == null)
             {
                 // Timeout only must start at first iteration
-                token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (ms), abortCallback);
+                token = Application.AddTimeout (TimeSpan.FromMilliseconds (ms), abortCallback);
             }
 
             iterations++;
diff --git a/UnitTests/Views/SpinnerViewTests.cs b/UnitTests/Views/SpinnerViewTests.cs
index b8e62f8281..0af6f0b50a 100644
--- a/UnitTests/Views/SpinnerViewTests.cs
+++ b/UnitTests/Views/SpinnerViewTests.cs
@@ -15,33 +15,33 @@ public void TestSpinnerView_AutoSpin (bool callStop)
     {
         SpinnerView view = GetSpinnerView ();
 
-        Assert.Empty (Application.MainLoop._timeouts);
+        Assert.Empty (Application.MainLoop.TimedEvents.Timeouts);
         view.AutoSpin = true;
-        Assert.NotEmpty (Application.MainLoop._timeouts);
+        Assert.NotEmpty (Application.MainLoop.TimedEvents.Timeouts);
         Assert.True (view.AutoSpin);
 
         //More calls to AutoSpin do not add more timeouts
-        Assert.Single (Application.MainLoop._timeouts);
+        Assert.Single (Application.MainLoop.TimedEvents.Timeouts);
         view.AutoSpin = true;
         view.AutoSpin = true;
         view.AutoSpin = true;
         Assert.True (view.AutoSpin);
-        Assert.Single (Application.MainLoop._timeouts);
+        Assert.Single (Application.MainLoop.TimedEvents.Timeouts);
 
         if (callStop)
         {
             view.AutoSpin = false;
-            Assert.Empty (Application.MainLoop._timeouts);
+            Assert.Empty (Application.MainLoop.TimedEvents.Timeouts);
             Assert.False (view.AutoSpin);
         }
         else
         {
-            Assert.NotEmpty (Application.MainLoop._timeouts);
+            Assert.NotEmpty (Application.MainLoop.TimedEvents.Timeouts);
         }
 
         // Dispose clears timeout
         view.Dispose ();
-        Assert.Empty (Application.MainLoop._timeouts);
+        Assert.Empty (Application.MainLoop.TimedEvents.Timeouts);
         Application.Top.Dispose ();
     }
 

From 634693a73af7142dc5ff2cdb7954c5d7e0694a60 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 7 Dec 2024 21:09:49 +0000
Subject: [PATCH 050/198] WIP try to resolve the fact that timeouts/locks are
 deadlock city

---
 Terminal.Gui/Application/MainLoop.cs    |  4 +--
 Terminal.Gui/Application/TimedEvents.cs | 19 +++++-----
 UnitTests/Application/MainLoopTests.cs  | 48 +++++++++++++------------
 3 files changed, 36 insertions(+), 35 deletions(-)

diff --git a/Terminal.Gui/Application/MainLoop.cs b/Terminal.Gui/Application/MainLoop.cs
index 445848b5c1..7eb9197a74 100644
--- a/Terminal.Gui/Application/MainLoop.cs
+++ b/Terminal.Gui/Application/MainLoop.cs
@@ -135,9 +135,9 @@ internal void RunIteration ()
 
         MainLoopDriver?.Iteration ();
 
-        TimedEvents.LockAndRunIdles ();
-
+        TimedEvents.LockAndRunTimers ();
 
+        TimedEvents.LockAndRunIdles ();
     }
 
     private void RunAnsiScheduler ()
diff --git a/Terminal.Gui/Application/TimedEvents.cs b/Terminal.Gui/Application/TimedEvents.cs
index bd493a9ade..95ddb95ed1 100644
--- a/Terminal.Gui/Application/TimedEvents.cs
+++ b/Terminal.Gui/Application/TimedEvents.cs
@@ -1,16 +1,13 @@
-using System.Collections.ObjectModel;
+using System.Collections.Concurrent;
+using System.Collections.ObjectModel;
 
 namespace Terminal.Gui;
 
 public class TimedEvents : ITimedEvents
 {
-    internal List<Func<bool>> _idleHandlers = new ();
-    internal SortedList<long, Timeout> _timeouts = new ();
+    internal ConcurrentBag<Func<bool>> _idleHandlers = new ();
+    internal ConcurrentDictionary<long, Timeout> _timeouts = new ();
 
-    /// <summary>The idle handlers and lock that must be held while manipulating them</summary>
-    private readonly object _idleHandlersLock = new ();
-
-    private readonly object _timeoutsLockToken = new ();
 
 
     /// <summary>Gets a copy of the list of all idle handlers.</summary>
@@ -18,10 +15,8 @@ public ReadOnlyCollection<Func<bool>> IdleHandlers
     {
         get
         {
-            lock (_idleHandlersLock)
-            {
-                return new List<Func<bool>> (_idleHandlers).AsReadOnly ();
-            }
+                return _idleHandlers.ToList ().AsReadOnly ();
+            
         }
     }
 
@@ -270,6 +265,8 @@ public interface ITimedEvents
 {
     void AddIdle (Func<bool> idleHandler);
     void LockAndRunIdles ();
+    void LockAndRunTimers ();
+
     bool CheckTimersAndIdleHandlers (out int waitTimeout);
 
     /// <summary>Adds a timeout to the application.</summary>
diff --git a/UnitTests/Application/MainLoopTests.cs b/UnitTests/Application/MainLoopTests.cs
index 221dbda72c..018e0426ca 100644
--- a/UnitTests/Application/MainLoopTests.cs
+++ b/UnitTests/Application/MainLoopTests.cs
@@ -55,8 +55,8 @@ public void AddIdle_Adds_And_Removes ()
         Func<bool> fnTrue = () => true;
         Func<bool> fnFalse = () => false;
 
-        ml.TimedEvents.AddIdle (fnTrue);
-        ml.TimedEvents.AddIdle (fnFalse);
+        ml.AddIdle (fnTrue);
+        ml.AddIdle (fnFalse);
 
         Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count);
         Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers [0]);
@@ -78,8 +78,8 @@ public void AddIdle_Adds_And_Removes ()
         Assert.False (ml.TimedEvents.RemoveIdle (fnFalse));
 
         // Add again, but with dupe
-        ml.TimedEvents.AddIdle (fnTrue);
-        ml.TimedEvents.AddIdle (fnTrue);
+        ml.AddIdle (fnTrue);
+        ml.AddIdle (fnTrue);
 
         Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count);
         Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers[0]);
@@ -101,7 +101,8 @@ public void AddIdle_Adds_And_Removes ()
         Assert.False (ml.TimedEvents.RemoveIdle (fnTrue));
     }
 
-    [Fact]
+    // TODO: Fix these tests that hang forever
+    //[Fact]
     public void AddIdle_Function_GetsCalled_OnIteration ()
     {
         var ml = new MainLoop (new FakeMainLoop ());
@@ -115,7 +116,7 @@ public void AddIdle_Function_GetsCalled_OnIteration ()
                             return true;
                         };
 
-        ml.TimedEvents.AddIdle (fn);
+        ml.AddIdle (fn);
         ml.RunIteration ();
         Assert.Equal (1, functionCalled);
     }
@@ -149,9 +150,9 @@ public void AddIdle_Twice_Returns_False_Called_Twice ()
                                 return true;
                             };
 
-        ml.TimedEvents.AddIdle (fnStop);
-        ml.TimedEvents.AddIdle (fn1);
-        ml.TimedEvents.AddIdle (fn1);
+        ml.AddIdle (fnStop);
+        ml.AddIdle (fn1);
+        ml.AddIdle (fn1);
         ml.Run ();
         Assert.True (ml.TimedEvents.RemoveIdle (fnStop));
         Assert.False (ml.TimedEvents.RemoveIdle (fn1));
@@ -174,8 +175,8 @@ public void AddIdleTwice_Function_CalledTwice ()
                             return true;
                         };
 
-        ml.TimedEvents.AddIdle (fn);
-        ml.TimedEvents.AddIdle (fn);
+        ml.AddIdle (fn);
+        ml.AddIdle (fn);
         ml.RunIteration ();
         Assert.Equal (2, functionCalled);
         Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count);
@@ -208,7 +209,7 @@ public void AddThenRemoveIdle_Function_NotCalled ()
                             return true;
                         };
 
-        ml.TimedEvents.AddIdle (fn);
+        ml.AddIdle (fn);
         Assert.True (ml.TimedEvents.RemoveIdle (fn));
         ml.RunIteration ();
         Assert.Equal (0, functionCalled);
@@ -305,7 +306,7 @@ public void AddTimer_EventFired ()
 
         object token = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
 
-        Assert.Same (ml, sender);
+        Assert.Same (ml.TimedEvents, sender);
         Assert.NotNull (args.Timeout);
         Assert.True (args.Ticks - originTicks >= 100 * TimeSpan.TicksPerMillisecond);
     }
@@ -364,7 +365,7 @@ public void AddTimer_Remove_NotCalled ()
 
                                 return true;
                             };
-        ml.TimedEvents.AddIdle (fnStop);
+        ml.AddIdle (fnStop);
 
         var callbackCount = 0;
 
@@ -402,7 +403,7 @@ public void AddTimer_ReturnFalse_StopsBeingCalled ()
 
                                 return true;
                             };
-        ml.TimedEvents.AddIdle (fnStop);
+        ml.AddIdle (fnStop);
 
         var callbackCount = 0;
 
@@ -420,7 +421,8 @@ public void AddTimer_ReturnFalse_StopsBeingCalled ()
         Assert.False (ml.TimedEvents.RemoveTimeout (token));
     }
 
-    [Fact]
+    // TODO: Fix these tests that hang forever
+    //[Fact]
     public void AddTimer_Run_Called ()
     {
         var ml = new MainLoop (new FakeMainLoop ());
@@ -473,7 +475,8 @@ public void AddTimer_Run_CalledAtApproximatelyRightTime ()
         Assert.Equal (1, callbackCount);
     }
 
-    [Fact]
+    // TODO: Fix these tests that hang forever
+    //[Fact]
     public void AddTimer_Run_CalledTwiceApproximatelyRightTime ()
     {
         var ml = new MainLoop (new FakeMainLoop ());
@@ -522,7 +525,7 @@ public void CheckTimersAndIdleHandlers_NoTimers_WithIdle_Returns_True ()
         var ml = new MainLoop (new FakeMainLoop ());
         Func<bool> fnTrue = () => true;
 
-        ml.TimedEvents.AddIdle (fnTrue);
+        ml.AddIdle (fnTrue);
         bool retVal = ml.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeOut);
         Assert.True (retVal);
         Assert.Equal (-1, waitTimeOut);
@@ -597,8 +600,8 @@ public void False_Idle_Stops_It_Being_Called_Again ()
                                 return true;
                             };
 
-        ml.TimedEvents.AddIdle (fnStop);
-        ml.TimedEvents.AddIdle (fn1);
+        ml.AddIdle (fnStop);
+        ml.AddIdle (fn1);
         ml.Run ();
         Assert.True (ml.TimedEvents.RemoveIdle (fnStop));
         Assert.False (ml.TimedEvents.RemoveIdle (fn1));
@@ -734,7 +737,8 @@ int pfour
         Application.Shutdown ();
     }
 
-    [Fact]
+    // TODO: Fix these tests that hang forever
+    //[Fact]
     public void RemoveIdle_Function_NotCalled ()
     {
         var ml = new MainLoop (new FakeMainLoop ());
@@ -772,7 +776,7 @@ public void Run_Runs_Idle_Stop_Stops_Idle ()
                             return true;
                         };
 
-        ml.TimedEvents.AddIdle (fn);
+        ml.AddIdle (fn);
         ml.Run ();
         Assert.True (ml.TimedEvents.RemoveIdle (fn));
 

From 00990ba42957b935f6de82b0feaa81d4274d611f Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 8 Dec 2024 09:51:41 +0000
Subject: [PATCH 051/198] Revert "WIP try to resolve the fact that
 timeouts/locks are deadlock city"

This reverts commit 634693a73af7142dc5ff2cdb7954c5d7e0694a60.
---
 Terminal.Gui/Application/MainLoop.cs    |  4 +--
 Terminal.Gui/Application/TimedEvents.cs | 19 +++++-----
 UnitTests/Application/MainLoopTests.cs  | 48 ++++++++++++-------------
 3 files changed, 35 insertions(+), 36 deletions(-)

diff --git a/Terminal.Gui/Application/MainLoop.cs b/Terminal.Gui/Application/MainLoop.cs
index 7eb9197a74..445848b5c1 100644
--- a/Terminal.Gui/Application/MainLoop.cs
+++ b/Terminal.Gui/Application/MainLoop.cs
@@ -135,9 +135,9 @@ internal void RunIteration ()
 
         MainLoopDriver?.Iteration ();
 
-        TimedEvents.LockAndRunTimers ();
-
         TimedEvents.LockAndRunIdles ();
+
+
     }
 
     private void RunAnsiScheduler ()
diff --git a/Terminal.Gui/Application/TimedEvents.cs b/Terminal.Gui/Application/TimedEvents.cs
index 95ddb95ed1..bd493a9ade 100644
--- a/Terminal.Gui/Application/TimedEvents.cs
+++ b/Terminal.Gui/Application/TimedEvents.cs
@@ -1,13 +1,16 @@
-using System.Collections.Concurrent;
-using System.Collections.ObjectModel;
+using System.Collections.ObjectModel;
 
 namespace Terminal.Gui;
 
 public class TimedEvents : ITimedEvents
 {
-    internal ConcurrentBag<Func<bool>> _idleHandlers = new ();
-    internal ConcurrentDictionary<long, Timeout> _timeouts = new ();
+    internal List<Func<bool>> _idleHandlers = new ();
+    internal SortedList<long, Timeout> _timeouts = new ();
 
+    /// <summary>The idle handlers and lock that must be held while manipulating them</summary>
+    private readonly object _idleHandlersLock = new ();
+
+    private readonly object _timeoutsLockToken = new ();
 
 
     /// <summary>Gets a copy of the list of all idle handlers.</summary>
@@ -15,8 +18,10 @@ public ReadOnlyCollection<Func<bool>> IdleHandlers
     {
         get
         {
-                return _idleHandlers.ToList ().AsReadOnly ();
-            
+            lock (_idleHandlersLock)
+            {
+                return new List<Func<bool>> (_idleHandlers).AsReadOnly ();
+            }
         }
     }
 
@@ -265,8 +270,6 @@ public interface ITimedEvents
 {
     void AddIdle (Func<bool> idleHandler);
     void LockAndRunIdles ();
-    void LockAndRunTimers ();
-
     bool CheckTimersAndIdleHandlers (out int waitTimeout);
 
     /// <summary>Adds a timeout to the application.</summary>
diff --git a/UnitTests/Application/MainLoopTests.cs b/UnitTests/Application/MainLoopTests.cs
index 018e0426ca..221dbda72c 100644
--- a/UnitTests/Application/MainLoopTests.cs
+++ b/UnitTests/Application/MainLoopTests.cs
@@ -55,8 +55,8 @@ public void AddIdle_Adds_And_Removes ()
         Func<bool> fnTrue = () => true;
         Func<bool> fnFalse = () => false;
 
-        ml.AddIdle (fnTrue);
-        ml.AddIdle (fnFalse);
+        ml.TimedEvents.AddIdle (fnTrue);
+        ml.TimedEvents.AddIdle (fnFalse);
 
         Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count);
         Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers [0]);
@@ -78,8 +78,8 @@ public void AddIdle_Adds_And_Removes ()
         Assert.False (ml.TimedEvents.RemoveIdle (fnFalse));
 
         // Add again, but with dupe
-        ml.AddIdle (fnTrue);
-        ml.AddIdle (fnTrue);
+        ml.TimedEvents.AddIdle (fnTrue);
+        ml.TimedEvents.AddIdle (fnTrue);
 
         Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count);
         Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers[0]);
@@ -101,8 +101,7 @@ public void AddIdle_Adds_And_Removes ()
         Assert.False (ml.TimedEvents.RemoveIdle (fnTrue));
     }
 
-    // TODO: Fix these tests that hang forever
-    //[Fact]
+    [Fact]
     public void AddIdle_Function_GetsCalled_OnIteration ()
     {
         var ml = new MainLoop (new FakeMainLoop ());
@@ -116,7 +115,7 @@ public void AddIdle_Function_GetsCalled_OnIteration ()
                             return true;
                         };
 
-        ml.AddIdle (fn);
+        ml.TimedEvents.AddIdle (fn);
         ml.RunIteration ();
         Assert.Equal (1, functionCalled);
     }
@@ -150,9 +149,9 @@ public void AddIdle_Twice_Returns_False_Called_Twice ()
                                 return true;
                             };
 
-        ml.AddIdle (fnStop);
-        ml.AddIdle (fn1);
-        ml.AddIdle (fn1);
+        ml.TimedEvents.AddIdle (fnStop);
+        ml.TimedEvents.AddIdle (fn1);
+        ml.TimedEvents.AddIdle (fn1);
         ml.Run ();
         Assert.True (ml.TimedEvents.RemoveIdle (fnStop));
         Assert.False (ml.TimedEvents.RemoveIdle (fn1));
@@ -175,8 +174,8 @@ public void AddIdleTwice_Function_CalledTwice ()
                             return true;
                         };
 
-        ml.AddIdle (fn);
-        ml.AddIdle (fn);
+        ml.TimedEvents.AddIdle (fn);
+        ml.TimedEvents.AddIdle (fn);
         ml.RunIteration ();
         Assert.Equal (2, functionCalled);
         Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count);
@@ -209,7 +208,7 @@ public void AddThenRemoveIdle_Function_NotCalled ()
                             return true;
                         };
 
-        ml.AddIdle (fn);
+        ml.TimedEvents.AddIdle (fn);
         Assert.True (ml.TimedEvents.RemoveIdle (fn));
         ml.RunIteration ();
         Assert.Equal (0, functionCalled);
@@ -306,7 +305,7 @@ public void AddTimer_EventFired ()
 
         object token = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
 
-        Assert.Same (ml.TimedEvents, sender);
+        Assert.Same (ml, sender);
         Assert.NotNull (args.Timeout);
         Assert.True (args.Ticks - originTicks >= 100 * TimeSpan.TicksPerMillisecond);
     }
@@ -365,7 +364,7 @@ public void AddTimer_Remove_NotCalled ()
 
                                 return true;
                             };
-        ml.AddIdle (fnStop);
+        ml.TimedEvents.AddIdle (fnStop);
 
         var callbackCount = 0;
 
@@ -403,7 +402,7 @@ public void AddTimer_ReturnFalse_StopsBeingCalled ()
 
                                 return true;
                             };
-        ml.AddIdle (fnStop);
+        ml.TimedEvents.AddIdle (fnStop);
 
         var callbackCount = 0;
 
@@ -421,8 +420,7 @@ public void AddTimer_ReturnFalse_StopsBeingCalled ()
         Assert.False (ml.TimedEvents.RemoveTimeout (token));
     }
 
-    // TODO: Fix these tests that hang forever
-    //[Fact]
+    [Fact]
     public void AddTimer_Run_Called ()
     {
         var ml = new MainLoop (new FakeMainLoop ());
@@ -475,8 +473,7 @@ public void AddTimer_Run_CalledAtApproximatelyRightTime ()
         Assert.Equal (1, callbackCount);
     }
 
-    // TODO: Fix these tests that hang forever
-    //[Fact]
+    [Fact]
     public void AddTimer_Run_CalledTwiceApproximatelyRightTime ()
     {
         var ml = new MainLoop (new FakeMainLoop ());
@@ -525,7 +522,7 @@ public void CheckTimersAndIdleHandlers_NoTimers_WithIdle_Returns_True ()
         var ml = new MainLoop (new FakeMainLoop ());
         Func<bool> fnTrue = () => true;
 
-        ml.AddIdle (fnTrue);
+        ml.TimedEvents.AddIdle (fnTrue);
         bool retVal = ml.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeOut);
         Assert.True (retVal);
         Assert.Equal (-1, waitTimeOut);
@@ -600,8 +597,8 @@ public void False_Idle_Stops_It_Being_Called_Again ()
                                 return true;
                             };
 
-        ml.AddIdle (fnStop);
-        ml.AddIdle (fn1);
+        ml.TimedEvents.AddIdle (fnStop);
+        ml.TimedEvents.AddIdle (fn1);
         ml.Run ();
         Assert.True (ml.TimedEvents.RemoveIdle (fnStop));
         Assert.False (ml.TimedEvents.RemoveIdle (fn1));
@@ -737,8 +734,7 @@ int pfour
         Application.Shutdown ();
     }
 
-    // TODO: Fix these tests that hang forever
-    //[Fact]
+    [Fact]
     public void RemoveIdle_Function_NotCalled ()
     {
         var ml = new MainLoop (new FakeMainLoop ());
@@ -776,7 +772,7 @@ public void Run_Runs_Idle_Stop_Stops_Idle ()
                             return true;
                         };
 
-        ml.AddIdle (fn);
+        ml.TimedEvents.AddIdle (fn);
         ml.Run ();
         Assert.True (ml.TimedEvents.RemoveIdle (fn));
 

From 4482c217ea3fcc28ec8bf990ab6064df7193d5f7 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 8 Dec 2024 09:58:20 +0000
Subject: [PATCH 052/198] Fix reverted TimedEvents to operate the same as
 before - we can refactor this later

---
 Terminal.Gui/Application/MainLoop.cs    |  4 +--
 Terminal.Gui/Application/TimedEvents.cs | 10 ++++---
 UnitTests/Application/MainLoopTests.cs  | 36 ++++++++++++-------------
 3 files changed, 26 insertions(+), 24 deletions(-)

diff --git a/Terminal.Gui/Application/MainLoop.cs b/Terminal.Gui/Application/MainLoop.cs
index 445848b5c1..7eb9197a74 100644
--- a/Terminal.Gui/Application/MainLoop.cs
+++ b/Terminal.Gui/Application/MainLoop.cs
@@ -135,9 +135,9 @@ internal void RunIteration ()
 
         MainLoopDriver?.Iteration ();
 
-        TimedEvents.LockAndRunIdles ();
-
+        TimedEvents.LockAndRunTimers ();
 
+        TimedEvents.LockAndRunIdles ();
     }
 
     private void RunAnsiScheduler ()
diff --git a/Terminal.Gui/Application/TimedEvents.cs b/Terminal.Gui/Application/TimedEvents.cs
index bd493a9ade..2c2a667d59 100644
--- a/Terminal.Gui/Application/TimedEvents.cs
+++ b/Terminal.Gui/Application/TimedEvents.cs
@@ -117,10 +117,11 @@ public void LockAndRunIdles ()
         lock (_idleHandlersLock)
         {
             runIdle = _idleHandlers.Count > 0;
-            if (runIdle)
-            {
-                RunIdle ();
-            }
+        }
+
+        if (runIdle)
+        {
+            RunIdle ();
         }
     }
     private void RunTimers ()
@@ -270,6 +271,7 @@ public interface ITimedEvents
 {
     void AddIdle (Func<bool> idleHandler);
     void LockAndRunIdles ();
+    void LockAndRunTimers ();
     bool CheckTimersAndIdleHandlers (out int waitTimeout);
 
     /// <summary>Adds a timeout to the application.</summary>
diff --git a/UnitTests/Application/MainLoopTests.cs b/UnitTests/Application/MainLoopTests.cs
index 221dbda72c..8204142746 100644
--- a/UnitTests/Application/MainLoopTests.cs
+++ b/UnitTests/Application/MainLoopTests.cs
@@ -55,8 +55,8 @@ public void AddIdle_Adds_And_Removes ()
         Func<bool> fnTrue = () => true;
         Func<bool> fnFalse = () => false;
 
-        ml.TimedEvents.AddIdle (fnTrue);
-        ml.TimedEvents.AddIdle (fnFalse);
+        ml.AddIdle (fnTrue);
+        ml.AddIdle (fnFalse);
 
         Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count);
         Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers [0]);
@@ -78,8 +78,8 @@ public void AddIdle_Adds_And_Removes ()
         Assert.False (ml.TimedEvents.RemoveIdle (fnFalse));
 
         // Add again, but with dupe
-        ml.TimedEvents.AddIdle (fnTrue);
-        ml.TimedEvents.AddIdle (fnTrue);
+        ml.AddIdle (fnTrue);
+        ml.AddIdle (fnTrue);
 
         Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count);
         Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers[0]);
@@ -115,7 +115,7 @@ public void AddIdle_Function_GetsCalled_OnIteration ()
                             return true;
                         };
 
-        ml.TimedEvents.AddIdle (fn);
+        ml.AddIdle (fn);
         ml.RunIteration ();
         Assert.Equal (1, functionCalled);
     }
@@ -149,9 +149,9 @@ public void AddIdle_Twice_Returns_False_Called_Twice ()
                                 return true;
                             };
 
-        ml.TimedEvents.AddIdle (fnStop);
-        ml.TimedEvents.AddIdle (fn1);
-        ml.TimedEvents.AddIdle (fn1);
+        ml.AddIdle (fnStop);
+        ml.AddIdle (fn1);
+        ml.AddIdle (fn1);
         ml.Run ();
         Assert.True (ml.TimedEvents.RemoveIdle (fnStop));
         Assert.False (ml.TimedEvents.RemoveIdle (fn1));
@@ -174,8 +174,8 @@ public void AddIdleTwice_Function_CalledTwice ()
                             return true;
                         };
 
-        ml.TimedEvents.AddIdle (fn);
-        ml.TimedEvents.AddIdle (fn);
+        ml.AddIdle (fn);
+        ml.AddIdle (fn);
         ml.RunIteration ();
         Assert.Equal (2, functionCalled);
         Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count);
@@ -208,7 +208,7 @@ public void AddThenRemoveIdle_Function_NotCalled ()
                             return true;
                         };
 
-        ml.TimedEvents.AddIdle (fn);
+        ml.AddIdle (fn);
         Assert.True (ml.TimedEvents.RemoveIdle (fn));
         ml.RunIteration ();
         Assert.Equal (0, functionCalled);
@@ -305,7 +305,7 @@ public void AddTimer_EventFired ()
 
         object token = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
 
-        Assert.Same (ml, sender);
+        Assert.Same (ml.TimedEvents, sender);
         Assert.NotNull (args.Timeout);
         Assert.True (args.Ticks - originTicks >= 100 * TimeSpan.TicksPerMillisecond);
     }
@@ -364,7 +364,7 @@ public void AddTimer_Remove_NotCalled ()
 
                                 return true;
                             };
-        ml.TimedEvents.AddIdle (fnStop);
+        ml.AddIdle (fnStop);
 
         var callbackCount = 0;
 
@@ -402,7 +402,7 @@ public void AddTimer_ReturnFalse_StopsBeingCalled ()
 
                                 return true;
                             };
-        ml.TimedEvents.AddIdle (fnStop);
+        ml.AddIdle (fnStop);
 
         var callbackCount = 0;
 
@@ -522,7 +522,7 @@ public void CheckTimersAndIdleHandlers_NoTimers_WithIdle_Returns_True ()
         var ml = new MainLoop (new FakeMainLoop ());
         Func<bool> fnTrue = () => true;
 
-        ml.TimedEvents.AddIdle (fnTrue);
+        ml.AddIdle (fnTrue);
         bool retVal = ml.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeOut);
         Assert.True (retVal);
         Assert.Equal (-1, waitTimeOut);
@@ -597,8 +597,8 @@ public void False_Idle_Stops_It_Being_Called_Again ()
                                 return true;
                             };
 
-        ml.TimedEvents.AddIdle (fnStop);
-        ml.TimedEvents.AddIdle (fn1);
+        ml.AddIdle (fnStop);
+        ml.AddIdle (fn1);
         ml.Run ();
         Assert.True (ml.TimedEvents.RemoveIdle (fnStop));
         Assert.False (ml.TimedEvents.RemoveIdle (fn1));
@@ -772,7 +772,7 @@ public void Run_Runs_Idle_Stop_Stops_Idle ()
                             return true;
                         };
 
-        ml.TimedEvents.AddIdle (fn);
+        ml.AddIdle (fn);
         ml.Run ();
         Assert.True (ml.TimedEvents.RemoveIdle (fn));
 

From 3ea02ca4e4294ac231e798c2d466dc226febef15 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 8 Dec 2024 10:24:56 +0000
Subject: [PATCH 053/198] V2 TimedEvents support

---
 Terminal.Gui/Application/ApplicationImpl.cs   |  2 +-
 .../ConsoleDrivers/V2/ApplicationV2.cs        | 27 +++++++++++++++----
 Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs   |  4 ++-
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    |  8 +++++-
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  |  8 +++---
 5 files changed, 38 insertions(+), 11 deletions(-)

diff --git a/Terminal.Gui/Application/ApplicationImpl.cs b/Terminal.Gui/Application/ApplicationImpl.cs
index c459e5855d..2e43b24772 100644
--- a/Terminal.Gui/Application/ApplicationImpl.cs
+++ b/Terminal.Gui/Application/ApplicationImpl.cs
@@ -271,6 +271,6 @@ public virtual object AddTimeout (TimeSpan time, Func<bool> callback)
     /// <inheritdoc />
     public virtual bool RemoveTimeout (object token)
     { 
-        return Application.MainLoop?.TimedEvents.RemoveTimeout (token) ?? false; 
+        return Application.MainLoop?.TimedEvents.RemoveTimeout (token) ?? false;
     }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index 220e687a47..50e3fc34ac 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -6,7 +6,7 @@ namespace Terminal.Gui.ConsoleDrivers.V2;
 public class ApplicationV2 : ApplicationImpl
 {
     private IMainLoopCoordinator _coordinator;
-
+    public ITimedEvents TimedEvents { get; } = new TimedEvents ();
     public ApplicationV2 ()
     {
         IsLegacy = false;
@@ -46,7 +46,8 @@ private void CreateDriver ()
         {*/
             var inputBuffer = new ConcurrentQueue<ConsoleKeyInfo> ();
             var loop = new MainLoop<ConsoleKeyInfo> ();
-            _coordinator = new MainLoopCoordinator<ConsoleKeyInfo> (() => new NetInput (),
+            _coordinator = new MainLoopCoordinator<ConsoleKeyInfo> (TimedEvents,
+                                                                    () => new NetInput (),
                                                                    inputBuffer,
                                                                    new NetInputProcessor (inputBuffer),
                                                                    () => new NetOutput (),
@@ -122,14 +123,30 @@ public override void RequestStop (Toplevel top)
     /// <inheritdoc />
     public override void Invoke (Action action)
     {
-        // TODO
+        TimedEvents.AddIdle (() =>
+                               {
+                                   action ();
+
+                                   return false;
+                               }
+                              );
     }
 
     /// <inheritdoc />
     public override void AddIdle (Func<bool> func)
     {
-        // TODO properly
+        TimedEvents.AddIdle (func);
+    }
 
-        func.Invoke ();
+    /// <inheritdoc />
+    public override object AddTimeout (TimeSpan time, Func<bool> callback)
+    {
+        return TimedEvents.AddTimeout(time,callback);
+    }
+
+    /// <inheritdoc />
+    public override bool RemoveTimeout (object token)
+    {
+        return TimedEvents.RemoveTimeout (token);
     }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
index fb616785e8..fbf369eb4c 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
@@ -4,6 +4,7 @@ namespace Terminal.Gui;
 
 public interface IMainLoop<T> : IDisposable
 {
+    public ITimedEvents TimedEvents { get; }
     public IOutputBuffer OutputBuffer { get; }
     public IInputProcessor InputProcessor { get; }
 
@@ -12,10 +13,11 @@ public interface IMainLoop<T> : IDisposable
     /// <summary>
     /// Initializes the loop with a buffer from which data can be read
     /// </summary>
+    /// <param name="timedEvents"></param>
     /// <param name="inputBuffer"></param>
     /// <param name="inputProcessor"></param>
     /// <param name="consoleOutput"></param>
-    void Initialize (ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput);
+    void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput);
 
     /// <summary>
     /// Runs <see cref="Iteration"/> in an infinite loop.
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index b037f6a3ec..8adbd2df44 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -4,6 +4,7 @@ namespace Terminal.Gui;
 
 public class MainLoop<T> : IMainLoop<T>
 {
+    public ITimedEvents TimedEvents { get; private set; }
     public ConcurrentQueue<T> InputBuffer { get; private set; } = new ();
 
     public IInputProcessor InputProcessor { get; private set; }
@@ -13,12 +14,13 @@ public class MainLoop<T> : IMainLoop<T>
     public IConsoleOutput Out { get;private set; }
     public AnsiRequestScheduler AnsiRequestScheduler { get; private set; }
 
-    public void Initialize (ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput)
+    public void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput)
     {
         InputBuffer = inputBuffer;
         Out = consoleOutput;
         InputProcessor = inputProcessor;
 
+        TimedEvents = timedEvents;
         AnsiRequestScheduler = new AnsiRequestScheduler (InputProcessor.GetParser ());
 
     }
@@ -61,6 +63,10 @@ public void Iteration ()
         }
 
         Out.Write (OutputBuffer);
+
+        TimedEvents.LockAndRunTimers ();
+
+        TimedEvents.LockAndRunIdles ();
     }
 
     /// <inheritdoc />
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index 42dbed0cdb..7e2d86c914 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -17,13 +17,14 @@ public class MainLoopCoordinator<T> : IMainLoopCoordinator
     private ConsoleDriverFacade<T> _facade;
     private Task _inputTask;
     private Task _loopTask;
+    private ITimedEvents _timedEvents;
 
     public SemaphoreSlim StartupSemaphore { get; } = new (0, 1);
 
-
     /// <summary>
     /// Creates a new coordinator
     /// </summary>
+    /// <param name="timedEvents"></param>
     /// <param name="inputFactory">Function to create a new input. This must call <see langword="new"/>
     ///     explicitly and cannot return an existing instance. This requirement arises because Windows
     ///     console screen buffer APIs are thread-specific for certain operations.</param>
@@ -33,8 +34,9 @@ public class MainLoopCoordinator<T> : IMainLoopCoordinator
     ///     explicitly and cannot return an existing instance. This requirement arises because Windows
     ///     console screen buffer APIs are thread-specific for certain operations.</param>
     /// <param name="loop"></param>
-    public MainLoopCoordinator (Func<IConsoleInput<T>> inputFactory, ConcurrentQueue<T> inputBuffer,IInputProcessor inputProcessor, Func<IConsoleOutput> outputFactory, IMainLoop<T> loop)
+    public MainLoopCoordinator (ITimedEvents timedEvents, Func<IConsoleInput<T>> inputFactory, ConcurrentQueue<T> inputBuffer,IInputProcessor inputProcessor, Func<IConsoleOutput> outputFactory, IMainLoop<T> loop)
     {
+        _timedEvents = timedEvents;
         _inputFactory = inputFactory;
         _inputBuffer = inputBuffer;
         _inputProcessor = inputProcessor;
@@ -78,7 +80,7 @@ private void RunLoop ()
         {
             // Instance must be constructed on the thread in which it is used.
             _output = _outputFactory.Invoke ();
-            _loop.Initialize (_inputBuffer, _inputProcessor, _output);
+            _loop.Initialize (_timedEvents, _inputBuffer, _inputProcessor, _output);
 
             BuildFacadeIfPossible ();
         }

From 2650e36d063204c0b976da078070c8e1dcfdf8f6 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 8 Dec 2024 10:26:31 +0000
Subject: [PATCH 054/198] Add timed events to class diagram

---
 Terminal.Gui/ConsoleDrivers/V2/V2.cd | 35 +++++++++++++++++++---------
 1 file changed, 24 insertions(+), 11 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index f24f147e84..60172ab857 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -51,7 +51,7 @@
   </Class>
   <Class Name="Terminal.Gui.MainLoop&lt;T&gt;" Collapsed="true" BaseTypeListCollapsed="true">
     <Position X="11" Y="4.75" Width="1.5" />
-    <AssociationLine Name="AnsiRequestScheduler" Type="Terminal.Gui.AnsiRequestScheduler" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
+    <AssociationLine Name="AnsiRequestScheduler" Type="Terminal.Gui.AnsiRequestScheduler" ManuallyRouted="true">
       <Path>
         <Point X="11.75" Y="4.75" />
         <Point X="11.75" Y="4.39" />
@@ -59,8 +59,13 @@
         <Point X="20.625" Y="4.5" />
       </Path>
     </AssociationLine>
+    <AssociationLine Name="TimedEvents" Type="Terminal.Gui.ITimedEvents">
+      <MemberNameLabel ManuallyPlaced="true">
+        <Position X="-1.14" Y="0.123" />
+      </MemberNameLabel>
+    </AssociationLine>
     <TypeIdentifier>
-      <HashCode>QQQAAAAAACABAQQAAAAAAAAAAAAAAAACAAAAAAAAAAI=</HashCode>
+      <HashCode>QQQAAAAAACABAQQAAAAAAAAAAAAAAAACAAAAAAAAEAI=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoop.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -68,13 +73,14 @@
       <Property Name="OutputBuffer" />
       <Property Name="Out" />
       <Property Name="AnsiRequestScheduler" />
+      <Property Name="TimedEvents" />
     </ShowAsAssociation>
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.MainLoopCoordinator&lt;T&gt;">
     <Position X="6.5" Y="2" Width="2" />
     <TypeIdentifier>
-      <HashCode>AAAAJAEAAAJAAAAAABQAAAAQABAQAAQAIQAABAAACgw=</HashCode>
+      <HashCode>AAAAJAEAAAJABAAAABQAAAAQABAQAAQAIQAABAAACgw=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoopCoordinator.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -132,7 +138,7 @@
       </Path>
     </AssociationLine>
     <TypeIdentifier>
-      <HashCode>AQCkAAAAAASAgAAAAgggAAAABAoAAAAAAAgAAAAAAAA=</HashCode>
+      <HashCode>AQCkAAAAAASAiAAAAgggAAAABAoAAAAAAAgAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\InputProcessor.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -143,14 +149,14 @@
   <Class Name="Terminal.Gui.NetInputProcessor" Collapsed="true">
     <Position X="17.75" Y="5.75" Width="2" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <HashCode>AAAAAAAAAAAACAAAAgAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\NetInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.WindowsInputProcessor" Collapsed="true">
     <Position X="15.75" Y="5.75" Width="2" />
     <TypeIdentifier>
-      <HashCode>AQAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <HashCode>AQAAAAAAAAAACAAAAgAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Class>
@@ -162,7 +168,7 @@
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.ConsoleDrivers.V2.ConsoleDriverFacade&lt;T&gt;">
-    <Position X="6.5" Y="7.25" Width="2" />
+    <Position X="6.5" Y="7.5" Width="2" />
     <Compartments>
       <Compartment Name="Methods" Collapsed="true" />
       <Compartment Name="Fields" Collapsed="true" />
@@ -275,7 +281,7 @@
   <Class Name="Terminal.Gui.ApplicationImpl" Collapsed="true">
     <Position X="3" Y="4.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AABAAAAAAAAIAgQAAAAAAQAAAAAAAAAAQAACgACAAAI=</HashCode>
+      <HashCode>AABAAAAAAAAIAgQQAAAAAQAAAAAAAAAAQAAKgACAAAI=</HashCode>
       <FileName>Application\ApplicationImpl.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -286,7 +292,7 @@
   <Class Name="Terminal.Gui.ConsoleDrivers.V2.ApplicationV2" Collapsed="true">
     <Position X="4.5" Y="4.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAIAgAAAAAAAQAAAAAAgAAAAAACgIAAAAI=</HashCode>
+      <HashCode>AAAAAAAAAAAIAgAQAAAAAQAAAAAAgAAAAAAKgIAAEAI=</HashCode>
       <FileName>ConsoleDrivers\V2\ApplicationV2.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -311,7 +317,7 @@
   <Interface Name="Terminal.Gui.IMainLoop&lt;T&gt;" Collapsed="true">
     <Position X="9.25" Y="4.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAQAAAAAAAABAQQAAAAAAAAAAAAAAAACAAAAAAAAAAI=</HashCode>
+      <HashCode>AAQAAAAAAAABAQQAAAAAAAAAAAAAAAACAAAAAAAAEAI=</HashCode>
       <FileName>ConsoleDrivers\V2\IMainLoop.cs</FileName>
     </TypeIdentifier>
   </Interface>
@@ -379,7 +385,7 @@
   <Interface Name="Terminal.Gui.IApplication">
     <Position X="3.25" Y="1.25" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAIAgQAAAAAAQAAAAAAAAAAAAACgAAAAAI=</HashCode>
+      <HashCode>AAAAAAAAAAAIAgQQAAAAAQAAAAAAAAAAAAAKgAAAAAI=</HashCode>
       <FileName>Application\IApplication.cs</FileName>
     </TypeIdentifier>
   </Interface>
@@ -390,6 +396,13 @@
       <FileName>ConsoleDrivers\V2\IMainLoopCoordinator.cs</FileName>
     </TypeIdentifier>
   </Interface>
+  <Interface Name="Terminal.Gui.ITimedEvents" Collapsed="true">
+    <Position X="12.25" Y="5.75" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>BAAAIAAAAQAAAAAQACAAAIBAAQAAAAAAAAAIgAAAAAA=</HashCode>
+      <FileName>Application\TimedEvents.cs</FileName>
+    </TypeIdentifier>
+  </Interface>
   <Enum Name="Terminal.Gui.AnsiResponseParserState">
     <Position X="20.5" Y="7.25" Width="2" />
     <TypeIdentifier>

From be7d8df1bf42bd087bc99f1b6efe3f994ce06eb8 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 8 Dec 2024 20:28:39 +0000
Subject: [PATCH 055/198] WIP: Trying to get windows driver to work

---
 .../ConsoleDrivers/V2/ApplicationV2.cs        | 22 +++++++++----------
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    |  3 ++-
 .../WindowsDriver/WindowsConsole.cs           | 11 +++++++---
 3 files changed, 21 insertions(+), 15 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index 50e3fc34ac..5a5cf02108 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -31,19 +31,19 @@ private void CreateDriver ()
 
         PlatformID p = Environment.OSVersion.Platform;
 
-        /*if ( p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
+        if ( p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
         {
-            var inputBuffer = new ConcurrentQueue<InputRecord> ();
-            var loop = new MainLoop<InputRecord> ();
-            _coordinator = new MainLoopCoordinator<InputRecord> (
-                                                                () => new WindowsInput (),
-                                                                inputBuffer,
-                                                                new WindowsInputProcessor (inputBuffer),
-                                                                () => new WindowsOutput (),
-                                                                loop);
+            var inputBuffer = new ConcurrentQueue<WindowsConsole.InputRecord> ();
+            var loop = new MainLoop<WindowsConsole.InputRecord> ();
+            _coordinator = new MainLoopCoordinator<WindowsConsole.InputRecord> (TimedEvents,
+                                                                                () => new WindowsInput (),
+                                                                                inputBuffer,
+                                                                                new WindowsInputProcessor (inputBuffer),
+                                                                                () => new WindowsOutput (),
+                                                                                loop);
         }
         else
-        {*/
+        {
             var inputBuffer = new ConcurrentQueue<ConsoleKeyInfo> ();
             var loop = new MainLoop<ConsoleKeyInfo> ();
             _coordinator = new MainLoopCoordinator<ConsoleKeyInfo> (TimedEvents,
@@ -52,7 +52,7 @@ private void CreateDriver ()
                                                                    new NetInputProcessor (inputBuffer),
                                                                    () => new NetOutput (),
                                                                    loop);
-        //}
+        }
 
         _coordinator.StartAsync ();
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 8adbd2df44..c4b1e6ee48 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -60,9 +60,10 @@ public void Iteration ()
             Application.Top.NeedsDraw = true;
             Application.Top.Layout ();
             Application.Top.Draw ();
+
+            Out.Write (OutputBuffer);
         }
 
-        Out.Write (OutputBuffer);
 
         TimedEvents.LockAndRunTimers ();
 
diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
index 020652c4af..655de4169d 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
@@ -1,6 +1,7 @@
 #nullable enable
 using System.Collections.Concurrent;
 using System.ComponentModel;
+using System.Diagnostics;
 using System.Runtime.InteropServices;
 using Terminal.Gui.ConsoleDrivers;
 
@@ -211,11 +212,15 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord
 
             // TODO: requires extensive testing if we go down this route
             // If console output has changed
- //           if (s != _lastWrite)
-       //     {
+            if (s != _lastWrite)
+            {
                 // supply console with the new content
                 result = WriteConsole (_outputHandle, s, (uint)s.Length, out uint _, nint.Zero);
- //           }
+                
+                
+                // Uncomment this to see that it does render properly :/
+                // Process.GetCurrentProcess ().Kill();
+            }
 
             _lastWrite = s;
 

From cea3e774cef8209ca95bcd92f851200223aa7311 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 8 Dec 2024 20:33:17 +0000
Subject: [PATCH 056/198] WIP WindowsConsole / WindowsOutput

---
 Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
index 655de4169d..68ee29a6cb 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
@@ -212,15 +212,15 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord
 
             // TODO: requires extensive testing if we go down this route
             // If console output has changed
-            if (s != _lastWrite)
-            {
+            //if (s != _lastWrite)
+            //{
                 // supply console with the new content
                 result = WriteConsole (_outputHandle, s, (uint)s.Length, out uint _, nint.Zero);
                 
                 
                 // Uncomment this to see that it does render properly :/
                 // Process.GetCurrentProcess ().Kill();
-            }
+            //}
 
             _lastWrite = s;
 

From 9a72628d354fbc419086ba8232e987b648f60479 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Mon, 9 Dec 2024 07:41:20 +0000
Subject: [PATCH 057/198] Make input infinite thread in WindowsConsole
 constructor optional

---
 Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs           | 2 +-
 .../ConsoleDrivers/WindowsDriver/WindowsConsole.cs        | 8 ++++++--
 .../ConsoleDrivers/WindowsDriver/WindowsDriver.cs         | 2 +-
 3 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
index c82471f819..c45bc583b6 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
@@ -55,7 +55,7 @@ private enum DesiredAccess : uint
 
     private nint _screenBuffer;
 
-    public WindowsConsole WinConsole { get; private set; } = new WindowsConsole ();
+    public WindowsConsole WinConsole { get; private set; } = new WindowsConsole (false);
 
     public WindowsOutput ()
     {
diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
index 68ee29a6cb..497ae53ad2 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
@@ -26,7 +26,7 @@ public class WindowsConsole
     private readonly StringBuilder _stringBuilder = new (256 * 1024);
     private string _lastWrite = string.Empty;
 
-    public WindowsConsole ()
+    public WindowsConsole (bool runInputThread)
     {
         _inputHandle = GetStdHandle (STD_INPUT_HANDLE);
         _outputHandle = GetStdHandle (STD_OUTPUT_HANDLE);
@@ -38,7 +38,11 @@ public WindowsConsole ()
         ConsoleMode = newConsoleMode;
 
         _inputReadyCancellationTokenSource = new ();
-        Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token);
+
+        if (runInputThread)
+        {
+            Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token);
+        }
     }
 
     public InputRecord? DequeueInput ()
diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs
index 12b0f5f31d..6f8b0b6702 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs
@@ -45,7 +45,7 @@ public WindowsDriver ()
     {
         if (Environment.OSVersion.Platform == PlatformID.Win32NT)
         {
-            WinConsole = new ();
+            WinConsole = new (true);
 
             // otherwise we're probably running in unit tests
             Clipboard = new WindowsClipboard ();

From 25968c1b6d42fc81051e26358059a962bc9e35c2 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Wed, 11 Dec 2024 20:19:39 +0000
Subject: [PATCH 058/198] Remove dependency on WindowsConsole

---
 .../ConsoleDrivers/V2/WindowsOutput.cs        | 106 ++++++++++++++++--
 .../WindowsDriver/WindowsConsole.cs           |   2 +-
 2 files changed, 99 insertions(+), 9 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
index c45bc583b6..42c8271353 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
@@ -1,5 +1,6 @@
 using System.ComponentModel;
 using System.Runtime.InteropServices;
+using System.Text;
 using static Terminal.Gui.WindowsConsole;
 
 namespace Terminal.Gui;
@@ -55,8 +56,6 @@ private enum DesiredAccess : uint
 
     private nint _screenBuffer;
 
-    public WindowsConsole WinConsole { get; private set; } = new WindowsConsole (false);
-
     public WindowsOutput ()
     {
         _screenBuffer = CreateConsoleScreenBuffer (
@@ -93,11 +92,8 @@ public void Write (string str)
 
     public void Write (IOutputBuffer buffer)
     {
-
         var outputBuffer = new ExtendedCharInfo [buffer.Rows * buffer.Cols];
 
-        Size windowSize = WinConsole?.GetConsoleBufferWindow (out Point _) ?? new Size (buffer.Cols, buffer.Rows);
-        
         // TODO: probably do need this right?
         /*
         if (!windowSize.IsEmpty && (windowSize.Width != buffer.Cols || windowSize.Height != buffer.Rows))
@@ -164,8 +160,7 @@ public void Write (IOutputBuffer buffer)
             Right = (short)buffer.Cols
         };
         //size, ExtendedCharInfo [] charInfoBuffer, Coord , SmallRect window,
-        if (WinConsole != null
-            && !WinConsole.WriteToConsole (
+        if (!WriteToConsole (
                                            size: new (buffer.Cols, buffer.Rows),
                                            charInfoBuffer: outputBuffer,
                                            bufferSize: bufferCoords,
@@ -182,6 +177,101 @@ public void Write (IOutputBuffer buffer)
         SmallRect.MakeEmpty (ref damageRegion);
     }
 
+    public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord bufferSize, SmallRect window, bool force16Colors)
+    {
+        var _stringBuilder = new StringBuilder ();
+
+        //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 (_screenBuffer, 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 (_screenBuffer, s, (uint)s.Length, out uint _, nint.Zero);
+
+            foreach (var sixel in Application.Sixel)
+            {
+                SetCursorPosition ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y);
+                WriteConsole (_screenBuffer, 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;
+    }
+
     public Size GetWindowSize ()
     {
         var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX ();
@@ -204,7 +294,7 @@ public void SetCursorVisibility (CursorVisibility visibility)
     {
         var sb = new StringBuilder ();
         sb.Append (visibility != CursorVisibility.Invisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
-        WinConsole?.WriteANSI (sb.ToString ());
+        Write (sb.ToString ());
     }
 
     /// <inheritdoc />
diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
index 497ae53ad2..94628f99e4 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
@@ -927,7 +927,7 @@ 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 (
+    public static extern bool WriteConsoleOutput (
         nint hConsoleOutput,
         CharInfo [] lpBuffer,
         Coord dwBufferSize,

From ac58ed1860b6c8d70b475c75637866e28597cbf6 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Wed, 11 Dec 2024 20:32:48 +0000
Subject: [PATCH 059/198] Fledgling mouse support for windows input

---
 .../ConsoleDrivers/V2/InputProcessor.cs       |  2 +-
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    | 11 ++++++----
 .../ConsoleDrivers/V2/WindowsInput.cs         | 21 ++++++++++++++++++-
 .../V2/WindowsInputProcessor.cs               |  5 ++++-
 4 files changed, 32 insertions(+), 7 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index edf5dd7fb2..6aaa91c013 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -51,7 +51,7 @@ public void OnMouseEvent (MouseEventArgs a)
     {
         // Ensure ScreenPosition is set
         a.ScreenPosition = a.Position;
-
+        
         foreach (var narrative in MouseInterpreter.Process (a))
         {
             ResolveNarrative (narrative);
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index c4b1e6ee48..0b8960e180 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -44,16 +44,19 @@ public void Run (CancellationToken token)
         while (!token.IsCancellationRequested);
     }
 
+    private bool first = true;
     /// <inheritdoc />
     public void Iteration ()
     {
         InputProcessor.ProcessQueue ();
 
-        // TODO: throttle this
-        var size = Out.GetWindowSize ();
+ 
+            // TODO: throttle this
+            var size = Out.GetWindowSize ();
+
+            OutputBuffer.SetWindowSize (size.Width, size.Height);
+            // TODO: Test only
 
-        OutputBuffer.SetWindowSize (size.Width, size.Height);
-        // TODO: Test only
 
         if (Application.Top != null)
         {
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
index 7293a344eb..f0a2e6eea3 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
@@ -26,9 +26,28 @@ out uint lpNumberOfEventsRead
     [DllImport ("kernel32.dll", SetLastError = true)]
     private static extern nint GetStdHandle (int nStdHandle);
 
+    [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);
+
+
+    private readonly uint _originalConsoleMode;
+
     public WindowsInput ()
     {
         _inputHandle = GetStdHandle (STD_INPUT_HANDLE);
+
+
+        GetConsoleMode (_inputHandle, out uint v);
+        _originalConsoleMode = v;
+
+        uint newConsoleMode = _originalConsoleMode;
+        newConsoleMode |= (uint)(ConsoleModes.EnableMouseInput | ConsoleModes.EnableExtendedFlags);
+        newConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode;
+        newConsoleMode &= ~(uint)ConsoleModes.EnableProcessedInput;
+        SetConsoleMode (_inputHandle,newConsoleMode);
     }
 
     protected override bool Peek ()
@@ -90,6 +109,6 @@ protected override IEnumerable<InputRecord> Read ()
     }
     public void Dispose ()
     {
-        // TODO: Un set any settings e.g. console buffer
+        SetConsoleMode (_inputHandle, _originalConsoleMode);
     }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
index d57fde2432..d96ac352e5 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
@@ -77,7 +77,10 @@ private MouseEventArgs ToDriverMouse (MouseEventRecord e)
     {
         var result = new MouseEventArgs
         {
-            Position = new (e.MousePosition.X, e.MousePosition.Y)
+            Position = new (e.MousePosition.X, e.MousePosition.Y),
+            //Wrong but for POC ok
+            Flags = e.ButtonState.HasFlag (WindowsConsole.ButtonState.Button1Pressed) ? MouseFlags.Button1Pressed : MouseFlags.None,
+
         };
 
         // TODO: Return keys too

From 4c667f30628e2dc292e0b1a9f15310bd4acc5816 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Wed, 11 Dec 2024 20:37:44 +0000
Subject: [PATCH 060/198] Fix for new names

---
 Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index 5a5cf02108..19412696c6 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -17,7 +17,7 @@ public override void Init (IConsoleDriver driver = null, string driverName = nul
     {
         Application.Navigation = new ();
 
-        Application.AddApplicationKeyBindings ();
+        Application.AddKeyBindings ();
 
         CreateDriver ();
 

From 650a075287c4e53eaa72000d5395c8fd5ef512a8 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 14 Dec 2024 10:45:42 +0000
Subject: [PATCH 061/198] Use LayoutAndDraw on iteration

---
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs |  5 +----
 Terminal.Gui/ConsoleDrivers/V2/V2.cd       | 21 ++++++++++++---------
 2 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 0b8960e180..5c6694fbfe 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -60,10 +60,7 @@ public void Iteration ()
 
         if (Application.Top != null)
         {
-            Application.Top.NeedsDraw = true;
-            Application.Top.Layout ();
-            Application.Top.Draw ();
-
+            Application.LayoutAndDraw ();
             Out.Write (OutputBuffer);
         }
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index 60172ab857..58f0406bec 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -30,7 +30,7 @@
   <Class Name="Terminal.Gui.WindowsInput" Collapsed="true">
     <Position X="11.5" Y="3" Width="1.75" />
     <TypeIdentifier>
-      <HashCode>QAAAAAAAACAEAAAAAAAAAAAkAAAAAAAAAgAAAAAAABA=</HashCode>
+      <HashCode>QIAACAAAACAEAAAAAAAAAAAkAAAAAAAAAwAAAAAAABA=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsInput.cs</FileName>
     </TypeIdentifier>
   </Class>
@@ -51,6 +51,11 @@
   </Class>
   <Class Name="Terminal.Gui.MainLoop&lt;T&gt;" Collapsed="true" BaseTypeListCollapsed="true">
     <Position X="11" Y="4.75" Width="1.5" />
+    <AssociationLine Name="TimedEvents" Type="Terminal.Gui.ITimedEvents">
+      <MemberNameLabel ManuallyPlaced="true">
+        <Position X="-1.14" Y="0.123" />
+      </MemberNameLabel>
+    </AssociationLine>
     <AssociationLine Name="AnsiRequestScheduler" Type="Terminal.Gui.AnsiRequestScheduler" ManuallyRouted="true">
       <Path>
         <Point X="11.75" Y="4.75" />
@@ -58,22 +63,20 @@
         <Point X="20.625" Y="4.39" />
         <Point X="20.625" Y="4.5" />
       </Path>
-    </AssociationLine>
-    <AssociationLine Name="TimedEvents" Type="Terminal.Gui.ITimedEvents">
       <MemberNameLabel ManuallyPlaced="true">
-        <Position X="-1.14" Y="0.123" />
+        <Position X="0.11" Y="0.143" />
       </MemberNameLabel>
     </AssociationLine>
     <TypeIdentifier>
-      <HashCode>QQQAAAAAACABAQQAAAAAAAAAAAAAAAACAAAAAAAAEAI=</HashCode>
+      <HashCode>QQQAAAAAACABAQQAAAEAAAAAAAAAAAACAAAAAAAAEAI=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoop.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
+      <Property Name="TimedEvents" />
       <Property Name="InputProcessor" />
       <Property Name="OutputBuffer" />
       <Property Name="Out" />
       <Property Name="AnsiRequestScheduler" />
-      <Property Name="TimedEvents" />
     </ShowAsAssociation>
     <Lollipop Position="0.2" />
   </Class>
@@ -118,7 +121,7 @@
   <Class Name="Terminal.Gui.WindowsOutput" Collapsed="true">
     <Position X="13.25" Y="8.25" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAAAABACACAAhAAACAAAACAAAAgAQAAIMAAAAAEAAAQ=</HashCode>
+      <HashCode>AEAAABACACAAhAAAAAAAACAAAAgAQAAIMAAAAAEAAAQ=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsOutput.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
@@ -274,7 +277,7 @@
   <Class Name="Terminal.Gui.Application" Collapsed="true">
     <Position X="0.5" Y="0.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>hEK4FAgAqARIspQfBQo0gTGiACNL0AICESJKoggBSw8=</HashCode>
+      <HashCode>hEK4FAgAqARIspQeBwoUgTGgACNL0AIAESJKoggBSw8=</HashCode>
       <FileName>Application\Application.cs</FileName>
     </TypeIdentifier>
   </Class>
@@ -302,7 +305,7 @@
   <Class Name="Terminal.Gui.View" Collapsed="true">
     <Position X="0.5" Y="3" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>3/v2dzPLvbb/5+LOGuv1x0dem3Y5zv/886afz2/e/Y8=</HashCode>
+      <HashCode>3/v2dzPLvbb/5+LOHuv1x0dem3Y57v/8c6afz2/e/Y8=</HashCode>
       <FileName>View\View.Adornments.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />

From a1dcb0848f18a9aab1d8656b6d09353afaf88b4b Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 14 Dec 2024 11:30:55 +0000
Subject: [PATCH 062/198] How we would like draw loop to be

---
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    | 45 ++++++++++++++-----
 .../ConsoleDrivers/V2/OutputBuffer.cs         |  2 +-
 2 files changed, 36 insertions(+), 11 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 5c6694fbfe..1135bee811 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -50,26 +50,51 @@ public void Iteration ()
     {
         InputProcessor.ProcessQueue ();
 
- 
-            // TODO: throttle this
-            var size = Out.GetWindowSize ();
-
-            OutputBuffer.SetWindowSize (size.Width, size.Height);
-            // TODO: Test only
-
 
         if (Application.Top != null)
         {
-            Application.LayoutAndDraw ();
-            Out.Write (OutputBuffer);
-        }
 
+            bool needsDrawOrLayout = AnySubviewsNeedDrawn(Application.Top);
+
+            if (needsDrawOrLayout)
+            {
+                // TODO: throttle this
+                var size = Out.GetWindowSize ();
+
+                OutputBuffer.SetWindowSize (size.Width, size.Height);
+
+                // TODO: Test only
+
+                Application.Top.Layout ();
+                Application.Top.Draw ();
+
+                Out.Write (OutputBuffer);
+            }
+        }
 
         TimedEvents.LockAndRunTimers ();
 
         TimedEvents.LockAndRunIdles ();
     }
 
+    private bool AnySubviewsNeedDrawn (View v)
+    {
+        if (v.NeedsDraw || v.NeedsLayout)
+        {
+            return true;
+        }
+
+        foreach(var subview in v.Subviews )
+        {
+            if (AnySubviewsNeedDrawn (subview))
+            {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
     /// <inheritdoc />
     public void Dispose ()
     { // TODO release managed resources here
diff --git a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
index c5b1643413..429437d7e4 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
@@ -14,7 +14,7 @@ public class OutputBuffer : IOutputBuffer
     ///     UpdateScreen is called.
     ///     <remarks>The format of the array is rows, columns. The first index is the row, the second index is the column.</remarks>
     /// </summary>
-    public Cell [,] Contents { get; set; }
+    public Cell [,] Contents { get; set; } = new Cell[0, 0];
 
     private Attribute _currentAttribute;
     private int _cols;

From 6d4f6c0178499c05ea19ed69f4bf1d06cfedf0dc Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 14 Dec 2024 17:24:00 +0000
Subject: [PATCH 063/198] Menu working

---
 Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs | 6 +++++-
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs       | 4 +---
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index 6aaa91c013..664619d084 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -51,7 +51,11 @@ public void OnMouseEvent (MouseEventArgs a)
     {
         // Ensure ScreenPosition is set
         a.ScreenPosition = a.Position;
-        
+
+        // Pass on basic state
+        MouseEvent?.Invoke (this, a);
+
+        // Pass on any interpreted states e.g. click/double click etc
         foreach (var narrative in MouseInterpreter.Process (a))
         {
             ResolveNarrative (narrative);
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 1135bee811..1c1a7974e7 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -64,9 +64,7 @@ public void Iteration ()
                 OutputBuffer.SetWindowSize (size.Width, size.Height);
 
                 // TODO: Test only
-
-                Application.Top.Layout ();
-                Application.Top.Draw ();
+                Application.LayoutAndDraw (true);
 
                 Out.Write (OutputBuffer);
             }

From adceea88f27bd6d7a8223124cb8100031a75a643 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 14 Dec 2024 17:43:39 +0000
Subject: [PATCH 064/198] Revert removal of Suspend

---
 Terminal.Gui/Application/Application.Keyboard.cs       |  8 ++++++++
 Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs          |  4 ++++
 Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs  |  6 ++++++
 .../ConsoleDrivers/WindowsDriver/WindowsConsole.cs     | 10 +++-------
 .../ConsoleDrivers/WindowsDriver/WindowsDriver.cs      |  2 +-
 5 files changed, 22 insertions(+), 8 deletions(-)

diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs
index 2ffb48aa62..873dc0af0f 100644
--- a/Terminal.Gui/Application/Application.Keyboard.cs
+++ b/Terminal.Gui/Application/Application.Keyboard.cs
@@ -177,7 +177,15 @@ internal static void AddKeyBindings ()
                         return true;
                     }
                    );
+        AddCommand (
+                    Command.Suspend,
+                    static () =>
+                    {
+                        Driver?.Suspend ();
 
+                        return true;
+                    }
+                   );
         AddCommand (
                     Command.NextTabStop,
                     static () => Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop));
diff --git a/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
index 795cd74012..7bc1d3638e 100644
--- a/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
@@ -205,6 +205,10 @@ public interface IConsoleDriver
     /// <summary>The event fired when the terminal is resized.</summary>
     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>
+    void Suspend ();
+
     /// <summary>
     ///     Sets the position of the terminal cursor to <see cref="ConsoleDriver.Col"/> and
     ///     <see cref="ConsoleDriver.Row"/>.
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
index 32e2312bb9..bbe34fea93 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
@@ -240,6 +240,12 @@ public bool GetCursorVisibility (out CursorVisibility current)
     /// <summary>The event fired when the terminal is resized.</summary>
     public event EventHandler<SizeChangedEventArgs> SizeChanged;
 
+    /// <inheritdoc />
+    public void Suspend ()
+    {
+
+    }
+
     /// <summary>Sets the position of the terminal cursor to <see cref="ConsoleDriver.Col"/> and <see cref="ConsoleDriver.Row"/>.</summary>
     public void UpdateCursor ()
     {
diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
index 94628f99e4..e3c9ca461b 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
@@ -26,7 +26,7 @@ public class WindowsConsole
     private readonly StringBuilder _stringBuilder = new (256 * 1024);
     private string _lastWrite = string.Empty;
 
-    public WindowsConsole (bool runInputThread)
+    public WindowsConsole ()
     {
         _inputHandle = GetStdHandle (STD_INPUT_HANDLE);
         _outputHandle = GetStdHandle (STD_OUTPUT_HANDLE);
@@ -37,12 +37,8 @@ public WindowsConsole (bool runInputThread)
         newConsoleMode &= ~(uint)ConsoleModes.EnableProcessedInput;
         ConsoleMode = newConsoleMode;
 
-        _inputReadyCancellationTokenSource = new ();
-
-        if (runInputThread)
-        {
-            Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token);
-        }
+        _inputReadyCancellationTokenSource = new (); 
+        Task.Run (ProcessInputQueue, _inputReadyCancellationTokenSource.Token);
     }
 
     public InputRecord? DequeueInput ()
diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs
index d5f96e2374..5d741f1ff0 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs
@@ -45,7 +45,7 @@ public WindowsDriver ()
     {
         if (Environment.OSVersion.Platform == PlatformID.Win32NT)
         {
-            WinConsole = new (true);
+            WinConsole = new ();
 
             // otherwise we're probably running in unit tests
             Clipboard = new WindowsClipboard ();

From 128b2193054d1d328e1566741292eccb4beccb96 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 14 Dec 2024 17:48:31 +0000
Subject: [PATCH 065/198] Revert changes to PositionCursor

---
 Terminal.Gui/Application/Application.Run.cs | 23 +++++++++++++++++----
 1 file changed, 19 insertions(+), 4 deletions(-)

diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs
index 9cc1aadef6..431036fb16 100644
--- a/Terminal.Gui/Application/Application.Run.cs
+++ b/Terminal.Gui/Application/Application.Run.cs
@@ -232,7 +232,12 @@ internal static bool PositionCursor ()
         if (mostFocused is null || !mostFocused.Visible || !mostFocused.Enabled)
         {
             CursorVisibility current = CursorVisibility.Invisible;
-            Driver?.SetCursorVisibility (CursorVisibility.Invisible);
+            Driver?.GetCursorVisibility (out current);
+
+            if (current != CursorVisibility.Invisible)
+            {
+                Driver?.SetCursorVisibility (CursorVisibility.Invisible);
+            }
 
             return false;
         }
@@ -248,6 +253,7 @@ internal static bool PositionCursor ()
 
         Point? cursor = mostFocused.PositionCursor ();
 
+        Driver!.GetCursorVisibility (out CursorVisibility currentCursorVisibility);
 
         if (cursor is { })
         {
@@ -257,18 +263,27 @@ internal static bool PositionCursor ()
             // If the cursor is not in a visible location in the SuperView, hide it
             if (!superViewViewport.Contains (cursor.Value))
             {
-                Driver.SetCursorVisibility (CursorVisibility.Invisible);
+                if (currentCursorVisibility != CursorVisibility.Invisible)
+                {
+                    Driver.SetCursorVisibility (CursorVisibility.Invisible);
+                }
 
                 return false;
             }
 
             // Show it
-            Driver.SetCursorVisibility (mostFocused.CursorVisibility);
+            if (currentCursorVisibility == CursorVisibility.Invisible)
+            {
+                Driver.SetCursorVisibility (mostFocused.CursorVisibility);
+            }
 
             return true;
         }
 
-        Driver.SetCursorVisibility (CursorVisibility.Invisible);
+        if (currentCursorVisibility != CursorVisibility.Invisible)
+        {
+            Driver.SetCursorVisibility (CursorVisibility.Invisible);
+        }
 
         return false;
     }

From 32aa24b29f21aa723563687375b14763afeac352 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 14 Dec 2024 17:59:29 +0000
Subject: [PATCH 066/198] Revert lots of little changes to accessibility etc in
 main codebase

---
 Terminal.Gui/Application/Application.Toplevel.cs    |  2 +-
 .../AnsiResponseParser/AnsiResponseExpectation.cs   |  2 +-
 .../AnsiResponseParser/AnsiResponseParser.cs        |  4 ++--
 .../ConsoleDrivers/AnsiResponseParser/IHeld.cs      |  2 +-
 Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs        |  2 +-
 .../ConsoleDrivers/CursesDriver/CursesDriver.cs     |  2 +-
 .../ConsoleDrivers/FakeDriver/FakeDriver.cs         |  2 +-
 Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs  |  2 +-
 Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs    |  2 +-
 Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs      |  2 +-
 .../ConsoleDrivers/V2/WindowsInputProcessor.cs      |  2 +-
 Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs     |  2 +-
 .../ConsoleDrivers/WindowsDriver/WindowsConsole.cs  | 13 ++++---------
 .../ConsoleDrivers/WindowsDriver/WindowsDriver.cs   |  2 +-
 14 files changed, 18 insertions(+), 23 deletions(-)

diff --git a/Terminal.Gui/Application/Application.Toplevel.cs b/Terminal.Gui/Application/Application.Toplevel.cs
index 904b067673..3403d1eda7 100644
--- a/Terminal.Gui/Application/Application.Toplevel.cs
+++ b/Terminal.Gui/Application/Application.Toplevel.cs
@@ -10,5 +10,5 @@ public static partial class Application // Toplevel handling
 
     /// <summary>The <see cref="Toplevel"/> that is currently active.</summary>
     /// <value>The top.</value>
-    public static Toplevel? Top { get; set; }
+    public static Toplevel? Top { get; internal set; }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseExpectation.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseExpectation.cs
index c313c21690..00aa1a9512 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseExpectation.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseExpectation.cs
@@ -1,7 +1,7 @@
 #nullable enable
 namespace Terminal.Gui;
 
-public record AnsiResponseExpectation (string Terminator, Action<IHeld> Response, Action? Abandoned)
+internal record AnsiResponseExpectation (string Terminator, Action<IHeld> Response, Action? Abandoned)
 {
     public bool Matches (string cur) { return cur.EndsWith (Terminator); }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
index 71052e967f..4c2ddfd70b 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
@@ -2,7 +2,7 @@
 
 namespace Terminal.Gui;
 
-public abstract class AnsiResponseParserBase : IAnsiResponseParser
+internal abstract class AnsiResponseParserBase : IAnsiResponseParser
 {
     private readonly AnsiMouseParser _mouseParser = new  ();
     protected object _lockExpectedResponses = new();
@@ -375,7 +375,7 @@ public void StopExpecting (string terminator, bool persistent)
     }
 }
 
-public class AnsiResponseParser<T> : AnsiResponseParserBase
+internal class AnsiResponseParser<T> : AnsiResponseParserBase
 {
     public AnsiResponseParser () : base (new GenericHeld<T> ()) { }
 
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/IHeld.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/IHeld.cs
index efcf025038..ab23f477fd 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/IHeld.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/IHeld.cs
@@ -5,7 +5,7 @@ namespace Terminal.Gui;
 ///     Describes a sequence of chars (and optionally T metadata) accumulated
 ///     by an <see cref="IAnsiResponseParser"/>
 /// </summary>
-public interface IHeld
+internal interface IHeld
 {
     /// <summary>
     ///     Clears all held objects
diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
index 84ce8b2f54..46b7f50332 100644
--- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
@@ -704,7 +704,7 @@ public void QueueAnsiRequest (AnsiEscapeSequenceRequest request)
         GetRequestScheduler ().SendOrSchedule (request);
     }
 
-    public abstract IAnsiResponseParser GetParser ();
+    internal abstract IAnsiResponseParser GetParser ();
 
     /// <summary>
     ///     Gets the <see cref="AnsiRequestScheduler"/> for this <see cref="ConsoleDriver"/>.
diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
index 10eb6ce271..48bcb713f5 100644
--- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
@@ -694,7 +694,7 @@ public override MainLoop Init ()
 
     private readonly AnsiResponseParser _parser = new ();
     /// <inheritdoc />
-    public override IAnsiResponseParser GetParser () => _parser;
+    internal override IAnsiResponseParser GetParser () => _parser;
 
     internal void ProcessInput ()
     {
diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
index e3c26ea40c..5e9a883d72 100644
--- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
@@ -401,7 +401,7 @@ public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool al
     private AnsiResponseParser _parser = new ();
 
     /// <inheritdoc />
-    public override IAnsiResponseParser GetParser () => _parser;
+    internal override IAnsiResponseParser GetParser () => _parser;
 
     public void SetBufferSize (int width, int height)
     {
diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs
index ebfe3f16c8..2ede00a000 100644
--- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs
@@ -226,7 +226,7 @@ void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int out
 
     // BUGBUG: Fix this nullable issue.
     /// <inheritdoc />
-    public override IAnsiResponseParser GetParser () => _mainLoopDriver._netEvents.Parser;
+    internal override IAnsiResponseParser GetParser () => _mainLoopDriver._netEvents.Parser;
     internal NetMainLoop? _mainLoopDriver;
 
     /// <inheritdoc />
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index 664619d084..8f203fdf35 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -10,7 +10,7 @@ public abstract class InputProcessor<T> : IInputProcessor
     /// </summary>
     TimeSpan _escTimeout = TimeSpan.FromMilliseconds (50);
 
-    public AnsiResponseParser<T> Parser { get; } = new ();
+    internal AnsiResponseParser<T> Parser { get; } = new ();
     public ConcurrentQueue<T> InputBuffer { get; }
 
     public IAnsiResponseParser GetParser () => Parser;
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
index f0a2e6eea3..4194417bb7 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
@@ -3,7 +3,7 @@
 
 namespace Terminal.Gui;
 
-public class WindowsInput : ConsoleInput<InputRecord>
+internal class WindowsInput : ConsoleInput<InputRecord>
 {
     private readonly nint _inputHandle;
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
index d96ac352e5..c14a38ea25 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
@@ -8,7 +8,7 @@ namespace Terminal.Gui;
 /// <summary>
 /// Input processor for <see cref="WindowsInput"/>, deals in <see cref="WindowsConsole.InputRecord"/> stream.
 /// </summary>
-public class WindowsInputProcessor : InputProcessor<InputRecord>
+internal class WindowsInputProcessor : InputProcessor<InputRecord>
 {
     /// <inheritdoc />
     public WindowsInputProcessor (ConcurrentQueue<InputRecord> inputBuffer) : base (inputBuffer) { }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
index 42c8271353..bc84db9fd6 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
@@ -4,7 +4,7 @@
 using static Terminal.Gui.WindowsConsole;
 
 namespace Terminal.Gui;
-public class WindowsOutput : IConsoleOutput
+internal class WindowsOutput : IConsoleOutput
 {
 
     [DllImport ("kernel32.dll", EntryPoint = "WriteConsole", SetLastError = true, CharSet = CharSet.Unicode)]
diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
index e3c9ca461b..66ceff41d4 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs
@@ -1,13 +1,12 @@
 #nullable enable
 using System.Collections.Concurrent;
 using System.ComponentModel;
-using System.Diagnostics;
 using System.Runtime.InteropServices;
 using Terminal.Gui.ConsoleDrivers;
 
 namespace Terminal.Gui;
 
-public class WindowsConsole
+internal class WindowsConsole
 {
     private CancellationTokenSource? _inputReadyCancellationTokenSource;
     private readonly BlockingCollection<InputRecord> _inputQueue = new (new ConcurrentQueue<InputRecord> ());
@@ -212,15 +211,11 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord
 
             // TODO: requires extensive testing if we go down this route
             // If console output has changed
-            //if (s != _lastWrite)
-            //{
+            if (s != _lastWrite)
+            {
                 // supply console with the new content
                 result = WriteConsole (_outputHandle, s, (uint)s.Length, out uint _, nint.Zero);
-                
-                
-                // Uncomment this to see that it does render properly :/
-                // Process.GetCurrentProcess ().Kill();
-            //}
+            }
 
             _lastWrite = s;
 
diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs
index 5d741f1ff0..02d8a03ff7 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs
@@ -189,7 +189,7 @@ public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool al
     }
 
     /// <inheritdoc />
-    public override IAnsiResponseParser GetParser () => _parser;
+    internal override IAnsiResponseParser GetParser () => _parser;
 
 
     public override void WriteRaw (string str)

From e832d6139e0d87808ba5f0857723ec39d0fa30c3 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 14 Dec 2024 18:55:14 +0000
Subject: [PATCH 067/198] Remove debug only reference

---
 UICatalog/UICatalog.cs | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs
index 2b5063b065..b4a28e6c0b 100644
--- a/UICatalog/UICatalog.cs
+++ b/UICatalog/UICatalog.cs
@@ -908,11 +908,6 @@ public UICatalogTopLevel ()
 
         public void ConfigChanged ()
         {
-            if (WasDisposed)
-            {
-                return;
-            }
-
             if (_topLevelColorScheme == null || !Colors.ColorSchemes.ContainsKey (_topLevelColorScheme))
             {
                 _topLevelColorScheme = "Base";

From 293fe4ef609f81de50a14d2579d0c913689fd476 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 14 Dec 2024 19:52:40 +0000
Subject: [PATCH 068/198] Do not deadlock in Stop if invoked

---
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  | 13 +++++-
 .../V2/WindowsInputProcessor.cs               | 44 ++++++++++++++++++-
 UICatalog/UICatalog.cs                        |  6 +++
 3 files changed, 60 insertions(+), 3 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index 7e2d86c914..5d504e223f 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -110,7 +110,16 @@ public void Stop ()
     {
         tokenSource.Cancel();
 
-        // Wait for both tasks to complete
-        Task.WhenAll (_inputTask, _loopTask).Wait ();
+        if (_loopTask.Id == Task.CurrentId)
+        {
+            // Cannot wait for loop to finish because we are actively executing on that thread
+            Task.WhenAll (_inputTask).Wait ();
+        }
+        else
+        {
+            // Wait for both tasks to complete
+            Task.WhenAll (_inputTask, _loopTask).Wait ();
+        }
+
     }
 }
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
index c14a38ea25..94480ec12e 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
@@ -68,7 +68,49 @@ protected override void Process (InputRecord inputEvent)
     /// <inheritdoc />
     protected override void ProcessAfterParsing (InputRecord input)
     {
-        var key = (Key)input.KeyEvent.UnicodeChar;
+        Key key;
+        if (input.KeyEvent.UnicodeChar == '\0')
+        {
+            key = input.KeyEvent.wVirtualKeyCode switch
+                  {
+                      VK.DOWN => Key.CursorDown,
+                      VK.UP => Key.CursorUp,
+                      VK.LEFT => Key.CursorLeft,
+                      VK.RIGHT => Key.CursorRight,
+                      VK.BACK => Key.Backspace,
+                      VK.TAB => Key.Tab,
+                      VK.F1 => Key.F1,
+                      VK.F2 => Key.F2,
+                      VK.F3 => Key.F3,
+                      VK.F4 => Key.F4,
+                      VK.F5 => Key.F5,
+                      VK.F6 => Key.F6,
+                      VK.F7 => Key.F7,
+                      VK.F8 => Key.F8,
+                      VK.F9 => Key.F9,
+                      VK.F10 => Key.F10,
+                      VK.F11 => Key.F11,
+                      VK.F12 => Key.F12,
+                      VK.F13 => Key.F13,
+                      VK.F14 => Key.F14,
+                      VK.F15 => Key.F15,
+                      VK.F16 => Key.F16,
+                      VK.F17 => Key.F17,
+                      VK.F18 => Key.F18,
+                      VK.F19 => Key.F19,
+                      VK.F20 => Key.F20,
+                      VK.F21 => Key.F21,
+                      VK.F22 => Key.F22,
+                      VK.F23 => Key.F23,
+                      VK.F24 => Key.F24,
+                      _ => (Key)'\0'
+                  };
+        }
+        else
+        {
+            key = input.KeyEvent.UnicodeChar;
+        }
+
         OnKeyDown (key);
         OnKeyUp (key);
     }
diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs
index b4a28e6c0b..9ec5cbd756 100644
--- a/UICatalog/UICatalog.cs
+++ b/UICatalog/UICatalog.cs
@@ -908,6 +908,12 @@ public UICatalogTopLevel ()
 
         public void ConfigChanged ()
         {
+            if (MenuBar == null)
+            {
+                // View is probably disposed
+                return;
+            }
+
             if (_topLevelColorScheme == null || !Colors.ColorSchemes.ContainsKey (_topLevelColorScheme))
             {
                 _topLevelColorScheme = "Base";

From f0a5e0450137da5feebf171d8c5708fe80605546 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 14 Dec 2024 20:44:45 +0000
Subject: [PATCH 069/198] Support specify v2net or v2win

---
 .../ConsoleDrivers/V2/ApplicationV2.cs        | 65 +++++++++++++------
 UICatalog/UICatalog.cs                        |  4 +-
 2 files changed, 47 insertions(+), 22 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index 19412696c6..a6088042a7 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -5,10 +5,12 @@ namespace Terminal.Gui.ConsoleDrivers.V2;
 
 public class ApplicationV2 : ApplicationImpl
 {
+    private readonly string _driver;
     private IMainLoopCoordinator _coordinator;
     public ITimedEvents TimedEvents { get; } = new TimedEvents ();
-    public ApplicationV2 ()
+    public ApplicationV2 (string driver = null)
     {
+        _driver = driver;
         IsLegacy = false;
     }
 
@@ -19,39 +21,38 @@ public override void Init (IConsoleDriver driver = null, string driverName = nul
 
         Application.AddKeyBindings ();
 
-        CreateDriver ();
+        CreateDriver (_driver ?? driverName);
 
         Application.Initialized = true;
 
         Application.SubscribeDriverEvents ();
     }
 
-    private void CreateDriver ()
+    private void CreateDriver (string driverName)
     {
 
         PlatformID p = Environment.OSVersion.Platform;
 
-        if ( p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
+        var definetlyWin = driverName?.Contains ("win") ?? false;
+        var definetlyNet = driverName?.Contains ("net") ?? false;
+
+        if (definetlyWin)
+        {
+            CreateWindowsSubcomponents ();
+
+        }
+        else if (definetlyNet)
+        {
+            CreateNetSubcomponents ();
+        }
+        else
+        if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
         {
-            var inputBuffer = new ConcurrentQueue<WindowsConsole.InputRecord> ();
-            var loop = new MainLoop<WindowsConsole.InputRecord> ();
-            _coordinator = new MainLoopCoordinator<WindowsConsole.InputRecord> (TimedEvents,
-                                                                                () => new WindowsInput (),
-                                                                                inputBuffer,
-                                                                                new WindowsInputProcessor (inputBuffer),
-                                                                                () => new WindowsOutput (),
-                                                                                loop);
+            CreateWindowsSubcomponents ();
         }
         else
         {
-            var inputBuffer = new ConcurrentQueue<ConsoleKeyInfo> ();
-            var loop = new MainLoop<ConsoleKeyInfo> ();
-            _coordinator = new MainLoopCoordinator<ConsoleKeyInfo> (TimedEvents,
-                                                                    () => new NetInput (),
-                                                                   inputBuffer,
-                                                                   new NetInputProcessor (inputBuffer),
-                                                                   () => new NetOutput (),
-                                                                   loop);
+            CreateNetSubcomponents ();
         }
 
         _coordinator.StartAsync ();
@@ -67,6 +68,30 @@ private void CreateDriver ()
         }
     }
 
+
+    private void CreateWindowsSubcomponents ()
+    {
+        var inputBuffer = new ConcurrentQueue<WindowsConsole.InputRecord> ();
+        var loop = new MainLoop<WindowsConsole.InputRecord> ();
+        _coordinator = new MainLoopCoordinator<WindowsConsole.InputRecord> (TimedEvents,
+                                                                            () => new WindowsInput (),
+                                                                            inputBuffer,
+                                                                            new WindowsInputProcessor (inputBuffer),
+                                                                            () => new WindowsOutput (),
+                                                                            loop);
+    }
+    private void CreateNetSubcomponents ()
+    {
+        var inputBuffer = new ConcurrentQueue<ConsoleKeyInfo> ();
+        var loop = new MainLoop<ConsoleKeyInfo> ();
+        _coordinator = new MainLoopCoordinator<ConsoleKeyInfo> (TimedEvents,
+                                                                () => new NetInput (),
+                                                                inputBuffer,
+                                                                new NetInputProcessor (inputBuffer),
+                                                                () => new NetOutput (),
+                                                                loop);
+    }
+
     /// <inheritdoc />
     public override T Run<T> (Func<Exception, bool> errorHandler = null, IConsoleDriver driver = null)
     {
diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs
index 9ec5cbd756..e789bdfb42 100644
--- a/UICatalog/UICatalog.cs
+++ b/UICatalog/UICatalog.cs
@@ -336,9 +336,9 @@ private static void UICatalogMain (Options options)
     {
         StartConfigFileWatcher ();
 
-        if (options.Driver == "v2")
+        if (options.Driver?.StartsWith ("v2")??false)
         {
-            ApplicationImpl.ChangeInstance (new ApplicationV2 ());
+            ApplicationImpl.ChangeInstance (new ApplicationV2 (options.Driver));
             options.Driver = string.Empty;
         }
 

From 13de24b80cbd18d343bbf04d9c1116244723ea23 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 14 Dec 2024 20:47:13 +0000
Subject: [PATCH 070/198] Update launch settings for new v2 options

---
 UICatalog/Properties/launchSettings.json | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/UICatalog/Properties/launchSettings.json b/UICatalog/Properties/launchSettings.json
index 12126bd1bc..0ddda42a4b 100644
--- a/UICatalog/Properties/launchSettings.json
+++ b/UICatalog/Properties/launchSettings.json
@@ -11,9 +11,13 @@
       "commandName": "Project",
       "commandLineArgs": "--driver WindowsDriver"
     },
-    "UICatalog --driver v2": {
+    "UICatalog --driver v2win": {
       "commandName": "Project",
-      "commandLineArgs": "--driver v2"
+      "commandLineArgs": "--driver v2win"
+    },
+    "UICatalog --driver v2net": {
+      "commandName": "Project",
+      "commandLineArgs": "--driver v2net"
     },
     "WSL: UICatalog": {
       "commandName": "Executable",

From 124ab9a5d41ea3f9c11f11b254a1285ea4dc179b Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 15 Dec 2024 08:54:17 +0000
Subject: [PATCH 071/198] Move v2 driver bootstrap logic into Application and
 out of UICatalog

---
 Terminal.Gui/Application/Application.Initialization.cs | 6 ++++++
 Terminal.Gui/Application/ApplicationImpl.cs            | 2 +-
 Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs        | 6 ++----
 UICatalog/UICatalog.cs                                 | 6 ------
 4 files changed, 9 insertions(+), 11 deletions(-)

diff --git a/Terminal.Gui/Application/Application.Initialization.cs b/Terminal.Gui/Application/Application.Initialization.cs
index b7e9f01a6c..dbf2144225 100644
--- a/Terminal.Gui/Application/Application.Initialization.cs
+++ b/Terminal.Gui/Application/Application.Initialization.cs
@@ -2,6 +2,7 @@
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Reflection;
+using Terminal.Gui.ConsoleDrivers.V2;
 
 namespace Terminal.Gui;
 
@@ -39,6 +40,11 @@ public static partial class Application // Initialization (Init/Shutdown)
     [RequiresDynamicCode ("AOT")]
     public static void Init (IConsoleDriver? driver = null, string? driverName = null)
     {
+        if (driverName?.StartsWith ("v2") ?? false)
+        {
+            ApplicationImpl.ChangeInstance (new ApplicationV2 ());
+        }
+
         ApplicationImpl.Instance.Init (driver, driverName);
     }
 
diff --git a/Terminal.Gui/Application/ApplicationImpl.cs b/Terminal.Gui/Application/ApplicationImpl.cs
index 2e43b24772..ad2d2c49ee 100644
--- a/Terminal.Gui/Application/ApplicationImpl.cs
+++ b/Terminal.Gui/Application/ApplicationImpl.cs
@@ -26,7 +26,7 @@ public static void ChangeInstance (IApplication newApplication)
     [RequiresDynamicCode ("AOT")]
     public virtual void Init (IConsoleDriver? driver = null, string? driverName = null)
     {
-        Application.InternalInit (driver, driverName);
+            Application.InternalInit (driver, driverName);
     }
 
     /// <summary>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index a6088042a7..1f869c26ee 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -5,12 +5,10 @@ namespace Terminal.Gui.ConsoleDrivers.V2;
 
 public class ApplicationV2 : ApplicationImpl
 {
-    private readonly string _driver;
     private IMainLoopCoordinator _coordinator;
     public ITimedEvents TimedEvents { get; } = new TimedEvents ();
-    public ApplicationV2 (string driver = null)
+    public ApplicationV2 ()
     {
-        _driver = driver;
         IsLegacy = false;
     }
 
@@ -21,7 +19,7 @@ public override void Init (IConsoleDriver driver = null, string driverName = nul
 
         Application.AddKeyBindings ();
 
-        CreateDriver (_driver ?? driverName);
+        CreateDriver (driverName);
 
         Application.Initialized = true;
 
diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs
index e789bdfb42..6280726770 100644
--- a/UICatalog/UICatalog.cs
+++ b/UICatalog/UICatalog.cs
@@ -336,12 +336,6 @@ private static void UICatalogMain (Options options)
     {
         StartConfigFileWatcher ();
 
-        if (options.Driver?.StartsWith ("v2")??false)
-        {
-            ApplicationImpl.ChangeInstance (new ApplicationV2 (options.Driver));
-            options.Driver = string.Empty;
-        }
-
         // By setting _forceDriver we ensure that if the user has specified a driver on the command line, it will be used
         // regardless of what's in a config file.
         Application.ForceDriver = _forceDriver = options.Driver;

From 74dc894602ca9436d4f5bb3afb86fff880f13a4f Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 15 Dec 2024 11:42:40 +0000
Subject: [PATCH 072/198] Add IWindowSizeMonitor

---
 .../ConsoleDrivers/V2/ConsoleDriverFacade.cs  |  8 +++-
 Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs   | 11 ++++++
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    | 16 ++++----
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  |  7 +++-
 Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs   |  2 +-
 Terminal.Gui/ConsoleDrivers/V2/V2.cd          | 39 +++++++++++++++++--
 .../ConsoleDrivers/V2/WindowSizeMonitor.cs    | 31 +++++++++++++++
 7 files changed, 99 insertions(+), 15 deletions(-)
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/WindowSizeMonitor.cs

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
index bbe34fea93..a2f76ed799 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
@@ -1,4 +1,6 @@
-namespace Terminal.Gui.ConsoleDrivers.V2;
+using System.Drawing;
+
+namespace Terminal.Gui.ConsoleDrivers.V2;
 class ConsoleDriverFacade<T> : IConsoleDriver
 {
     private readonly IInputProcessor _inputProcessor;
@@ -7,7 +9,7 @@ class ConsoleDriverFacade<T> : IConsoleDriver
     private readonly AnsiRequestScheduler _ansiRequestScheduler;
     private CursorVisibility _lastCursor = CursorVisibility.Default;
 
-    public ConsoleDriverFacade (IInputProcessor inputProcessor, IOutputBuffer outputBuffer, IConsoleOutput output, AnsiRequestScheduler ansiRequestScheduler)
+    public ConsoleDriverFacade (IInputProcessor inputProcessor, IOutputBuffer outputBuffer, IConsoleOutput output, AnsiRequestScheduler ansiRequestScheduler,IWindowSizeMonitor windowSizeMonitor)
     {
         _inputProcessor = inputProcessor;
         _output = output;
@@ -17,6 +19,8 @@ public ConsoleDriverFacade (IInputProcessor inputProcessor, IOutputBuffer output
         _inputProcessor.KeyDown += (s, e) => KeyDown?.Invoke (s, e);
         _inputProcessor.KeyUp += (s, e) => KeyUp?.Invoke (s, e);
         _inputProcessor.MouseEvent += (s, e) => MouseEvent?.Invoke (s, e);
+
+        windowSizeMonitor.SizeChanging += (_, e) => Application.OnSizeChanging (e);
     }
 
     /// <summary>Gets the location and size of the terminal screen.</summary>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
index fbf369eb4c..7faab0083a 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
@@ -1,4 +1,5 @@
 using System.Collections.Concurrent;
+using static Unix.Terminal.Curses;
 
 namespace Terminal.Gui;
 
@@ -10,6 +11,8 @@ public interface IMainLoop<T> : IDisposable
 
     public AnsiRequestScheduler AnsiRequestScheduler { get; }
 
+    public IWindowSizeMonitor WindowSizeMonitor { get; }
+
     /// <summary>
     /// Initializes the loop with a buffer from which data can be read
     /// </summary>
@@ -32,3 +35,11 @@ public interface IMainLoop<T> : IDisposable
     /// </summary>
     public void Iteration ();
 }
+
+public interface IWindowSizeMonitor
+{
+    /// <summary>Invoked when the terminal's size changed. The new size of the terminal is provided.</summary>
+    event EventHandler<SizeChangedEventArgs>? SizeChanging;
+
+    void Poll ();
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 1c1a7974e7..22d20dbfe8 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -1,4 +1,6 @@
 using System.Collections.Concurrent;
+using System.Drawing;
+using static Unix.Terminal.Curses;
 
 namespace Terminal.Gui;
 
@@ -14,6 +16,8 @@ public class MainLoop<T> : IMainLoop<T>
     public IConsoleOutput Out { get;private set; }
     public AnsiRequestScheduler AnsiRequestScheduler { get; private set; }
 
+    public IWindowSizeMonitor WindowSizeMonitor { get; private set; }
+
     public void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput)
     {
         InputBuffer = inputBuffer;
@@ -23,6 +27,7 @@ public void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer
         TimedEvents = timedEvents;
         AnsiRequestScheduler = new AnsiRequestScheduler (InputProcessor.GetParser ());
 
+        WindowSizeMonitor = new WindowSizeMonitor (Out,OutputBuffer);
     }
 
     public void Run (CancellationToken token)
@@ -44,25 +49,21 @@ public void Run (CancellationToken token)
         while (!token.IsCancellationRequested);
     }
 
-    private bool first = true;
     /// <inheritdoc />
     public void Iteration ()
     {
         InputProcessor.ProcessQueue ();
 
-
         if (Application.Top != null)
         {
 
             bool needsDrawOrLayout = AnySubviewsNeedDrawn(Application.Top);
 
+            // TODO: throttle this
+            WindowSizeMonitor.Poll ();
+
             if (needsDrawOrLayout)
             {
-                // TODO: throttle this
-                var size = Out.GetWindowSize ();
-
-                OutputBuffer.SetWindowSize (size.Width, size.Height);
-
                 // TODO: Test only
                 Application.LayoutAndDraw (true);
 
@@ -75,6 +76,7 @@ public void Iteration ()
         TimedEvents.LockAndRunIdles ();
     }
 
+
     private bool AnySubviewsNeedDrawn (View v)
     {
         if (v.NeedsDraw || v.NeedsLayout)
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index 5d504e223f..e5aba6f428 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -99,7 +99,12 @@ private void BuildFacadeIfPossible ()
     {
         if (_input != null && _output != null)
         {
-            _facade = new ConsoleDriverFacade<T> (_inputProcessor, _loop.OutputBuffer,_output,_loop.AnsiRequestScheduler);
+            _facade = new ConsoleDriverFacade<T> (
+                                                  _inputProcessor,
+                                                  _loop.OutputBuffer,
+                                                  _output,
+                                                  _loop.AnsiRequestScheduler,
+                                                  _loop.WindowSizeMonitor);
             Application.Driver = _facade;
 
             StartupSemaphore.Release ();
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
index 932b1ccc14..663883896f 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
@@ -168,7 +168,7 @@ public void Write (IOutputBuffer buffer)
     /// <inheritdoc />
     public Size GetWindowSize ()
     {
-        return new Size (Console.BufferWidth, Console.BufferHeight);
+        return new Size (Console.WindowWidth, Console.WindowHeight);
     }
 
     void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index 58f0406bec..b736753dca 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -56,6 +56,17 @@
         <Position X="-1.14" Y="0.123" />
       </MemberNameLabel>
     </AssociationLine>
+    <AssociationLine Name="Out" Type="Terminal.Gui.IConsoleOutput" ManuallyRouted="true">
+      <Path>
+        <Point X="11.852" Y="5.312" />
+        <Point X="11.852" Y="7.146" />
+        <Point X="12.75" Y="7.146" />
+        <Point X="12.75" Y="7.596" />
+        <Point X="12.76" Y="7.596" Type="JumpStart" />
+        <Point X="12.927" Y="7.596" Type="JumpEnd" />
+        <Point X="14" Y="7.596" />
+      </Path>
+    </AssociationLine>
     <AssociationLine Name="AnsiRequestScheduler" Type="Terminal.Gui.AnsiRequestScheduler" ManuallyRouted="true">
       <Path>
         <Point X="11.75" Y="4.75" />
@@ -67,8 +78,20 @@
         <Position X="0.11" Y="0.143" />
       </MemberNameLabel>
     </AssociationLine>
+    <AssociationLine Name="WindowSizeMonitor" Type="Terminal.Gui.IWindowSizeMonitor" ManuallyRouted="true">
+      <Path>
+        <Point X="12.125" Y="5.312" />
+        <Point X="12.125" Y="7" />
+        <Point X="12.844" Y="7" />
+        <Point X="12.844" Y="14.281" />
+        <Point X="13.75" Y="14.281" />
+      </Path>
+      <MemberNameLabel ManuallyPlaced="true">
+        <Position X="0.047" Y="-0.336" />
+      </MemberNameLabel>
+    </AssociationLine>
     <TypeIdentifier>
-      <HashCode>QQQAAAAAACABAQQAAAEAAAAAAAAAAAACAAAAAAAAEAI=</HashCode>
+      <HashCode>QQQAAAAAACABIQQAAAAAAAAAAAAAAAACAAAAAACAEAI=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoop.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -77,6 +100,7 @@
       <Property Name="OutputBuffer" />
       <Property Name="Out" />
       <Property Name="AnsiRequestScheduler" />
+      <Property Name="WindowSizeMonitor" />
     </ShowAsAssociation>
     <Lollipop Position="0.2" />
   </Class>
@@ -177,7 +201,7 @@
       <Compartment Name="Fields" Collapsed="true" />
     </Compartments>
     <TypeIdentifier>
-      <HashCode>AQMgAAAAAKBAgFEIBBgAQJEAAjkaQgIAGQADKABDggQ=</HashCode>
+      <HashCode>AQMgAAAAAKBAgFEIBBgAQJEAAjkaQiIAGQADKABDggQ=</HashCode>
       <FileName>ConsoleDrivers\V2\ConsoleDriverFacade.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
@@ -295,7 +319,7 @@
   <Class Name="Terminal.Gui.ConsoleDrivers.V2.ApplicationV2" Collapsed="true">
     <Position X="4.5" Y="4.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAIAgAQAAAAAQAAAAAAgAAAAAAKgIAAEAI=</HashCode>
+      <HashCode>AAAAAAAAAAAIAgAQAAAAAQAAAAAAgAEAAAAKgIAAEgI=</HashCode>
       <FileName>ConsoleDrivers\V2\ApplicationV2.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -320,7 +344,7 @@
   <Interface Name="Terminal.Gui.IMainLoop&lt;T&gt;" Collapsed="true">
     <Position X="9.25" Y="4.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAQAAAAAAAABAQQAAAAAAAAAAAAAAAACAAAAAAAAEAI=</HashCode>
+      <HashCode>AAQAAAAAAAABIQQAAAAAAAAAAAAAAAACAAAAAAAAEAI=</HashCode>
       <FileName>ConsoleDrivers\V2\IMainLoop.cs</FileName>
     </TypeIdentifier>
   </Interface>
@@ -406,6 +430,13 @@
       <FileName>Application\TimedEvents.cs</FileName>
     </TypeIdentifier>
   </Interface>
+  <Interface Name="Terminal.Gui.IWindowSizeMonitor" Collapsed="true">
+    <Position X="13.75" Y="14" Width="1.75" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAAAAAEAAAAAAAAAAAACAAAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\IMainLoop.cs</FileName>
+    </TypeIdentifier>
+  </Interface>
   <Enum Name="Terminal.Gui.AnsiResponseParserState">
     <Position X="20.5" Y="7.25" Width="2" />
     <TypeIdentifier>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowSizeMonitor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowSizeMonitor.cs
new file mode 100644
index 0000000000..9e75cf5234
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowSizeMonitor.cs
@@ -0,0 +1,31 @@
+namespace Terminal.Gui;
+
+internal class WindowSizeMonitor : IWindowSizeMonitor
+{
+    private readonly IConsoleOutput _consoleOut;
+    private readonly IOutputBuffer _outputBuffer;
+    private Size _lastSize = new Size (0,0);
+
+    /// <summary>Invoked when the terminal's size changed. The new size of the terminal is provided.</summary>
+    public event EventHandler<SizeChangedEventArgs> SizeChanging;
+
+    public WindowSizeMonitor (IConsoleOutput consoleOut, IOutputBuffer outputBuffer)
+    {
+        _consoleOut = consoleOut;
+        _outputBuffer = outputBuffer;
+    }
+
+    /// <inheritdoc />
+    public void Poll ()
+    {
+        var size = _consoleOut.GetWindowSize ();
+
+        _outputBuffer.SetWindowSize (size.Width, size.Height);
+
+        if (size != _lastSize)
+        {
+            _lastSize = size;
+            SizeChanging?.Invoke (this,new (size));
+        }
+    }
+}

From c31d11d697892e553476cf59a9d54dca47c351b2 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 15 Dec 2024 11:59:08 +0000
Subject: [PATCH 073/198] Make sleep wait in input loop conditional

---
 Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs | 17 ++++++++++++++++-
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs     | 10 ++++++++--
 Terminal.Gui/ConsoleDrivers/V2/V2.cd           |  8 ++++----
 3 files changed, 28 insertions(+), 7 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
index 9a0269be19..dce9faccd3 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
@@ -7,6 +7,12 @@ public abstract class ConsoleInput<T> : IConsoleInput<T>
 {
     private ConcurrentQueue<T>? _inputBuffer;
 
+    /// <summary>
+    /// Determines how to get the current system type, adjust
+    /// in unit tests to simulate specific timings.
+    /// </summary>
+    public Func<DateTime> Now { get; set; } = ()=>DateTime.Now;
+
     /// <inheritdoc />
     public virtual void Dispose ()
     {
@@ -29,6 +35,8 @@ public void Run (CancellationToken token)
 
         do
         {
+            var dt = Now ();
+
             if (Peek ())
             {
                 foreach (var r in Read ())
@@ -37,7 +45,14 @@ public void Run (CancellationToken token)
                 }
             }
 
-            Task.Delay (TimeSpan.FromMilliseconds (20), token).Wait (token);
+            var took = Now () - dt;
+            var sleepFor = TimeSpan.FromMilliseconds (20) - took;
+
+            if (sleepFor.Milliseconds > 0)
+            {
+                Task.Delay (sleepFor, token).Wait (token);
+            }
+
             token.ThrowIfCancellationRequested ();
         }
         while (!token.IsCancellationRequested);
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 22d20dbfe8..950d4dc782 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -18,6 +18,12 @@ public class MainLoop<T> : IMainLoop<T>
 
     public IWindowSizeMonitor WindowSizeMonitor { get; private set; }
 
+    /// <summary>
+    /// Determines how to get the current system type, adjust
+    /// in unit tests to simulate specific timings.
+    /// </summary>
+    public Func<DateTime> Now { get; set; } = () => DateTime.Now;
+
     public void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput)
     {
         InputBuffer = inputBuffer;
@@ -34,11 +40,11 @@ public void Run (CancellationToken token)
     {
         do
         {
-            var dt = DateTime.Now;
+            var dt = Now();
 
             Iteration ();
 
-            var took = DateTime.Now - dt;
+            var took = Now() - dt;
             var sleepFor = TimeSpan.FromMilliseconds (50) - took;
 
             if (sleepFor.Milliseconds > 0)
diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index b736753dca..3530b365e5 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -25,7 +25,7 @@
     <Position X="0.5" Y="1.417" Height="0.875" Width="1.7" />
   </Comment>
   <Comment CommentText="Forwarded subset of gateway functionality. These exist to allow ''subclassing' Application.  Note that most methods 'ping pong' a lot back to main gateway submethods e.g. to manipulate TopLevel etc">
-    <Position X="3.083" Y="5.292" Height="1.063" Width="2.992" />
+    <Position X="2.895" Y="5.417" Height="1.063" Width="2.992" />
   </Comment>
   <Class Name="Terminal.Gui.WindowsInput" Collapsed="true">
     <Position X="11.5" Y="3" Width="1.75" />
@@ -306,7 +306,7 @@
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.ApplicationImpl" Collapsed="true">
-    <Position X="3" Y="4.5" Width="1.5" />
+    <Position X="2.75" Y="4.5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AABAAAAAAAAIAgQQAAAAAQAAAAAAAAAAQAAKgACAAAI=</HashCode>
       <FileName>Application\ApplicationImpl.cs</FileName>
@@ -317,7 +317,7 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.ConsoleDrivers.V2.ApplicationV2" Collapsed="true">
-    <Position X="4.5" Y="4.5" Width="1.5" />
+    <Position X="4.75" Y="4.5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAIAgAQAAAAAQAAAAAAgAEAAAAKgIAAEgI=</HashCode>
       <FileName>ConsoleDrivers\V2\ApplicationV2.cs</FileName>
@@ -410,7 +410,7 @@
     </ShowAsAssociation>
   </Interface>
   <Interface Name="Terminal.Gui.IApplication">
-    <Position X="3.25" Y="1.25" Width="1.5" />
+    <Position X="3" Y="1" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAIAgQQAAAAAQAAAAAAAAAAAAAKgAAAAAI=</HashCode>
       <FileName>Application\IApplication.cs</FileName>

From c01f974edda6ce98ca5b0eedc69ea219fc7d169e Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 15 Dec 2024 18:25:22 +0000
Subject: [PATCH 074/198] Create first test for Application2

---
 .../ConsoleDrivers/V2/ApplicationV2Tests.cs   | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)
 create mode 100644 UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs

diff --git a/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs b/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
new file mode 100644
index 0000000000..6c7f768a3c
--- /dev/null
+++ b/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
@@ -0,0 +1,19 @@
+using Terminal.Gui.ConsoleDrivers.V2;
+
+namespace UnitTests.ConsoleDrivers.V2;
+public class ApplicationV2Tests
+{
+    [Fact]
+    public void TestInit_CreatesKeybindings ()
+    {
+        var v2 = new ApplicationV2 ();
+
+        Application.KeyBindings.Clear();
+
+        Assert.Empty(Application.KeyBindings.GetBindings ());
+
+        v2.Init ();
+
+        Assert.NotEmpty (Application.KeyBindings.GetBindings ());
+    }
+}

From 55fb132f19ede2582b9ff679836204ae09b67f0e Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 15 Dec 2024 18:29:49 +0000
Subject: [PATCH 075/198] Add shutdown call

---
 UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs b/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
index 6c7f768a3c..16071ead38 100644
--- a/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
+++ b/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
@@ -15,5 +15,7 @@ public void TestInit_CreatesKeybindings ()
         v2.Init ();
 
         Assert.NotEmpty (Application.KeyBindings.GetBindings ());
+
+        v2.Shutdown ();
     }
 }

From 821659708f168356c61d822c0e5201d0f0bbe62a Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 15 Dec 2024 19:45:35 +0000
Subject: [PATCH 076/198] Allow DI of input/output

---
 Terminal.Gui/Application/TimedEvents.cs       | 15 +++--
 .../ConsoleDrivers/V2/ApplicationV2.cs        | 39 +++++++----
 .../ConsoleDrivers/V2/IMainLoopCoordinator.cs |  8 +--
 Terminal.Gui/ConsoleDrivers/V2/INetInput.cs   |  4 ++
 .../ConsoleDrivers/V2/IWindowsInput.cs        |  4 ++
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  |  6 +-
 Terminal.Gui/ConsoleDrivers/V2/NetInput.cs    |  2 +-
 .../ConsoleDrivers/V2/WindowsInput.cs         |  4 +-
 Terminal.Gui/Terminal.Gui.csproj              |  1 +
 UnitTests/Application/MainLoopTests.cs        |  2 -
 .../ConsoleDrivers/V2/ApplicationV2Tests.cs   | 66 ++++++++++++++++++-
 11 files changed, 120 insertions(+), 31 deletions(-)
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/INetInput.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/IWindowsInput.cs

diff --git a/Terminal.Gui/Application/TimedEvents.cs b/Terminal.Gui/Application/TimedEvents.cs
index 2c2a667d59..12ccafa8a2 100644
--- a/Terminal.Gui/Application/TimedEvents.cs
+++ b/Terminal.Gui/Application/TimedEvents.cs
@@ -83,16 +83,21 @@ private long NudgeToUniqueKey (long k)
     // INTENT: It looks like the general architecture here is trying to be a form of publisher/consumer pattern.
     private void RunIdle ()
     {
-        List<Func<bool>> iterate;
-
-        iterate = _idleHandlers;
-        _idleHandlers = new List<Func<bool>> ();
+        Func<bool> [] iterate;
+        lock (_idleHandlers)
+        {
+            iterate = _idleHandlers.ToArray ();
+            _idleHandlers = new List<Func<bool>> ();
+        }
 
         foreach (Func<bool> idle in iterate)
         {
             if (idle ())
             {
-                _idleHandlers.Add (idle);
+                lock (_idleHandlers)
+                {
+                    _idleHandlers.Add (idle);
+                }
             }
         }
     }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index 1f869c26ee..bd1f04ea09 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -5,10 +5,31 @@ namespace Terminal.Gui.ConsoleDrivers.V2;
 
 public class ApplicationV2 : ApplicationImpl
 {
+    private readonly Func<INetInput> _netInputFactory;
+    private readonly Func<IConsoleOutput> _netOutputFactory;
+    private readonly Func<IWindowsInput> _winInputFactory;
+    private readonly Func<IConsoleOutput> _winOutputFactory;
     private IMainLoopCoordinator _coordinator;
     public ITimedEvents TimedEvents { get; } = new TimedEvents ();
-    public ApplicationV2 ()
+    public ApplicationV2 () : this (
+                                    ()=>new NetInput (),
+                                    ()=>new NetOutput (),
+                                    ()=>new WindowsInput (),
+                                    ()=>new WindowsOutput ()
+                                    )
     {
+    }
+    internal ApplicationV2 (
+        Func<INetInput> netInputFactory,
+        Func<IConsoleOutput> netOutputFactory,
+        Func<IWindowsInput> winInputFactory,
+        Func<IConsoleOutput> winOutputFactory
+        )
+    {
+        _netInputFactory = netInputFactory;
+        _netOutputFactory = netOutputFactory;
+        _winInputFactory = winInputFactory;
+        _winOutputFactory = winOutputFactory;
         IsLegacy = false;
     }
 
@@ -53,12 +74,7 @@ private void CreateDriver (string driverName)
             CreateNetSubcomponents ();
         }
 
-        _coordinator.StartAsync ();
-
-        if (!_coordinator.StartupSemaphore.WaitAsync (TimeSpan.FromSeconds (3)).Result)
-        {
-            throw new Exception ("Failed to boot MainLoopCoordinator in sensible timeframe");
-        }
+        _coordinator.StartAsync().Wait();
 
         if (Application.Driver == null)
         {
@@ -72,10 +88,10 @@ private void CreateWindowsSubcomponents ()
         var inputBuffer = new ConcurrentQueue<WindowsConsole.InputRecord> ();
         var loop = new MainLoop<WindowsConsole.InputRecord> ();
         _coordinator = new MainLoopCoordinator<WindowsConsole.InputRecord> (TimedEvents,
-                                                                            () => new WindowsInput (),
+                                                                            _winInputFactory,
                                                                             inputBuffer,
                                                                             new WindowsInputProcessor (inputBuffer),
-                                                                            () => new WindowsOutput (),
+                                                                            _winOutputFactory,
                                                                             loop);
     }
     private void CreateNetSubcomponents ()
@@ -83,10 +99,10 @@ private void CreateNetSubcomponents ()
         var inputBuffer = new ConcurrentQueue<ConsoleKeyInfo> ();
         var loop = new MainLoop<ConsoleKeyInfo> ();
         _coordinator = new MainLoopCoordinator<ConsoleKeyInfo> (TimedEvents,
-                                                                () => new NetInput (),
+                                                                _netInputFactory,
                                                                 inputBuffer,
                                                                 new NetInputProcessor (inputBuffer),
-                                                                () => new NetOutput (),
+                                                                _netOutputFactory,
                                                                 loop);
     }
 
@@ -126,6 +142,7 @@ public override void Shutdown ()
     {
         _coordinator.Stop ();
         base.Shutdown ();
+        Application.Driver = null;
     }
 
     /// <inheritdoc />
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
index a9210fa00b..0ceb2343d0 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
@@ -2,13 +2,7 @@
 
 public interface IMainLoopCoordinator
 {
-    /// <summary>
-    /// Can be waited on after calling <see cref="StartAsync"/> to know when
-    /// boot up threads are running. Do not wait unless you constructed the coordinator.
-    /// Do not wait multiple times on the same coordinator.
-    /// </summary>
-    SemaphoreSlim StartupSemaphore { get; }
-    public void StartAsync ();
+    public Task StartAsync ();
 
     public void Stop ();
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/INetInput.cs b/Terminal.Gui/ConsoleDrivers/V2/INetInput.cs
new file mode 100644
index 0000000000..9b3842ff2e
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/INetInput.cs
@@ -0,0 +1,4 @@
+namespace Terminal.Gui;
+
+internal interface INetInput :IConsoleInput<ConsoleKeyInfo>
+{ }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IWindowsInput.cs b/Terminal.Gui/ConsoleDrivers/V2/IWindowsInput.cs
new file mode 100644
index 0000000000..63bf8e2933
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/IWindowsInput.cs
@@ -0,0 +1,4 @@
+namespace Terminal.Gui;
+
+internal interface IWindowsInput : IConsoleInput<WindowsConsole.InputRecord>
+{ }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index e5aba6f428..f766d2042e 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -47,10 +47,14 @@ public MainLoopCoordinator (ITimedEvents timedEvents, Func<IConsoleInput<T>> inp
     /// <summary>
     /// Starts the main and input loop threads in separate tasks (returning immediately).
     /// </summary>
-    public void StartAsync ()
+    public async Task StartAsync ()
     {
+        // TODO: if crash on boot then semaphore never finishes
         _inputTask = Task.Run (RunInput);
         _loopTask = Task.Run (RunLoop);
+
+        // Use asynchronous semaphore waiting.
+        await StartupSemaphore.WaitAsync ().ConfigureAwait (false);
     }
 
     private void RunInput ()
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
index ed39fc866b..2c49d649db 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
@@ -1,6 +1,6 @@
 namespace Terminal.Gui;
 
-public class NetInput : ConsoleInput<ConsoleKeyInfo>
+public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
 {
     private NetWinVTConsole _adjustConsole;
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
index 4194417bb7..3dc82ffac2 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
@@ -3,7 +3,7 @@
 
 namespace Terminal.Gui;
 
-internal class WindowsInput : ConsoleInput<InputRecord>
+internal class WindowsInput : ConsoleInput<InputRecord>, IWindowsInput
 {
     private readonly nint _inputHandle;
 
@@ -111,4 +111,4 @@ public void Dispose ()
     {
         SetConsoleMode (_inputHandle, _originalConsoleMode);
     }
-}
+}
\ No newline at end of file
diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj
index 8ef9dadbcc..0d8756154c 100644
--- a/Terminal.Gui/Terminal.Gui.csproj
+++ b/Terminal.Gui/Terminal.Gui.csproj
@@ -79,6 +79,7 @@
   <ItemGroup>
     <InternalsVisibleTo Include="UnitTests" />
     <InternalsVisibleTo Include="TerminalGuiDesigner" />
+    <InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
   </ItemGroup>
   <!-- =================================================================== -->
   <!-- API Documentation -->
diff --git a/UnitTests/Application/MainLoopTests.cs b/UnitTests/Application/MainLoopTests.cs
index 8204142746..ce50dcee5c 100644
--- a/UnitTests/Application/MainLoopTests.cs
+++ b/UnitTests/Application/MainLoopTests.cs
@@ -784,8 +784,6 @@ private static void Launch (Random r, TextField tf, int target)
         Task.Run (
                   () =>
                   {
-                      Thread.Sleep (r.Next (2, 4));
-
                       Application.Invoke (
                                           () =>
                                           {
diff --git a/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs b/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
index 16071ead38..e05f9d6258 100644
--- a/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
+++ b/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
@@ -1,12 +1,23 @@
-using Terminal.Gui.ConsoleDrivers.V2;
+using Moq;
+using Terminal.Gui.ConsoleDrivers.V2;
 
 namespace UnitTests.ConsoleDrivers.V2;
 public class ApplicationV2Tests
 {
+
+    private ApplicationV2 NewApplicationV2 ()
+    {
+        return new (
+                    Mock.Of<INetInput>,
+                    Mock.Of<IConsoleOutput>,
+                    Mock.Of<IWindowsInput>,
+                    Mock.Of<IConsoleOutput>);
+    }
+
     [Fact]
     public void TestInit_CreatesKeybindings ()
     {
-        var v2 = new ApplicationV2 ();
+        var v2 = NewApplicationV2();
 
         Application.KeyBindings.Clear();
 
@@ -18,4 +29,55 @@ public void TestInit_CreatesKeybindings ()
 
         v2.Shutdown ();
     }
+
+    [Fact]
+    public void TestInit_DriverIsFacade ()
+    {
+        var v2 = NewApplicationV2();
+
+        Assert.Null (Application.Driver);
+        v2.Init ();
+        Assert.NotNull (Application.Driver);
+
+        var type = Application.Driver.GetType ();
+        Assert.True(type.IsGenericType); 
+        Assert.True (type.GetGenericTypeDefinition () == typeof (ConsoleDriverFacade<>));
+        v2.Shutdown ();
+
+        Assert.Null (Application.Driver);
+    }
+
+    [Fact]
+    public void Test_NoInitThrowOnRun ()
+    {
+        var app = NewApplicationV2();
+
+        var ex = Assert.Throws<Exception> (() => app.Run (new Window ()));
+        Assert.Equal ("App not Initialized",ex.Message);
+    }
+    /* TODO : Infinite loops
+    [Fact]
+    public void Test_InitRunShutdown ()
+    {
+        var v2 = NewApplicationV2();
+
+        v2.Init ();
+
+        v2.AddTimeout (TimeSpan.FromMilliseconds (150),
+                       () =>
+                       {
+                           if (Application.Top != null)
+                           {
+                               Application.RequestStop ();
+                               return true;
+                           }
+
+                           return true;
+                       }
+                       );
+        Assert.Null (Application.Top);
+        v2.Run (new Window ());
+        Assert.Null (Application.Top);
+        v2.Shutdown ();
+    }*/
 }

From ba09cd297915e4ff25849ca8fb13de50fcea56c5 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 15 Dec 2024 19:55:18 +0000
Subject: [PATCH 077/198] Fix wrong lock in TimedEvents

---
 Terminal.Gui/Application/TimedEvents.cs | 6 +++---
 UnitTests/Application/MainLoopTests.cs  | 2 ++
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/Terminal.Gui/Application/TimedEvents.cs b/Terminal.Gui/Application/TimedEvents.cs
index 12ccafa8a2..7e304dd341 100644
--- a/Terminal.Gui/Application/TimedEvents.cs
+++ b/Terminal.Gui/Application/TimedEvents.cs
@@ -84,7 +84,7 @@ private long NudgeToUniqueKey (long k)
     private void RunIdle ()
     {
         Func<bool> [] iterate;
-        lock (_idleHandlers)
+        lock (_idleHandlersLock)
         {
             iterate = _idleHandlers.ToArray ();
             _idleHandlers = new List<Func<bool>> ();
@@ -94,7 +94,7 @@ private void RunIdle ()
         {
             if (idle ())
             {
-                lock (_idleHandlers)
+                lock (_idleHandlersLock)
                 {
                     _idleHandlers.Add (idle);
                 }
@@ -265,7 +265,7 @@ public bool CheckTimersAndIdleHandlers (out int waitTimeout)
 
         // There are no timers set, check if there are any idle handlers
 
-        lock (_idleHandlers)
+        lock (_idleHandlersLock)
         {
             return _idleHandlers.Count > 0;
         }
diff --git a/UnitTests/Application/MainLoopTests.cs b/UnitTests/Application/MainLoopTests.cs
index ce50dcee5c..8204142746 100644
--- a/UnitTests/Application/MainLoopTests.cs
+++ b/UnitTests/Application/MainLoopTests.cs
@@ -784,6 +784,8 @@ private static void Launch (Random r, TextField tf, int target)
         Task.Run (
                   () =>
                   {
+                      Thread.Sleep (r.Next (2, 4));
+
                       Application.Invoke (
                                           () =>
                                           {

From 52a0dec4d97247d4dfb1c70bb89f39834dc78159 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Mon, 16 Dec 2024 16:48:35 +0000
Subject: [PATCH 078/198] tests for AnsiResponseParser HandleMouse

---
 .../ConsoleDrivers/AnsiResponseParserTests.cs | 39 +++++++++++++++++++
 1 file changed, 39 insertions(+)

diff --git a/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs b/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs
index fbc87761f5..32ae7dbff3 100644
--- a/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs
+++ b/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs
@@ -474,6 +474,45 @@ public void UnknownResponses_ParameterShouldMatch ()
         Assert.Equal (expectedUnknownResponses, unknownResponses);
     }
 
+    [Fact]
+    public void ParserDetectsMouse ()
+    {
+        // ANSI escape sequence for mouse down (using a generic format example)
+        const string MOUSE_DOWN = "\u001B[<0;12;32M";
+
+        // ANSI escape sequence for Device Attribute Response (e.g., Terminal identifying itself)
+        const string DEVICE_ATTRIBUTE_RESPONSE = "\u001B[?1;2c";
+
+        // ANSI escape sequence for mouse up (using a generic format example)
+        const string MOUSE_UP = "\u001B[<0;25;50m";
+
+        var parser = new AnsiResponseParser ();
+
+        parser.HandleMouse = true;
+        string? foundDar = null;
+        List<MouseEventArgs> mouseEventArgs = new ();
+
+        parser.Mouse += (s, e) => mouseEventArgs.Add (e);
+        parser.ExpectResponse ("c", (dar) => foundDar = dar, null, false);
+        var released = parser.ProcessInput ("a" + MOUSE_DOWN + "asdf" + DEVICE_ATTRIBUTE_RESPONSE + "bbcc" + MOUSE_UP + "sss");
+
+        Assert.Equal ("aasdfbbccsss", released);
+
+        Assert.Equal (2, mouseEventArgs.Count);
+
+        Assert.NotNull (foundDar);
+        Assert.Equal (DEVICE_ATTRIBUTE_RESPONSE,foundDar);
+
+        Assert.True (mouseEventArgs [0].IsPressed);
+        // Mouse positions in ANSI are 1 based so actual Terminal.Gui Screen positions are x-1,y-1
+        Assert.Equal (11,mouseEventArgs [0].Position.X);
+        Assert.Equal (31, mouseEventArgs [0].Position.Y);
+
+        Assert.True (mouseEventArgs [1].IsReleased);
+        Assert.Equal (24, mouseEventArgs [1].Position.X);
+        Assert.Equal (49, mouseEventArgs [1].Position.Y);
+    }
+
     private Tuple<char, int> [] StringToBatch (string batch)
     {
         return batch.Select ((k) => Tuple.Create (k, tIndex++)).ToArray ();

From e0f66434210cfef426830e3131234212ac667ead Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Mon, 16 Dec 2024 17:06:26 +0000
Subject: [PATCH 079/198] Switch to established method EscSeqUtils.MapKey

---
 .../ConsoleDrivers/ConsoleKeyMapping.cs       | 104 ------------------
 .../ConsoleDrivers/V2/NetInputProcessor.cs    |   2 +-
 2 files changed, 1 insertion(+), 105 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs
index 27d580e9a8..6fce2e040f 100644
--- a/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs
+++ b/Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs
@@ -739,110 +739,6 @@ internal static uint MapKeyCodeToConsoleKey (KeyCode keyValue, out bool isConsol
         return (uint)keyValue;
     }
 
-    public static KeyCode MapKey (ConsoleKeyInfo keyInfo)
-    {
-        // TODO: I Copied this from NetDriver and made it static because it is about ConsoleKeyMapping so belongs here
-        //       However there is a separate version in FakeDriver ?! Is that just an older version of the same code?!
-
-        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));
-    }
-
     /// <summary>Maps a <see cref="ConsoleKeyInfo"/> to a <see cref="KeyCode"/>.</summary>
     /// <param name="consoleKeyInfo">The console key.</param>
     /// <returns>The <see cref="KeyCode"/> or the <paramref name="consoleKeyInfo"/>.</returns>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
index 22f0057ae2..398232deb8 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
@@ -23,7 +23,7 @@ protected override void Process (ConsoleKeyInfo consoleKeyInfo)
     /// <inheritdoc />
     protected override void ProcessAfterParsing (ConsoleKeyInfo input)
     {
-        var key = ConsoleKeyMapping.MapKey (input);
+        var key = EscSeqUtils.MapKey (input);
         OnKeyDown (key);
         OnKeyUp (key);
     }

From efd6b975652ea2c401d74dc3215dc1fadae38b90 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Mon, 16 Dec 2024 17:54:05 +0000
Subject: [PATCH 080/198] ConsoleInput test

---
 .../ConsoleDrivers/V2/ConsoleInput.cs         | 44 +++++++-------
 .../ConsoleDrivers/V2/ConsoleInputTests.cs    | 57 +++++++++++++++++++
 2 files changed, 82 insertions(+), 19 deletions(-)
 create mode 100644 UnitTests/ConsoleDrivers/V2/ConsoleInputTests.cs

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
index dce9faccd3..deb996e076 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
@@ -28,34 +28,40 @@ public void Initialize (ConcurrentQueue<T> inputBuffer)
     /// <inheritdoc />
     public void Run (CancellationToken token)
     {
-        if (_inputBuffer == null)
+        try
         {
-            throw new ("Cannot run input before Initialization");
-        }
-
-        do
-        {
-            var dt = Now ();
+            if (_inputBuffer == null)
+            {
+                throw new ("Cannot run input before Initialization");
+            }
 
-            if (Peek ())
+            do
             {
-                foreach (var r in Read ())
+                var dt = Now ();
+
+                if (Peek ())
                 {
-                    _inputBuffer.Enqueue (r);
+                    foreach (var r in Read ())
+                    {
+                        _inputBuffer.Enqueue (r);
+                    }
                 }
-            }
 
-            var took = Now () - dt;
-            var sleepFor = TimeSpan.FromMilliseconds (20) - took;
+                var took = Now () - dt;
+                var sleepFor = TimeSpan.FromMilliseconds (20) - took;
 
-            if (sleepFor.Milliseconds > 0)
-            {
-                Task.Delay (sleepFor, token).Wait (token);
-            }
+                if (sleepFor.Milliseconds > 0)
+                {
+                    Task.Delay (sleepFor, token).Wait (token);
+                }
 
-            token.ThrowIfCancellationRequested ();
+                token.ThrowIfCancellationRequested ();
+            }
+            while (!token.IsCancellationRequested);
+        }
+        catch (OperationCanceledException)
+        {
         }
-        while (!token.IsCancellationRequested);
     }
 
     /// <summary>
diff --git a/UnitTests/ConsoleDrivers/V2/ConsoleInputTests.cs b/UnitTests/ConsoleDrivers/V2/ConsoleInputTests.cs
new file mode 100644
index 0000000000..888cd5943b
--- /dev/null
+++ b/UnitTests/ConsoleDrivers/V2/ConsoleInputTests.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace UnitTests.ConsoleDrivers.V2;
+public class ConsoleInputTests
+{
+    class FakeInput : ConsoleInput<char>
+    {
+        private readonly string [] _reads;
+
+        public FakeInput (params string [] reads ) { _reads = reads; }
+
+        int iteration = 0;
+        /// <inheritdoc />
+        protected override bool Peek ()
+        {
+            return iteration < _reads.Length;
+        }
+
+        /// <inheritdoc />
+        protected override IEnumerable<char> Read ()
+        {
+            return _reads [iteration++];
+        }
+    }
+
+    [Fact]
+    public void Test_ThrowsIfNotInitialized ()
+    {
+        var input = new FakeInput ("Fish");
+
+        var ex = Assert.Throws<Exception>(()=>input.Run (new (canceled: true)));
+        Assert.Equal ("Cannot run input before Initialization", ex.Message);
+    }
+
+
+    [Fact]
+    public void Test_Simple ()
+    {
+        var input = new FakeInput ("Fish");
+        var queue = new ConcurrentQueue<char> ();
+
+        input.Initialize (queue);
+
+        var cts = new CancellationTokenSource ();
+        cts.CancelAfter (25); // Cancel after 25 milliseconds
+        CancellationToken token = cts.Token;
+        Assert.Empty (queue);
+        input.Run (token);
+
+        Assert.Equal ("Fish",new string (queue.ToArray ()));
+    }
+}

From 0a0da5999ab7227b9bcdd8ebb9f45da05a7aca22 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 19 Dec 2024 07:27:57 +0000
Subject: [PATCH 081/198] Add try/catch for the two main loops

---
 .../ConsoleDrivers/V2/ApplicationV2.cs        |  8 ++-
 Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs   |  1 +
 .../ConsoleDrivers/V2/IMainLoopCoordinator.cs |  3 +
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    |  4 ++
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  | 65 ++++++++++++-------
 5 files changed, 56 insertions(+), 25 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index bd1f04ea09..1b2222d242 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -133,6 +133,11 @@ public override void Run (Toplevel view, Func<Exception, bool> errorHandler = nu
         // TODO : how to know when we are done?
         while (Application.TopLevels.TryPeek (out var found) && found == view)
         {
+            var ex = _coordinator.InputCrashedException ?? _coordinator.LoopCrashedException;
+            if(ex != null)
+            {
+                throw ex;
+            }
             Task.Delay (100).Wait ();
         }
     }
@@ -148,7 +153,8 @@ public override void Shutdown ()
     /// <inheritdoc />
     public override void RequestStop (Toplevel top)
     {
-        Application.TopLevels.Pop ();
+        // TODO: This definition of stop seems sketchy
+        Application.TopLevels.TryPop (out _);
 
         if(Application.TopLevels.Count>0)
         {
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
index 7faab0083a..a0875eaaca 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
@@ -5,6 +5,7 @@ namespace Terminal.Gui;
 
 public interface IMainLoop<T> : IDisposable
 {
+
     public ITimedEvents TimedEvents { get; }
     public IOutputBuffer OutputBuffer { get; }
     public IInputProcessor InputProcessor { get; }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
index 0ceb2343d0..c98f78bbbd 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
@@ -5,4 +5,7 @@ public interface IMainLoopCoordinator
     public Task StartAsync ();
 
     public void Stop ();
+
+    public Exception InputCrashedException { get; }
+    public Exception LoopCrashedException { get; }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 950d4dc782..eec20eed1c 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -4,9 +4,12 @@
 
 namespace Terminal.Gui;
 
+/// <inheritdoc/>
 public class MainLoop<T> : IMainLoop<T>
 {
+    /// <inheritdoc/>
     public ITimedEvents TimedEvents { get; private set; }
+
     public ConcurrentQueue<T> InputBuffer { get; private set; } = new ();
 
     public IInputProcessor InputProcessor { get; private set; }
@@ -36,6 +39,7 @@ public void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer
         WindowSizeMonitor = new WindowSizeMonitor (Out,OutputBuffer);
     }
 
+    /// <inheritdoc/>
     public void Run (CancellationToken token)
     {
         do
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index f766d2042e..2b7dce037b 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -19,6 +19,9 @@ public class MainLoopCoordinator<T> : IMainLoopCoordinator
     private Task _loopTask;
     private ITimedEvents _timedEvents;
 
+    public Exception InputCrashedException { get; private set; }
+    public Exception LoopCrashedException { get; private set; }
+
     public SemaphoreSlim StartupSemaphore { get; } = new (0, 1);
 
     /// <summary>
@@ -59,44 +62,58 @@ public async Task StartAsync ()
 
     private void RunInput ()
     {
-        lock (oLockInitialization)
-        {
-            // Instance must be constructed on the thread in which it is used.
-            _input = _inputFactory.Invoke ();
-            _input.Initialize (_inputBuffer);
-
-            BuildFacadeIfPossible ();
-        }
-
         try
         {
-            _input.Run (tokenSource.Token);
+            lock (oLockInitialization)
+            {
+                // Instance must be constructed on the thread in which it is used.
+                _input = _inputFactory.Invoke ();
+                _input.Initialize (_inputBuffer);
+
+                BuildFacadeIfPossible ();
+            }
+
+            try
+            {
+                _input.Run (tokenSource.Token);
+            }
+            catch (OperationCanceledException)
+            {
+            }
+            _input.Dispose ();
         }
-        catch (OperationCanceledException)
+        catch (Exception e)
         {
+            InputCrashedException = e;
         }
-        _input.Dispose ();
     }
 
     private void RunLoop ()
     {
-        lock (oLockInitialization)
-        {
-            // Instance must be constructed on the thread in which it is used.
-            _output = _outputFactory.Invoke ();
-            _loop.Initialize (_timedEvents, _inputBuffer, _inputProcessor, _output);
-
-            BuildFacadeIfPossible ();
-        }
-
         try
         {
-            _loop.Run (tokenSource.Token);
+            lock (oLockInitialization)
+            {
+                // Instance must be constructed on the thread in which it is used.
+                _output = _outputFactory.Invoke ();
+                _loop.Initialize (_timedEvents, _inputBuffer, _inputProcessor, _output);
+
+                BuildFacadeIfPossible ();
+            }
+
+            try
+            {
+                _loop.Run (tokenSource.Token);
+            }
+            catch (OperationCanceledException)
+            {
+            }
+            _loop.Dispose ();
         }
-        catch (OperationCanceledException)
+        catch (Exception e)
         {
+            LoopCrashedException = e;
         }
-        _loop.Dispose ();
     }
 
     private void BuildFacadeIfPossible ()

From 2e35203814724805f136b3fe1b40d987c91f1277 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Fri, 20 Dec 2024 19:48:42 +0000
Subject: [PATCH 082/198] Make MainLoop<T> execute as part of Application2.Run
 instead of in its own thread

---
 .../ConsoleDrivers/V2/ApplicationV2.cs        |  7 +--
 Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs   |  8 ---
 .../ConsoleDrivers/V2/IMainLoopCoordinator.cs |  2 +-
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    | 13 ++---
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  | 56 +++++++------------
 5 files changed, 26 insertions(+), 60 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index 1b2222d242..4adc311cfb 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -133,12 +133,7 @@ public override void Run (Toplevel view, Func<Exception, bool> errorHandler = nu
         // TODO : how to know when we are done?
         while (Application.TopLevels.TryPeek (out var found) && found == view)
         {
-            var ex = _coordinator.InputCrashedException ?? _coordinator.LoopCrashedException;
-            if(ex != null)
-            {
-                throw ex;
-            }
-            Task.Delay (100).Wait ();
+            _coordinator.RunIteration ();
         }
     }
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
index a0875eaaca..c99eaeb913 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
@@ -23,14 +23,6 @@ public interface IMainLoop<T> : IDisposable
     /// <param name="consoleOutput"></param>
     void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput);
 
-    /// <summary>
-    /// Runs <see cref="Iteration"/> in an infinite loop.
-    /// </summary>
-    /// <param name="token"></param>
-    /// <exception cref="OperationCanceledException">Raised when token is
-    /// cancelled. This is the only means of exiting.</exception>
-    public void Run (CancellationToken token);
-
     /// <summary>
     /// Perform a single iteration of the main loop without blocking anywhere.
     /// </summary>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
index c98f78bbbd..b511bee278 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
@@ -7,5 +7,5 @@ public interface IMainLoopCoordinator
     public void Stop ();
 
     public Exception InputCrashedException { get; }
-    public Exception LoopCrashedException { get; }
+    void RunIteration ();
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index eec20eed1c..0a852eb57a 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -40,27 +40,22 @@ public void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer
     }
 
     /// <inheritdoc/>
-    public void Run (CancellationToken token)
+    public void Iteration ()
     {
-        do
-        {
             var dt = Now();
 
-            Iteration ();
+            IterationImpl ();
 
             var took = Now() - dt;
             var sleepFor = TimeSpan.FromMilliseconds (50) - took;
 
             if (sleepFor.Milliseconds > 0)
             {
-                Task.Delay (sleepFor, token).Wait (token);
+                Task.Delay (sleepFor).Wait ();
             }
-        }
-        while (!token.IsCancellationRequested);
     }
 
-    /// <inheritdoc />
-    public void Iteration ()
+    public void IterationImpl ()
     {
         InputProcessor.ProcessQueue ();
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index 2b7dce037b..61719c5548 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -16,14 +16,13 @@ public class MainLoopCoordinator<T> : IMainLoopCoordinator
     object oLockInitialization = new ();
     private ConsoleDriverFacade<T> _facade;
     private Task _inputTask;
-    private Task _loopTask;
     private ITimedEvents _timedEvents;
 
     public Exception InputCrashedException { get; private set; }
-    public Exception LoopCrashedException { get; private set; }
 
     public SemaphoreSlim StartupSemaphore { get; } = new (0, 1);
 
+
     /// <summary>
     /// Creates a new coordinator
     /// </summary>
@@ -48,13 +47,15 @@ public MainLoopCoordinator (ITimedEvents timedEvents, Func<IConsoleInput<T>> inp
     }
 
     /// <summary>
-    /// Starts the main and input loop threads in separate tasks (returning immediately).
+    /// Starts the input loop thread in separate task (returning immediately).
     /// </summary>
     public async Task StartAsync ()
     {
         // TODO: if crash on boot then semaphore never finishes
         _inputTask = Task.Run (RunInput);
-        _loopTask = Task.Run (RunLoop);
+
+        // Main loop is now booted on same thread as rest of users application
+        BootMainLoop ();
 
         // Use asynchronous semaphore waiting.
         await StartupSemaphore.WaitAsync ().ConfigureAwait (false);
@@ -88,31 +89,23 @@ private void RunInput ()
         }
     }
 
-    private void RunLoop ()
+    /// <inheritdoc />
+    public void RunIteration ()
     {
-        try
-        {
-            lock (oLockInitialization)
-            {
-                // Instance must be constructed on the thread in which it is used.
-                _output = _outputFactory.Invoke ();
-                _loop.Initialize (_timedEvents, _inputBuffer, _inputProcessor, _output);
 
-                BuildFacadeIfPossible ();
-            }
+        _loop.Iteration ();
+    }
 
-            try
-            {
-                _loop.Run (tokenSource.Token);
-            }
-            catch (OperationCanceledException)
-            {
-            }
-            _loop.Dispose ();
-        }
-        catch (Exception e)
+
+    private void BootMainLoop ()
+    {
+        lock (oLockInitialization)
         {
-            LoopCrashedException = e;
+            // Instance must be constructed on the thread in which it is used.
+            _output = _outputFactory.Invoke ();
+            _loop.Initialize (_timedEvents, _inputBuffer, _inputProcessor, _output);
+
+            BuildFacadeIfPossible ();
         }
     }
 
@@ -136,16 +129,7 @@ public void Stop ()
     {
         tokenSource.Cancel();
 
-        if (_loopTask.Id == Task.CurrentId)
-        {
-            // Cannot wait for loop to finish because we are actively executing on that thread
-            Task.WhenAll (_inputTask).Wait ();
-        }
-        else
-        {
-            // Wait for both tasks to complete
-            Task.WhenAll (_inputTask, _loopTask).Wait ();
-        }
-
+        // Wait for input infinite loop to exit
+        Task.WhenAll (_inputTask).Wait ();
     }
 }
\ No newline at end of file

From 7dacaccaa7c3540dcf758194a0cdf1874e2a9adb Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 21 Dec 2024 10:02:00 +0000
Subject: [PATCH 083/198] Add clipboards

---
 .../ConsoleDrivers/V2/ConsoleDriverFacade.cs  | 31 +++++++++++++++++--
 1 file changed, 29 insertions(+), 2 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
index a2f76ed799..541f7d8bf9 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
@@ -1,4 +1,5 @@
 using System.Drawing;
+using System.Runtime.InteropServices;
 
 namespace Terminal.Gui.ConsoleDrivers.V2;
 class ConsoleDriverFacade<T> : IConsoleDriver
@@ -21,6 +22,33 @@ public ConsoleDriverFacade (IInputProcessor inputProcessor, IOutputBuffer output
         _inputProcessor.MouseEvent += (s, e) => MouseEvent?.Invoke (s, e);
 
         windowSizeMonitor.SizeChanging += (_, e) => Application.OnSizeChanging (e);
+
+        CreateClipboard ();
+    }
+
+    private void CreateClipboard ()
+    {
+        PlatformID p = Environment.OSVersion.Platform;
+
+        if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
+        {
+            Clipboard = new WindowsClipboard ();
+        }
+        else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX))
+        {
+            Clipboard = new MacOSXClipboard ();
+        }
+        else
+        {
+            if (CursesDriver.Is_WSL_Platform ())
+            {
+                Clipboard = new WSLClipboard ();
+            }
+            else
+            {
+                Clipboard = new FakeDriver.FakeClipboard ();
+            }
+        }
     }
 
     /// <summary>Gets the location and size of the terminal screen.</summary>
@@ -36,9 +64,8 @@ public Region Clip {
         set => _outputBuffer.Clip = value;
     }
 
-    // TODO: Clipboard support
     /// <summary>Get the operating system clipboard.</summary>
-    public IClipboard Clipboard { get; } = new FakeDriver.FakeClipboard ();
+    public IClipboard Clipboard { get; private set; } = new FakeDriver.FakeClipboard ();
 
     /// <summary>
     ///     Gets the column last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/> are used by

From c6e96dc27472bc1ccab7eb558357e5106622622a Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 21 Dec 2024 10:03:11 +0000
Subject: [PATCH 084/198] Simplify branching structure of clipboard
 construction

---
 .../ConsoleDrivers/V2/ConsoleDriverFacade.cs        | 13 +++++--------
 1 file changed, 5 insertions(+), 8 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
index 541f7d8bf9..6610f7e227 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
@@ -38,16 +38,13 @@ private void CreateClipboard ()
         {
             Clipboard = new MacOSXClipboard ();
         }
+        else if (CursesDriver.Is_WSL_Platform ())
+        {
+            Clipboard = new WSLClipboard ();
+        }
         else
         {
-            if (CursesDriver.Is_WSL_Platform ())
-            {
-                Clipboard = new WSLClipboard ();
-            }
-            else
-            {
-                Clipboard = new FakeDriver.FakeClipboard ();
-            }
+            Clipboard = new FakeDriver.FakeClipboard ();
         }
     }
 

From 43547126f274f785ae883cc3f0efabedc5eba1aa Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 21 Dec 2024 10:31:40 +0000
Subject: [PATCH 085/198] Add mouse button narratives for all buttons not just
 1

---
 Terminal.Gui/ConsoleDrivers/V2/MouseState.cs | 50 +++++++++++++-------
 1 file changed, 34 insertions(+), 16 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseState.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseState.cs
index 78f357305a..d465480498 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MouseState.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseState.cs
@@ -56,22 +56,28 @@ public MouseInterpreter (
 
     public IEnumerable<ButtonNarrative> Process (MouseEventArgs e)
     {
-        // Update narratives
-       if (e.Flags.HasFlag (MouseFlags.Button1Pressed))
-       {
-           if (_ongoingNarratives [0] == null)
-           {
-               _ongoingNarratives [0] = BeginPressedNarrative (0,e);
-           }
-           else
-           {
-               _ongoingNarratives [0]?.Process (0,e.Position,true);
-           }
-       }
-       else
-       {
-           _ongoingNarratives [0]?.Process (0,e.Position,false);
-       }
+        // For each mouse button
+        for (int i = 0; i < 4; i++)
+        {
+            bool isPressed = IsPressed (i, e.Flags);
+
+            // Update narratives
+            if (isPressed)
+            {
+                if (_ongoingNarratives [i] == null)
+                {
+                    _ongoingNarratives [i] = BeginPressedNarrative (i, e);
+                }
+                else
+                {
+                    _ongoingNarratives [i]?.Process (i, e.Position, true);
+                }
+            }
+            else
+            {
+                _ongoingNarratives [i]?.Process (i, e.Position, false);
+            }
+        }
 
        for (var i = 0; i < _ongoingNarratives.Length; i++)
        {
@@ -88,6 +94,18 @@ public IEnumerable<ButtonNarrative> Process (MouseEventArgs e)
        }
     }
 
+    private bool IsPressed (int btn, MouseFlags eFlags)
+    {
+        return btn switch
+               {
+                   0=>eFlags.HasFlag (MouseFlags.Button1Pressed),
+                   1 => eFlags.HasFlag (MouseFlags.Button2Pressed),
+                   2 => eFlags.HasFlag (MouseFlags.Button3Pressed),
+                   3 => eFlags.HasFlag (MouseFlags.Button4Pressed),
+                   _ => throw new ArgumentOutOfRangeException(nameof(btn))
+               };
+    }
+
     private bool ShouldRelease (ButtonNarrative narrative)
     {
         // TODO: needs to be way smarter

From 83b60d2c4850b907de6237290e455552b48ef5ad Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 22 Dec 2024 19:12:32 +0000
Subject: [PATCH 086/198] Refactor MouseInterpreter

---
 .../ConsoleDrivers/V2/IInputProcessor.cs      |   5 -
 .../ConsoleDrivers/V2/InputProcessor.cs       |  24 +-
 .../ConsoleDrivers/V2/MouseButtonSequence.cs  | 148 ++++++++++
 .../ConsoleDrivers/V2/MouseButtonState.cs     |  54 ++++
 .../ConsoleDrivers/V2/MouseInterpreter.cs     | 165 +++++++++++
 Terminal.Gui/ConsoleDrivers/V2/MouseState.cs  | 268 +-----------------
 Terminal.Gui/ConsoleDrivers/V2/V2.cd          |  88 +++---
 .../V2/MouseInterpreterTests.cs               |  34 +++
 8 files changed, 442 insertions(+), 344 deletions(-)
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/MouseButtonSequence.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/MouseButtonState.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
 create mode 100644 UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs

diff --git a/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
index 580068ad49..e1dcb357be 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
@@ -15,11 +15,6 @@ public interface IInputProcessor
 
     /// <summary>Event fired when a mouse event occurs.</summary>
     event EventHandler<MouseEventArgs>? MouseEvent;
-    
-    /// <summary>
-    /// Process low level mouse events into atomic operations / sequences
-    /// </summary>
-    MouseInterpreter MouseInterpreter { get; }
 
     /// <summary>
     ///     Called when a key is pressed down. Fires the <see cref="KeyDown"/> event. This is a precursor to
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index 8f203fdf35..282efa920b 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -15,7 +15,7 @@ public abstract class InputProcessor<T> : IInputProcessor
 
     public IAnsiResponseParser GetParser () => Parser;
 
-    public MouseInterpreter MouseInterpreter { get; } = new ();
+    private MouseInterpreter _mouseInterpreter { get; } = new ();
 
     /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="KeyUp"/>.</summary>
     public event EventHandler<Key>? KeyDown;
@@ -56,27 +56,9 @@ public void OnMouseEvent (MouseEventArgs a)
         MouseEvent?.Invoke (this, a);
 
         // Pass on any interpreted states e.g. click/double click etc
-        foreach (var narrative in MouseInterpreter.Process (a))
+        foreach (var e in _mouseInterpreter.Process (a))
         {
-            ResolveNarrative (narrative);
-        }
-    }
-
-    private void ResolveNarrative (ButtonNarrative narrative)
-    {
-        if (narrative.NumberOfClicks == 1)
-        {
-            var last = narrative.MouseStates.Last ();
-
-            // its a click
-            MouseEvent?.Invoke (this, new MouseEventArgs
-            {
-                Handled = false,
-                Flags = MouseFlags.Button1Clicked,
-                ScreenPosition = last.Position,
-                Position = last.ViewportPosition,
-                View = last.View,
-            });
+            MouseEvent?.Invoke (this, e);
         }
     }
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseButtonSequence.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseButtonSequence.cs
new file mode 100644
index 0000000000..f1aee77798
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseButtonSequence.cs
@@ -0,0 +1,148 @@
+#nullable enable
+using Terminal.Gui.ConsoleDrivers.V2;
+
+namespace Terminal.Gui;
+
+/// <summary>
+/// Describes a completed narrative e.g. 'user triple clicked'.
+/// </summary>
+/// <remarks>Internally we can have a double click narrative that becomes
+/// a triple click narrative. But we will not release both i.e. we don't say
+/// user clicked then user double-clicked then user triple clicked</remarks>
+internal class MouseButtonSequence
+{
+    private readonly IViewFinder _viewFinder;
+    public int NumberOfClicks { get; set; }
+
+    /// <summary>
+    /// Mouse states during which click was generated.
+    /// N = 2x<see cref="NumberOfClicks"/>
+    /// </summary>
+    public List<MouseButtonStateEx> MouseStates { get; set; } = new ();
+
+    public int ButtonIdx { get; }
+
+    /// <summary>
+    /// Function for returning the current time. Use in unit tests to
+    /// ensure repeatable tests.
+    /// </summary>
+    public Func<DateTime> Now { get; set; }
+
+    public MouseButtonSequence (int buttonIdx, Func<DateTime> now, IViewFinder viewFinder)
+    {
+        ButtonIdx = buttonIdx;
+        Now = now;
+        _viewFinder = viewFinder;
+    }
+
+    public MouseEventArgs? Process (Point position, bool pressed)
+    {
+        var last = MouseStates.Last ();
+
+        // Still pressed
+        if (last.Pressed && pressed)
+        {
+            // No change
+            return null;
+        }
+
+        var view = _viewFinder.GetViewAt (position, out var viewport);
+
+        NumberOfClicks++;
+        MouseStates.Add (new MouseButtonStateEx
+        {
+            Button = ButtonIdx,
+            At = Now(),
+            Pressed = false,
+            Position = position,
+
+            View = view,
+            ViewportPosition = viewport,
+
+            /* TODO: Need these probably*/
+            Shift = false,
+            Ctrl = false,
+            Alt = false,
+
+        });
+
+
+        if (IsResolveable ())
+        {
+            return Resolve ();
+        }
+
+        return null;
+    }
+
+    public bool IsResolveable ()
+    {
+        return NumberOfClicks > 0;
+    }
+    /// <summary>
+    /// Resolves the narrative completely with immediate effect.
+    /// This may return null if e.g. so far all that has accumulated is a mouse down.
+    /// </summary>
+    /// <returns></returns>
+    public MouseEventArgs? Resolve ()
+    {
+        if (!IsResolveable ())
+        {
+            return null;
+        }
+
+        if (NumberOfClicks == 1)
+        {
+            var last = MouseStates.Last ();
+
+            // its a click
+            return new MouseEventArgs
+            {
+                Handled = false,
+                Flags = ToClicks(this.ButtonIdx,NumberOfClicks),
+                ScreenPosition = last.Position,
+                Position = last.ViewportPosition,
+                View = last.View,
+            };
+        }
+
+        return null;
+    }
+
+    private MouseFlags ToClicks (int buttonIdx, int numberOfClicks)
+    {
+        if (numberOfClicks == 0)
+        {
+            throw new ArgumentOutOfRangeException (nameof (numberOfClicks), "Zero clicks are not valid.");
+        }
+
+        return buttonIdx switch
+               {
+                   0 => numberOfClicks switch
+                        {
+                            1 => MouseFlags.Button1Clicked,
+                            2 => MouseFlags.Button1DoubleClicked,
+                            _ => MouseFlags.Button1TripleClicked
+                        },
+                   1 => numberOfClicks switch
+                        {
+                            1 => MouseFlags.Button2Clicked,
+                            2 => MouseFlags.Button2DoubleClicked,
+                            _ => MouseFlags.Button2TripleClicked
+                        },
+                   2 => numberOfClicks switch
+                        {
+                            1 => MouseFlags.Button3Clicked,
+                            2 => MouseFlags.Button3DoubleClicked,
+                            _ => MouseFlags.Button3TripleClicked
+                        },
+                   3 => numberOfClicks switch
+                        {
+                            1 => MouseFlags.Button4Clicked,
+                            2 => MouseFlags.Button4DoubleClicked,
+                            _ => MouseFlags.Button4TripleClicked
+                        },
+                   _ => throw new ArgumentOutOfRangeException (nameof (buttonIdx), "Unsupported button index")
+               };
+    }
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseButtonState.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseButtonState.cs
new file mode 100644
index 0000000000..f190321034
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseButtonState.cs
@@ -0,0 +1,54 @@
+#nullable enable
+namespace Terminal.Gui;
+
+/// <summary>
+/// Not to be confused with <see cref="NetEvents.MouseButtonState"/>
+/// </summary>
+internal class MouseButtonStateEx
+{
+    public required int Button { get; set; }
+    /// <summary>
+    ///     When the button entered its current state.
+    /// </summary>
+    public DateTime At { get; set; }
+
+    /// <summary>
+    /// <see langword="true"/> if the button is currently down
+    /// </summary>
+    public bool Pressed { get; set; }
+
+    /// <summary>
+    /// The screen location when the mouse button entered its current state
+    /// (became pressed or was released)
+    /// </summary>
+    public Point Position { get; set; }
+
+    /// <summary>
+    /// The <see cref="View"/> (if any) that was at the <see cref="Position"/>
+    /// when the button entered its current state.
+    /// </summary>
+    public View? View { get; set; }
+
+    /// <summary>
+    /// Viewport relative position within <see cref="View"/> (if there is one)
+    /// </summary>
+    public Point ViewportPosition { get; set; }
+
+    /// <summary>
+    /// True if shift was provided by the console at the time the mouse
+    ///  button entered its current state.
+    /// </summary>
+    public bool Shift { get; set; }
+
+    /// <summary>
+    /// True if control was provided by the console at the time the mouse
+    /// button entered its current state.
+    /// </summary>
+    public bool Ctrl { get; set; }
+
+    /// <summary>
+    /// True if alt was held down at the time the mouse
+    /// button entered its current state.
+    /// </summary>
+    public bool Alt { get; set; }
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
new file mode 100644
index 0000000000..a7d0f9f17e
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
@@ -0,0 +1,165 @@
+#nullable enable
+using Terminal.Gui.ConsoleDrivers.V2;
+
+namespace Terminal.Gui;
+
+internal class MouseInterpreter
+{
+    private readonly IViewFinder _viewFinder;
+
+    /// <summary>
+    /// Function for returning the current time. Use in unit tests to
+    /// ensure repeatable tests.
+    /// </summary>
+    private Func<DateTime> Now { get; set; }
+
+    /// <summary>
+    /// How long to wait for a second click after the first before giving up and
+    /// releasing event as a 'click'
+    /// </summary>
+    public TimeSpan DoubleClickThreshold { get; set; }
+
+    /// <summary>
+    /// How long to wait for a third click after the second before giving up and
+    /// releasing event as a 'double click'
+    /// </summary>
+    public TimeSpan TripleClickThreshold { get; set; }
+
+    /// <summary>
+    /// How far between a mouse down and mouse up before it is considered a 'drag' rather
+    /// than a 'click'. Console row counts for 2 units while column counts for only 1. Distance is
+    /// measured in Euclidean distance.
+    /// </summary>
+    public double DragThreshold { get; set; }
+
+    public MouseState CurrentState { get; private set; }
+
+    private MouseButtonSequence? [] _ongoingSequences = new MouseButtonSequence? [4];
+
+    public Action<MouseButtonSequence> Click { get; set; }
+
+    public MouseInterpreter (
+        Func<DateTime>? now = null,
+        IViewFinder viewFinder = null,
+        TimeSpan? doubleClickThreshold = null,
+        TimeSpan? tripleClickThreshold = null,
+        int dragThreshold = 5
+    )
+    {
+        _viewFinder = viewFinder ?? new StaticViewFinder ();
+        Now = now ?? (() => DateTime.Now);
+        DoubleClickThreshold = doubleClickThreshold ?? TimeSpan.FromMilliseconds (500);
+        TripleClickThreshold = tripleClickThreshold ?? TimeSpan.FromMilliseconds (1000);
+        DragThreshold = dragThreshold;
+    }
+
+    public IEnumerable<MouseEventArgs> Process (MouseEventArgs e)
+    {
+        // For each mouse button
+        for (int i = 0; i < 4; i++)
+        {
+            bool isPressed = IsPressed (i, e.Flags);
+
+            // Update narratives
+            if (isPressed)
+            {
+                if (_ongoingSequences [i] == null)
+                {
+                    _ongoingSequences [i] = BeginPressedNarrative (i, e);
+                }
+                else
+                {
+                    var resolve = _ongoingSequences [i]?.Process (e.Position, true);
+
+                    if (resolve != null)
+                    {
+                        _ongoingSequences [i] = null;
+                        yield return resolve;
+                    }
+                }
+            }
+            else
+            {
+                var resolve = _ongoingSequences [i]?.Process (e.Position, false);
+
+                if (resolve != null)
+                {
+
+                    _ongoingSequences [i] = null;
+                    yield return resolve;
+                }
+            }
+        }
+    }
+
+    public IEnumerable<MouseEventArgs> Release ()
+    {
+        for (var i = 0; i < _ongoingSequences.Length; i++)
+        {
+            MouseButtonSequence? narrative = _ongoingSequences [i];
+
+            if (narrative != null)
+            {
+                if (narrative.IsResolveable ())
+                {
+                    var args = narrative.Resolve ();
+
+                    if (args != null)
+                    {
+                        yield return args;
+                    }
+                    _ongoingSequences [i] = null;
+                }
+            }
+        }
+    }
+
+    private bool IsPressed (int btn, MouseFlags eFlags)
+    {
+        return btn switch
+               {
+                   0=>eFlags.HasFlag (MouseFlags.Button1Pressed),
+                   1 => eFlags.HasFlag (MouseFlags.Button2Pressed),
+                   2 => eFlags.HasFlag (MouseFlags.Button3Pressed),
+                   3 => eFlags.HasFlag (MouseFlags.Button4Pressed),
+                   _ => throw new ArgumentOutOfRangeException(nameof(btn))
+               };
+    }
+
+    private MouseButtonSequence BeginPressedNarrative (int buttonIdx, MouseEventArgs e)
+    {
+        var view = _viewFinder.GetViewAt (e.Position, out var viewport);
+
+        return new MouseButtonSequence(buttonIdx, Now,_viewFinder)
+        {
+            NumberOfClicks = 0,
+            MouseStates =
+            [
+                new MouseButtonStateEx()
+                {
+                    Button = buttonIdx,
+                    At = Now(),
+                    Pressed = true,
+                    Position = e.ScreenPosition,
+                    View = view,
+                    ViewportPosition = viewport,
+
+                    /* TODO: Do these too*/
+                    Shift = false,
+                    Ctrl = false,
+                    Alt = false
+                }
+            ]
+        };
+    }
+
+    public Point ViewportPosition { get; set; }
+
+    /* TODO: Probably need this at some point
+    public static double DistanceTo (Point p1, Point p2)
+    {
+        int deltaX = p2.X - p1.X;
+        int deltaY = p2.Y - p1.Y;
+        return Math.Sqrt (deltaX * deltaX + deltaY * deltaY);
+    }*/
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseState.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseState.cs
index d465480498..63a6d51cc0 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MouseState.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseState.cs
@@ -1,274 +1,10 @@
 #nullable enable
 
-using Terminal.Gui.ConsoleDrivers.V2;
-
 namespace Terminal.Gui;
 
-public class MouseInterpreter
-{
-    private readonly IViewFinder _viewFinder;
-
-    /// <summary>
-    /// Function for returning the current time. Use in unit tests to
-    /// ensure repeatable tests.
-    /// </summary>
-    private Func<DateTime> Now { get; set; }
-
-    /// <summary>
-    /// How long to wait for a second click after the first before giving up and
-    /// releasing event as a 'click'
-    /// </summary>
-    public TimeSpan DoubleClickThreshold { get; set; }
-
-    /// <summary>
-    /// How long to wait for a third click after the second before giving up and
-    /// releasing event as a 'double click'
-    /// </summary>
-    public TimeSpan TripleClickThreshold { get; set; }
-
-    /// <summary>
-    /// How far between a mouse down and mouse up before it is considered a 'drag' rather
-    /// than a 'click'. Console row counts for 2 units while column counts for only 1. Distance is
-    /// measured in Euclidean distance.
-    /// </summary>
-    public double DragThreshold { get; set; }
-
-    public MouseState CurrentState { get; private set; }
-
-    private ButtonNarrative? [] _ongoingNarratives = new ButtonNarrative? [4];
-
-    public Action<ButtonNarrative> Click { get; set; }
-
-    public MouseInterpreter (
-        Func<DateTime>? now = null,
-        IViewFinder viewFinder = null,
-        TimeSpan? doubleClickThreshold = null,
-        TimeSpan? tripleClickThreshold = null,
-        int dragThreshold = 5
-    )
-    {
-        _viewFinder = viewFinder ?? new StaticViewFinder ();
-        Now = now ?? (() => DateTime.Now);
-        DoubleClickThreshold = doubleClickThreshold ?? TimeSpan.FromMilliseconds (500);
-        TripleClickThreshold = tripleClickThreshold ?? TimeSpan.FromMilliseconds (1000);
-        DragThreshold = dragThreshold;
-    }
-
-    public IEnumerable<ButtonNarrative> Process (MouseEventArgs e)
-    {
-        // For each mouse button
-        for (int i = 0; i < 4; i++)
-        {
-            bool isPressed = IsPressed (i, e.Flags);
-
-            // Update narratives
-            if (isPressed)
-            {
-                if (_ongoingNarratives [i] == null)
-                {
-                    _ongoingNarratives [i] = BeginPressedNarrative (i, e);
-                }
-                else
-                {
-                    _ongoingNarratives [i]?.Process (i, e.Position, true);
-                }
-            }
-            else
-            {
-                _ongoingNarratives [i]?.Process (i, e.Position, false);
-            }
-        }
-
-       for (var i = 0; i < _ongoingNarratives.Length; i++)
-       {
-           ButtonNarrative? narrative = _ongoingNarratives [i];
-
-           if (narrative != null)
-           {
-               if (ShouldRelease (narrative))
-               {
-                   yield return narrative;
-                   _ongoingNarratives [i] = null;
-               }
-           }
-       }
-    }
-
-    private bool IsPressed (int btn, MouseFlags eFlags)
-    {
-        return btn switch
-               {
-                   0=>eFlags.HasFlag (MouseFlags.Button1Pressed),
-                   1 => eFlags.HasFlag (MouseFlags.Button2Pressed),
-                   2 => eFlags.HasFlag (MouseFlags.Button3Pressed),
-                   3 => eFlags.HasFlag (MouseFlags.Button4Pressed),
-                   _ => throw new ArgumentOutOfRangeException(nameof(btn))
-               };
-    }
-
-    private bool ShouldRelease (ButtonNarrative narrative)
-    {
-        // TODO: needs to be way smarter
-        if (narrative.NumberOfClicks > 0)
-        {
-            return true;
-        }
-
-        return false;
-    }
-
-    private ButtonNarrative BeginPressedNarrative (int buttonIdx, MouseEventArgs e)
-    {
-        var view = _viewFinder.GetViewAt (e.Position, out var viewport);
-
-        return new ButtonNarrative(Now,_viewFinder)
-        {
-            NumberOfClicks = 0,
-            MouseStates =
-            [
-                new ButtonState()
-                {
-                    Button = buttonIdx,
-                    At = Now(),
-                    Pressed = true,
-                    Position = e.ScreenPosition,
-                    View = view,
-                    ViewportPosition = viewport,
-
-                    /* TODO: Do these too*/
-                    Shift = false,
-                    Ctrl = false,
-                    Alt = false
-                }
-            ]
-        };
-    }
-
-    public Point ViewportPosition { get; set; }
-
-    /* TODO: Probably need this at some point
-    public static double DistanceTo (Point p1, Point p2)
-    {
-        int deltaX = p2.X - p1.X;
-        int deltaY = p2.Y - p1.Y;
-        return Math.Sqrt (deltaX * deltaX + deltaY * deltaY);
-    }*/
-}
-
-/// <summary>
-/// Describes a completed narrative e.g. 'user triple clicked'.
-/// </summary>
-/// <remarks>Internally we can have a double click narrative that becomes
-/// a triple click narrative. But we will not release both i.e. we don't say
-/// user clicked then user double-clicked then user triple clicked</remarks>
-public class ButtonNarrative
-{
-    private readonly IViewFinder _viewFinder;
-    public int NumberOfClicks { get; set; }
-
-    /// <summary>
-    /// Mouse states during which click was generated.
-    /// N = 2x<see cref="NumberOfClicks"/>
-    /// </summary>
-    public List<ButtonState> MouseStates { get; set; } = new ();
-
-    /// <summary>
-    /// Function for returning the current time. Use in unit tests to
-    /// ensure repeatable tests.
-    /// </summary>
-    public Func<DateTime> Now { get; set; }
-
-    public ButtonNarrative (Func<DateTime> now, IViewFinder viewFinder)
-    {
-        Now = now;
-        _viewFinder = viewFinder;
-    }
-
-    public void Process (int buttonIdx, Point position, bool pressed)
-    {
-        var last = MouseStates.Last ();
-
-        // Still pressed
-        if (last.Pressed && pressed)
-        {
-            // No change
-            return;
-        }
-
-        var view = _viewFinder.GetViewAt (position, out var viewport);
-
-        NumberOfClicks++;
-        MouseStates.Add (new ButtonState
-        {
-            Button = buttonIdx,
-            At = Now(),
-            Pressed = false,
-            Position = position,
-
-            View = view,
-            ViewportPosition = viewport,
-
-            /* TODO: Need these probably*/
-            Shift = false,
-            Ctrl = false,
-            Alt = false,
-
-        });
-    }
-}
-
-public class MouseState
+internal class MouseState
 {
-    public ButtonState[] ButtonStates = new ButtonState? [4];
+    public MouseButtonStateEx [] ButtonStates = new MouseButtonStateEx? [4];
 
     public Point Position;
-}
-
-public class ButtonState
-{
-    public required int Button { get; set; }
-    /// <summary>
-    ///     When the button entered its current state.
-    /// </summary>
-    public DateTime At { get; set; }
-
-    /// <summary>
-    /// <see langword="true"/> if the button is currently down
-    /// </summary>
-    public bool Pressed { get; set; }
-
-    /// <summary>
-    /// The screen location when the mouse button entered its current state
-    /// (became pressed or was released)
-    /// </summary>
-    public Point Position { get; set; }
-
-    /// <summary>
-    /// The <see cref="View"/> (if any) that was at the <see cref="Position"/>
-    /// when the button entered its current state.
-    /// </summary>
-    public View? View { get; set; }
-
-    /// <summary>
-    /// Viewport relative position within <see cref="View"/> (if there is one)
-    /// </summary>
-    public Point ViewportPosition { get; set; }
-
-    /// <summary>
-    /// True if shift was provided by the console at the time the mouse
-    ///  button entered its current state.
-    /// </summary>
-    public bool Shift { get; set; }
-
-    /// <summary>
-    /// True if control was provided by the console at the time the mouse
-    /// button entered its current state.
-    /// </summary>
-    public bool Ctrl { get; set; }
-
-    /// <summary>
-    /// True if alt was held down at the time the mouse
-    /// button entered its current state.
-    /// </summary>
-    public bool Alt { get; set; }
 }
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index 3530b365e5..3d049cfdc8 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -16,7 +16,7 @@
     <Position X="19.458" Y="3.562" Height="0.396" Width="2.825" />
   </Comment>
   <Comment CommentText="Mouse interpretation subsystem">
-    <Position X="13.625" Y="9.833" Height="0.396" Width="2.825" />
+    <Position X="13.271" Y="9.896" Height="0.396" Width="2.075" />
   </Comment>
   <Comment CommentText="In Terminal.Gui views get things done almost exclusively by calling static methods on Application e.g. RequestStop, Run, Refresh etc">
     <Position X="0.5" Y="3.75" Height="1.146" Width="1.7" />
@@ -33,6 +33,7 @@
       <HashCode>QIAACAAAACAEAAAAAAAAAAAkAAAAAAAAAwAAAAAAABA=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsInput.cs</FileName>
     </TypeIdentifier>
+    <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.NetInput" Collapsed="true">
     <Position X="13.25" Y="3" Width="2" />
@@ -40,11 +41,12 @@
       <HashCode>AAAAAAAAACAEAAAAQAAAAAAgAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\NetInput.cs</FileName>
     </TypeIdentifier>
+    <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.ConsoleInput&lt;T&gt;" Collapsed="true">
     <Position X="12.5" Y="2" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAACAEAQAAAAAAAAAgAAAAAAAAAAAAAAAAAAo=</HashCode>
+      <HashCode>AAAAAAAAACAEAQAAAAAAAAAgACAAAAAAAAAAAAAAAAo=</HashCode>
       <FileName>ConsoleDrivers\V2\ConsoleInput.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
@@ -62,7 +64,7 @@
         <Point X="11.852" Y="7.146" />
         <Point X="12.75" Y="7.146" />
         <Point X="12.75" Y="7.596" />
-        <Point X="12.76" Y="7.596" Type="JumpStart" />
+        <Point X="12.761" Y="7.596" Type="JumpStart" />
         <Point X="12.927" Y="7.596" Type="JumpEnd" />
         <Point X="14" Y="7.596" />
       </Path>
@@ -83,15 +85,15 @@
         <Point X="12.125" Y="5.312" />
         <Point X="12.125" Y="7" />
         <Point X="12.844" Y="7" />
-        <Point X="12.844" Y="14.281" />
-        <Point X="13.75" Y="14.281" />
+        <Point X="12.844" Y="14.531" />
+        <Point X="13.25" Y="14.531" />
       </Path>
       <MemberNameLabel ManuallyPlaced="true">
         <Position X="0.047" Y="-0.336" />
       </MemberNameLabel>
     </AssociationLine>
     <TypeIdentifier>
-      <HashCode>QQQAAAAAACABIQQAAAAAAAAAAAAAAAACAAAAAACAEAI=</HashCode>
+      <HashCode>QQQAAAAQACABIQQAAAAAAAAAACAAAAACAAAAAACAEAA=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoop.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -107,7 +109,7 @@
   <Class Name="Terminal.Gui.MainLoopCoordinator&lt;T&gt;">
     <Position X="6.5" Y="2" Width="2" />
     <TypeIdentifier>
-      <HashCode>AAAAJAEAAAJABAAAABQAAAAQABAQAAQAIQAABAAACgw=</HashCode>
+      <HashCode>AAAAIAEACAIABAAAABQAAAAQABAQAAQAIQIABAAASgw=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoopCoordinator.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -151,37 +153,35 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.InputProcessor&lt;T&gt;" Collapsed="true">
-    <Position X="16.5" Y="4.75" Width="2" />
-    <AssociationLine Name="Parser" Type="Terminal.Gui.AnsiResponseParser&lt;T&gt;" FixedFromPoint="true" FixedToPoint="true">
+    <Position X="16.75" Y="4.75" Width="2" />
+    <AssociationLine Name="_mouseInterpreter" Type="Terminal.Gui.MouseInterpreter" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
       <Path>
-        <Point X="18.5" Y="5.031" />
-        <Point X="19.125" Y="5.031" />
-        <Point X="19.125" Y="5.5" />
-        <Point X="20" Y="5.5" />
-        <Point X="20" Y="9.75" />
-        <Point X="19.5" Y="9.75" />
-        <Point X="19.5" Y="10.25" />
-        <Point X="19.75" Y="10.25" />
+        <Point X="18" Y="5.312" />
+        <Point X="18" Y="10.031" />
+        <Point X="15.99" Y="10.031" />
+        <Point X="15.99" Y="10.625" />
+        <Point X="15" Y="10.625" />
       </Path>
     </AssociationLine>
     <TypeIdentifier>
-      <HashCode>AQCkAAAAAASAiAAAAgggAAAABAoAAAAAAAgAAAAAAAA=</HashCode>
+      <HashCode>AQCkEAAAAASAiAAAAgggAAAABAIAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\InputProcessor.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
       <Property Name="Parser" />
+      <Property Name="_mouseInterpreter" />
     </ShowAsAssociation>
     <Lollipop Position="0.1" />
   </Class>
   <Class Name="Terminal.Gui.NetInputProcessor" Collapsed="true">
-    <Position X="17.75" Y="5.75" Width="2" />
+    <Position X="18" Y="5.75" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAACAAAAgAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\NetInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.WindowsInputProcessor" Collapsed="true">
-    <Position X="15.75" Y="5.75" Width="2" />
+    <Position X="16" Y="5.75" Width="2" />
     <TypeIdentifier>
       <HashCode>AQAAAAAAAAAACAAAAgAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsInputProcessor.cs</FileName>
@@ -195,7 +195,7 @@
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.ConsoleDrivers.V2.ConsoleDriverFacade&lt;T&gt;">
-    <Position X="6.5" Y="7.5" Width="2" />
+    <Position X="6.5" Y="7.75" Width="2" />
     <Compartments>
       <Compartment Name="Methods" Collapsed="true" />
       <Compartment Name="Fields" Collapsed="true" />
@@ -229,23 +229,23 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.MouseInterpreter">
-    <Position X="13.75" Y="10.5" Width="1.5" />
+    <Position X="13.25" Y="10.5" Width="1.75" />
     <TypeIdentifier>
-      <HashCode>AAQAAAAAAAABAAIAAkAAACAAICgIAAAAAABAAAAAAgA=</HashCode>
-      <FileName>ConsoleDrivers\V2\MouseState.cs</FileName>
+      <HashCode>AAAAAAAAAAABAAIAAkAAACAAICgIAAAAAAAAABAAAhg=</HashCode>
+      <FileName>ConsoleDrivers\V2\MouseInterpreter.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
       <Field Name="_viewFinder" />
     </ShowAsAssociation>
     <ShowAsCollectionAssociation>
-      <Field Name="_ongoingNarratives" />
+      <Field Name="_ongoingSequences" />
     </ShowAsCollectionAssociation>
   </Class>
-  <Class Name="Terminal.Gui.ButtonNarrative">
+  <Class Name="Terminal.Gui.MouseButtonSequence">
     <Position X="16.75" Y="12" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>IAAAAAAAAAAAAAAAAgAAAAAAICAAAAQAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\MouseState.cs</FileName>
+      <HashCode>IAAABAAAAACAAAAAAgAAAAAAICAAAEQAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\MouseButtonSequence.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
       <Field Name="_viewFinder" />
@@ -254,11 +254,11 @@
       <Property Name="MouseStates" />
     </ShowAsCollectionAssociation>
   </Class>
-  <Class Name="Terminal.Gui.ButtonState">
-    <Position X="19.5" Y="12" Width="1.5" />
+  <Class Name="Terminal.Gui.MouseButtonStateEx">
+    <Position X="19.5" Y="12" Width="2" />
     <TypeIdentifier>
       <HashCode>AEAAAAAAAAAAAAggAAAAAAAAAACJAAAAAoAAAAAEAAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\MouseState.cs</FileName>
+      <FileName>ConsoleDrivers\V2\MouseButtonState.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.StringHeld" Collapsed="true">
@@ -319,7 +319,7 @@
   <Class Name="Terminal.Gui.ConsoleDrivers.V2.ApplicationV2" Collapsed="true">
     <Position X="4.75" Y="4.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAIAgAQAAAAAQAAAAAAgAEAAAAKgIAAEgI=</HashCode>
+      <HashCode>QAAAAAgABAAIAgAQAAAAAQAAAAAAgAEAAAAKgIAAEgI=</HashCode>
       <FileName>ConsoleDrivers\V2\ApplicationV2.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -344,7 +344,7 @@
   <Interface Name="Terminal.Gui.IMainLoop&lt;T&gt;" Collapsed="true">
     <Position X="9.25" Y="4.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAQAAAAAAAABIQQAAAAAAAAAAAAAAAACAAAAAAAAEAI=</HashCode>
+      <HashCode>AAQAAAAAAAABIQQAAAAAAAAAAAAAAAACAAAAAAAAEAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IMainLoop.cs</FileName>
     </TypeIdentifier>
   </Interface>
@@ -364,26 +364,10 @@
   </Interface>
   <Interface Name="Terminal.Gui.IInputProcessor">
     <Position X="14" Y="4.5" Width="1.5" />
-    <AssociationLine Name="MouseInterpreter" Type="Terminal.Gui.MouseInterpreter" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
-      <Path>
-        <Point X="15.5" Y="6.875" />
-        <Point X="16.5" Y="6.875" />
-        <Point X="16.5" Y="10.125" />
-        <Point X="13.375" Y="10.125" />
-        <Point X="13.375" Y="12.143" />
-        <Point X="13.75" Y="12.143" />
-      </Path>
-      <MemberNameLabel ManuallyPlaced="true">
-        <Position X="0.123" Y="0.152" />
-      </MemberNameLabel>
-    </AssociationLine>
     <TypeIdentifier>
-      <HashCode>AAAkAAAAAACAgAAAAAggAAAABAIAAAAAAAgAAAAAAAA=</HashCode>
+      <HashCode>AAAkAAAAAACAgAAAAAggAAAABAIAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IInputProcessor.cs</FileName>
     </TypeIdentifier>
-    <ShowAsAssociation>
-      <Property Name="MouseInterpreter" />
-    </ShowAsAssociation>
   </Interface>
   <Interface Name="Terminal.Gui.ConsoleDrivers.V2.IViewFinder">
     <Position X="16.75" Y="10.25" Width="1.5" />
@@ -419,7 +403,7 @@
   <Interface Name="Terminal.Gui.IMainLoopCoordinator" Collapsed="true">
     <Position X="6.5" Y="0.5" Width="2" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAIQAAAAAAAAA=</HashCode>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQIAAAAAQAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IMainLoopCoordinator.cs</FileName>
     </TypeIdentifier>
   </Interface>
@@ -431,7 +415,7 @@
     </TypeIdentifier>
   </Interface>
   <Interface Name="Terminal.Gui.IWindowSizeMonitor" Collapsed="true">
-    <Position X="13.75" Y="14" Width="1.75" />
+    <Position X="13.25" Y="14.25" Width="1.75" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAEAAAAAAAAAAAACAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IMainLoop.cs</FileName>
diff --git a/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs b/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
new file mode 100644
index 0000000000..d944b091b5
--- /dev/null
+++ b/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Moq;
+using Terminal.Gui.ConsoleDrivers.V2;
+
+namespace UnitTests.ConsoleDrivers.V2;
+public class MouseInterpreterTests
+{
+    [Fact]
+    public void Mouse1Click ()
+    {
+        var v = Mock.Of<IViewFinder> ();
+        var interpreter = new MouseInterpreter (null, v);
+
+        Assert.Empty (interpreter.Process (
+                             new MouseEventArgs ()
+                             {
+                                 Flags = MouseFlags.Button1Pressed
+                             }));
+
+        var result = interpreter.Process (
+                             new MouseEventArgs ()
+                             {
+                                 Flags = MouseFlags.Button1Released
+                             }).ToArray ();
+        var e = Assert.Single (result);
+
+        // TODO: Ultimately will not be the case as we will be dealing with double click and triple
+        Assert.Equal (MouseFlags.Button1Clicked,e.Flags);
+    }
+}

From 151f528c8ba7dc2d0ac6995240a811f1a153b625 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 26 Dec 2024 13:05:37 +0000
Subject: [PATCH 087/198] Add logging to Terminal.Gui

---
 .../Application/Application.Initialization.cs |  1 -
 .../ConsoleDrivers/V2/ApplicationV2.cs        |  2 +-
 .../ConsoleDrivers/V2/ConsoleDriverFacade.cs  |  2 +-
 .../ConsoleDrivers/V2/IMainLoopCoordinator.cs |  1 -
 Terminal.Gui/ConsoleDrivers/V2/IViewFinder.cs |  2 +-
 Terminal.Gui/ConsoleDrivers/V2/Logging.cs     | 18 +++++++++++++
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    |  3 +++
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  | 16 ++++++------
 .../ConsoleDrivers/V2/MouseButtonSequence.cs  |  1 -
 .../ConsoleDrivers/V2/MouseInterpreter.cs     |  1 -
 Terminal.Gui/ConsoleDrivers/V2/V2.cd          |  4 +--
 Terminal.Gui/Terminal.Gui.csproj              |  1 +
 UICatalog/UICatalog.cs                        | 25 ++++++++++++++++++-
 UICatalog/UICatalog.csproj                    |  3 +++
 .../ConsoleDrivers/V2/ApplicationV2Tests.cs   |  1 -
 .../V2/MouseInterpreterTests.cs               |  8 +-----
 16 files changed, 64 insertions(+), 25 deletions(-)
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/Logging.cs

diff --git a/Terminal.Gui/Application/Application.Initialization.cs b/Terminal.Gui/Application/Application.Initialization.cs
index dbf2144225..e142790747 100644
--- a/Terminal.Gui/Application/Application.Initialization.cs
+++ b/Terminal.Gui/Application/Application.Initialization.cs
@@ -2,7 +2,6 @@
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Reflection;
-using Terminal.Gui.ConsoleDrivers.V2;
 
 namespace Terminal.Gui;
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index 4adc311cfb..cea51aa776 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -1,6 +1,6 @@
 using System.Collections.Concurrent;
 
-namespace Terminal.Gui.ConsoleDrivers.V2;
+namespace Terminal.Gui;
 
 
 public class ApplicationV2 : ApplicationImpl
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
index 6610f7e227..b776652243 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
@@ -1,7 +1,7 @@
 using System.Drawing;
 using System.Runtime.InteropServices;
 
-namespace Terminal.Gui.ConsoleDrivers.V2;
+namespace Terminal.Gui;
 class ConsoleDriverFacade<T> : IConsoleDriver
 {
     private readonly IInputProcessor _inputProcessor;
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
index b511bee278..9cbca9e3e8 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
@@ -6,6 +6,5 @@ public interface IMainLoopCoordinator
 
     public void Stop ();
 
-    public Exception InputCrashedException { get; }
     void RunIteration ();
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IViewFinder.cs b/Terminal.Gui/ConsoleDrivers/V2/IViewFinder.cs
index 23d32ac9c3..9d737cbedb 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IViewFinder.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IViewFinder.cs
@@ -4,7 +4,7 @@
 using System.Text;
 using System.Threading.Tasks;
 
-namespace Terminal.Gui.ConsoleDrivers.V2;
+namespace Terminal.Gui;
 
 public interface IViewFinder
 {
diff --git a/Terminal.Gui/ConsoleDrivers/V2/Logging.cs b/Terminal.Gui/ConsoleDrivers/V2/Logging.cs
new file mode 100644
index 0000000000..25a7357691
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/Logging.cs
@@ -0,0 +1,18 @@
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+
+namespace Terminal.Gui;
+
+/// <summary>
+/// Singleton logging instance class. Do not use console loggers
+/// with this class as it will interfere with Terminal.Gui
+/// screen output (i.e. use a file logger)
+/// </summary>
+public static class Logging
+{
+    /// <summary>
+    /// Logger, defaults to NullLogger (i.e. no logging).  Set this to a
+    /// file logger to enable logging of Terminal.Gui internals.
+    /// </summary>
+    public static ILogger Logger { get; set; } = NullLogger.Instance;
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 0a852eb57a..79633130a6 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -1,5 +1,6 @@
 using System.Collections.Concurrent;
 using System.Drawing;
+using Microsoft.Extensions.Logging;
 using static Unix.Terminal.Curses;
 
 namespace Terminal.Gui;
@@ -49,6 +50,8 @@ public void Iteration ()
             var took = Now() - dt;
             var sleepFor = TimeSpan.FromMilliseconds (50) - took;
 
+            Logging.Logger.LogTrace ($"MainLoop iteration took {took.Milliseconds}ms, sleeping for {Math.Max(0,sleepFor.Milliseconds)}ms");
+
             if (sleepFor.Milliseconds > 0)
             {
                 Task.Delay (sleepFor).Wait ();
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index 61719c5548..941260263b 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -1,5 +1,5 @@
 using System.Collections.Concurrent;
-using Terminal.Gui.ConsoleDrivers.V2;
+using Microsoft.Extensions.Logging;
 
 namespace Terminal.Gui;
 
@@ -18,9 +18,7 @@ public class MainLoopCoordinator<T> : IMainLoopCoordinator
     private Task _inputTask;
     private ITimedEvents _timedEvents;
 
-    public Exception InputCrashedException { get; private set; }
-
-    public SemaphoreSlim StartupSemaphore { get; } = new (0, 1);
+    private SemaphoreSlim _startupSemaphore = new (0, 1);
 
 
     /// <summary>
@@ -51,6 +49,8 @@ public MainLoopCoordinator (ITimedEvents timedEvents, Func<IConsoleInput<T>> inp
     /// </summary>
     public async Task StartAsync ()
     {
+        Logging.Logger.LogInformation ("Main Loop Coordinator booting...");
+
         // TODO: if crash on boot then semaphore never finishes
         _inputTask = Task.Run (RunInput);
 
@@ -58,7 +58,9 @@ public async Task StartAsync ()
         BootMainLoop ();
 
         // Use asynchronous semaphore waiting.
-        await StartupSemaphore.WaitAsync ().ConfigureAwait (false);
+        await _startupSemaphore.WaitAsync ().ConfigureAwait (false);
+
+        Logging.Logger.LogInformation ("Main Loop Coordinator booting complete");
     }
 
     private void RunInput ()
@@ -85,7 +87,7 @@ private void RunInput ()
         }
         catch (Exception e)
         {
-            InputCrashedException = e;
+            Logging.Logger.LogCritical (e,"Input loop crashed");
         }
     }
 
@@ -121,7 +123,7 @@ private void BuildFacadeIfPossible ()
                                                   _loop.WindowSizeMonitor);
             Application.Driver = _facade;
 
-            StartupSemaphore.Release ();
+            _startupSemaphore.Release ();
         }
     }
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseButtonSequence.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseButtonSequence.cs
index f1aee77798..3476b87e3a 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MouseButtonSequence.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseButtonSequence.cs
@@ -1,5 +1,4 @@
 #nullable enable
-using Terminal.Gui.ConsoleDrivers.V2;
 
 namespace Terminal.Gui;
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
index a7d0f9f17e..afb74fe88c 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
@@ -1,5 +1,4 @@
 #nullable enable
-using Terminal.Gui.ConsoleDrivers.V2;
 
 namespace Terminal.Gui;
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index 3d049cfdc8..cb3a1cd70c 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -242,7 +242,7 @@
     </ShowAsCollectionAssociation>
   </Class>
   <Class Name="Terminal.Gui.MouseButtonSequence">
-    <Position X="16.75" Y="12" Width="1.5" />
+    <Position X="16.75" Y="12" Width="2" />
     <TypeIdentifier>
       <HashCode>IAAABAAAAACAAAAAAgAAAAAAICAAAEQAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\MouseButtonSequence.cs</FileName>
@@ -255,7 +255,7 @@
     </ShowAsCollectionAssociation>
   </Class>
   <Class Name="Terminal.Gui.MouseButtonStateEx">
-    <Position X="19.5" Y="12" Width="2" />
+    <Position X="20" Y="12" Width="2" />
     <TypeIdentifier>
       <HashCode>AEAAAAAAAAAAAAggAAAAAAAAAACJAAAAAoAAAAAEAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\MouseButtonState.cs</FileName>
diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj
index 0d8756154c..6e9153a3dd 100644
--- a/Terminal.Gui/Terminal.Gui.csproj
+++ b/Terminal.Gui/Terminal.Gui.csproj
@@ -57,6 +57,7 @@
     <PackageReference Include="Microsoft.CodeAnalysis" Version="[4.10,5)" PrivateAssets="all" />
     <PackageReference Include="Microsoft.CodeAnalysis.Common" Version="[4.10,5)" PrivateAssets="all" />
     <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="[4.10,5)" PrivateAssets="all" />
+    <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
     <!-- Enable Nuget Source Link for github -->
     <PackageReference Include="Microsoft.SourceLink.GitHub" Version="[8,9)" PrivateAssets="all" />
     <PackageReference Include="System.IO.Abstractions" Version="[21.0.22,22)" />
diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs
index 6280726770..5fc4e0b200 100644
--- a/UICatalog/UICatalog.cs
+++ b/UICatalog/UICatalog.cs
@@ -20,11 +20,13 @@
 using System.Text.Json.Serialization;
 using System.Threading;
 using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using Serilog;
 using Terminal.Gui;
-using Terminal.Gui.ConsoleDrivers.V2;
 using UICatalog.Scenarios;
 using static Terminal.Gui.ConfigurationManager;
 using Command = Terminal.Gui.Command;
+using ILogger = Microsoft.Extensions.Logging.ILogger;
 using RuntimeEnvironment = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment;
 
 #nullable enable
@@ -124,6 +126,8 @@ private static void ConfigFileChanged (object sender, FileSystemEventArgs e)
 
     private static int Main (string [] args)
     {
+        Logging.Logger = CreateLogger ();
+
         Console.OutputEncoding = Encoding.Default;
 
         if (Debugger.IsAttached)
@@ -210,6 +214,25 @@ private static int Main (string [] args)
         return 0;
     }
 
+    private static ILogger CreateLogger ()
+    {
+        // Configure Serilog to write logs to a file
+        Log.Logger = new LoggerConfiguration ()
+                     .WriteTo.File ("logs/logfile.txt", rollingInterval: RollingInterval.Day)
+                     .CreateLogger ();
+
+        // Create a logger factory compatible with Microsoft.Extensions.Logging
+        using var loggerFactory = LoggerFactory.Create (builder =>
+                                                        {
+                                                            builder
+                                                                .AddSerilog (dispose: true) // Integrate Serilog with ILogger
+                                                                .SetMinimumLevel (LogLevel.Trace); // Set minimum log level
+                                                        });
+
+        // Get an ILogger instance
+        return loggerFactory.CreateLogger ("Global Logger");
+    }
+
     private static void OpenUrl (string url)
     {
         if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
diff --git a/UICatalog/UICatalog.csproj b/UICatalog/UICatalog.csproj
index fb12478862..c854ef8e68 100644
--- a/UICatalog/UICatalog.csproj
+++ b/UICatalog/UICatalog.csproj
@@ -31,6 +31,9 @@
   <ItemGroup>
     <PackageReference Include="JetBrains.Annotations" Version="[2024.2.0,)" PrivateAssets="all" />
     <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="[1.21,2)" />
+    <PackageReference Include="Serilog" Version="4.2.0" />
+    <PackageReference Include="Serilog.Extensions.Logging" Version="9.0.0" />
+    <PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
     <PackageReference Include="SixLabors.ImageSharp" Version="[3.1.5,4)" />
     <PackageReference Include="CsvHelper" Version="[33.0.1,34)" />
     <PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="[3.1.6,4)" />
diff --git a/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs b/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
index e05f9d6258..eeca507040 100644
--- a/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
+++ b/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
@@ -1,5 +1,4 @@
 using Moq;
-using Terminal.Gui.ConsoleDrivers.V2;
 
 namespace UnitTests.ConsoleDrivers.V2;
 public class ApplicationV2Tests
diff --git a/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs b/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
index d944b091b5..5fc89d79e7 100644
--- a/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
+++ b/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
@@ -1,10 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Moq;
-using Terminal.Gui.ConsoleDrivers.V2;
+using Moq;
 
 namespace UnitTests.ConsoleDrivers.V2;
 public class MouseInterpreterTests

From e4d07d55db4dc9c8b67afc179d786ff5a0a9020a Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 26 Dec 2024 15:47:29 +0000
Subject: [PATCH 088/198] Change to DiagnosticSource for metrics

---
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 79633130a6..b42a6f9732 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -1,7 +1,5 @@
 using System.Collections.Concurrent;
-using System.Drawing;
-using Microsoft.Extensions.Logging;
-using static Unix.Terminal.Curses;
+using System.Diagnostics;
 
 namespace Terminal.Gui;
 
@@ -28,6 +26,9 @@ public class MainLoop<T> : IMainLoop<T>
     /// </summary>
     public Func<DateTime> Now { get; set; } = () => DateTime.Now;
 
+    private static readonly DiagnosticSource _diagnosticSource = new DiagnosticListener ("MainLoop");
+
+
     public void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput)
     {
         InputBuffer = inputBuffer;
@@ -50,7 +51,15 @@ public void Iteration ()
             var took = Now() - dt;
             var sleepFor = TimeSpan.FromMilliseconds (50) - took;
 
-            Logging.Logger.LogTrace ($"MainLoop iteration took {took.Milliseconds}ms, sleeping for {Math.Max(0,sleepFor.Milliseconds)}ms");
+            if (_diagnosticSource.IsEnabled ("MainLoop.Iteration"))
+            {
+                _diagnosticSource.Write (
+                                         "MainLoop.Iteration",
+                                         new
+                                         {
+                                             Duration = took.Milliseconds
+                                         });
+            }
 
             if (sleepFor.Milliseconds > 0)
             {

From 7327a8694744d6a9871cc241f0598950436fdf1c Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 26 Dec 2024 18:11:40 +0000
Subject: [PATCH 089/198] Switch to Meter and Histogram for metrics

---
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs | 15 +++++----------
 1 file changed, 5 insertions(+), 10 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index b42a6f9732..7ac04a0fa2 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -1,5 +1,6 @@
 using System.Collections.Concurrent;
 using System.Diagnostics;
+using System.Diagnostics.Metrics;
 
 namespace Terminal.Gui;
 
@@ -26,9 +27,11 @@ public class MainLoop<T> : IMainLoop<T>
     /// </summary>
     public Func<DateTime> Now { get; set; } = () => DateTime.Now;
 
-    private static readonly DiagnosticSource _diagnosticSource = new DiagnosticListener ("MainLoop");
+    static Meter s_meter = new Meter ("Terminal.Gui");
 
 
+    static Histogram<int> s_iterations = s_meter.CreateHistogram<int> ("Terminal.Gui.iteration");
+
     public void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput)
     {
         InputBuffer = inputBuffer;
@@ -51,15 +54,7 @@ public void Iteration ()
             var took = Now() - dt;
             var sleepFor = TimeSpan.FromMilliseconds (50) - took;
 
-            if (_diagnosticSource.IsEnabled ("MainLoop.Iteration"))
-            {
-                _diagnosticSource.Write (
-                                         "MainLoop.Iteration",
-                                         new
-                                         {
-                                             Duration = took.Milliseconds
-                                         });
-            }
+            s_iterations.Record (took.Milliseconds);
 
             if (sleepFor.Milliseconds > 0)
             {

From 2101849387ba8a83c5b250f9a26567ff370323bb Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Fri, 27 Dec 2024 14:58:09 +0000
Subject: [PATCH 090/198] Fix verbose logging to work correctly and add some
 trace logging to AnsiResponseParser

---
 .../AnsiResponseParser/AnsiResponseParser.cs       | 10 ++++++++++
 Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs    |  5 +++++
 Terminal.Gui/ConsoleDrivers/V2/Logging.cs          | 14 ++++++++++++--
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs         |  7 ++-----
 UICatalog/UICatalog.cs                             |  1 +
 5 files changed, 30 insertions(+), 7 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
index 4c2ddfd70b..6e02acbfdf 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
@@ -1,5 +1,7 @@
 #nullable enable
 
+using Microsoft.Extensions.Logging;
+
 namespace Terminal.Gui;
 
 internal abstract class AnsiResponseParserBase : IAnsiResponseParser
@@ -181,6 +183,8 @@ int inputLength
 
     private void ReleaseHeld (Action<object> appendOutput, AnsiResponseParserState newState = AnsiResponseParserState.Normal)
     {
+        Logging.Logger.LogTrace ($"AnsiResponseParser releasing '{_heldContent.HeldToString ()}'");
+
         foreach (object o in _heldContent.HeldToObjects ())
         {
             appendOutput (o);
@@ -201,6 +205,8 @@ protected bool ShouldReleaseHeldContent ()
             {
                 RaiseMouseEvent(cur);
                 ResetState();
+
+                Logging.Logger.LogTrace ($"AnsiResponseParser handled as mouse '{cur}'");
                 return false;
             }
 
@@ -251,6 +257,8 @@ protected bool ShouldReleaseHeldContent ()
                 {
                     _heldContent.ClearHeld ();
 
+                    Logging.Logger.LogTrace ($"AnsiResponseParser bespoke processed '{cur}'");
+
                     // Do not send back to input stream
                     return false;
                 }
@@ -301,6 +309,8 @@ private bool MatchResponse (string cur, List<AnsiResponseExpectation> collection
 
         if (matchingResponse?.Response != null)
         {
+            Logging.Logger.LogTrace ($"AnsiResponseParser processed '{cur}'");
+
             if (invokeCallback)
             {
                 matchingResponse.Response.Invoke (_heldContent);
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index cea51aa776..e9672f564c 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -1,4 +1,6 @@
 using System.Collections.Concurrent;
+using Microsoft.Extensions.Logging;
+using System.Runtime.ConstrainedExecution;
 
 namespace Terminal.Gui;
 
@@ -119,6 +121,7 @@ public override T Run<T> (Func<Exception, bool> errorHandler = null, IConsoleDri
     /// <inheritdoc />
     public override void Run (Toplevel view, Func<Exception, bool> errorHandler = null)
     {
+        Logging.Logger.LogInformation ($"Run '{view}'");
         ArgumentNullException.ThrowIfNull (view);
 
         if (!Application.Initialized)
@@ -148,6 +151,8 @@ public override void Shutdown ()
     /// <inheritdoc />
     public override void RequestStop (Toplevel top)
     {
+        Logging.Logger.LogInformation ($"RequestStop '{top}'");
+
         // TODO: This definition of stop seems sketchy
         Application.TopLevels.TryPop (out _);
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/Logging.cs b/Terminal.Gui/ConsoleDrivers/V2/Logging.cs
index 25a7357691..a4149cd1ae 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/Logging.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/Logging.cs
@@ -1,4 +1,5 @@
-using Microsoft.Extensions.Logging;
+using System.Diagnostics.Metrics;
+using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging.Abstractions;
 
 namespace Terminal.Gui;
@@ -6,8 +7,11 @@ namespace Terminal.Gui;
 /// <summary>
 /// Singleton logging instance class. Do not use console loggers
 /// with this class as it will interfere with Terminal.Gui
-/// screen output (i.e. use a file logger)
+/// screen output (i.e. use a file logger).
 /// </summary>
+/// <remarks>Also contains the
+/// <see cref="Meter"/> instance that should be used for internal metrics
+/// (iteration timing etc).</remarks>
 public static class Logging
 {
     /// <summary>
@@ -15,4 +19,10 @@ public static class Logging
     /// file logger to enable logging of Terminal.Gui internals.
     /// </summary>
     public static ILogger Logger { get; set; } = NullLogger.Instance;
+
+    /// <summary>
+    /// Metrics reporting meter for internal Terminal.Gui processes. To use
+    /// create your own static instrument e.g. CreateCounter, CreateHistogram etc
+    /// </summary>
+    internal static readonly Meter Meter = new ("Terminal.Gui");
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 7ac04a0fa2..6c81228545 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -27,10 +27,7 @@ public class MainLoop<T> : IMainLoop<T>
     /// </summary>
     public Func<DateTime> Now { get; set; } = () => DateTime.Now;
 
-    static Meter s_meter = new Meter ("Terminal.Gui");
-
-
-    static Histogram<int> s_iterations = s_meter.CreateHistogram<int> ("Terminal.Gui.iteration");
+    static readonly Histogram<int> totalIterationMetric = Logging.Meter.CreateHistogram<int> ("Total Iteration Time");
 
     public void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput)
     {
@@ -54,7 +51,7 @@ public void Iteration ()
             var took = Now() - dt;
             var sleepFor = TimeSpan.FromMilliseconds (50) - took;
 
-            s_iterations.Record (took.Milliseconds);
+            totalIterationMetric.Record (took.Milliseconds);
 
             if (sleepFor.Milliseconds > 0)
             {
diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs
index 5fc4e0b200..7aa687d9a1 100644
--- a/UICatalog/UICatalog.cs
+++ b/UICatalog/UICatalog.cs
@@ -218,6 +218,7 @@ private static ILogger CreateLogger ()
     {
         // Configure Serilog to write logs to a file
         Log.Logger = new LoggerConfiguration ()
+                     .MinimumLevel.Verbose () // Verbose includes Trace and Debug
                      .WriteTo.File ("logs/logfile.txt", rollingInterval: RollingInterval.Day)
                      .CreateLogger ();
 

From 0a2cddf98257b1f6cfbd9fd2a2afe4282e2e71da Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Fri, 27 Dec 2024 15:23:49 +0000
Subject: [PATCH 091/198] Logging, metrics and fix performance issue in new
 ConsoleInput class

---
 .../AnsiResponseParser/AnsiResponseParser.cs  | 20 +++++++++++++++----
 .../ConsoleDrivers/V2/ConsoleInput.cs         |  8 +++++++-
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    |  2 +-
 3 files changed, 24 insertions(+), 6 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
index 6e02acbfdf..e8a9a40576 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
@@ -183,8 +183,6 @@ int inputLength
 
     private void ReleaseHeld (Action<object> appendOutput, AnsiResponseParserState newState = AnsiResponseParserState.Normal)
     {
-        Logging.Logger.LogTrace ($"AnsiResponseParser releasing '{_heldContent.HeldToString ()}'");
-
         foreach (object o in _heldContent.HeldToObjects ())
         {
             appendOutput (o);
@@ -399,12 +397,20 @@ public IEnumerable<Tuple<char, T>> ProcessInput (params Tuple<char, T> [] input)
         ProcessInputBase (
                           i => input [i].Item1,
                           i => input [i],
-                          c => output.Add ((Tuple<char, T>)c),
+                          c => AppendOutput(output,c),
                           input.Length);
 
         return output;
     }
 
+    private void AppendOutput (List<Tuple<char, T>> output, object c)
+    {
+        var tuple = (Tuple<char, T>)c;
+
+        Logging.Logger.LogTrace ($"AnsiResponseParser releasing '{tuple.Item1}'");
+        output.Add (tuple);
+    }
+
     public Tuple<char, T> [] Release ()
     {
         // Lock in case Release is called from different Thread from parse
@@ -469,12 +475,18 @@ public string ProcessInput (string input)
         ProcessInputBase (
                           i => input [i],
                           i => input [i], // For string there is no T so object is same as char
-                          c => output.Append ((char)c),
+                          c => AppendOutput(output,(char)c),
                           input.Length);
 
         return output.ToString ();
     }
 
+    private void AppendOutput (StringBuilder output, char c)
+    {
+        Logging.Logger.LogTrace ($"AnsiResponseParser releasing '{c}'");
+        output.Append (c);
+    }
+
     public string Release ()
     {
         lock (_lockState)
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
index deb996e076..b89f8630cc 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
@@ -1,5 +1,6 @@
 #nullable enable
 using System.Collections.Concurrent;
+using System.Diagnostics.Metrics;
 
 namespace Terminal.Gui;
 
@@ -13,6 +14,9 @@ public abstract class ConsoleInput<T> : IConsoleInput<T>
     /// </summary>
     public Func<DateTime> Now { get; set; } = ()=>DateTime.Now;
 
+    private Histogram<int> drainInputStream = Logging.Meter.CreateHistogram<int> ("Drain Input (ms)");
+
+
     /// <inheritdoc />
     public virtual void Dispose ()
     {
@@ -39,7 +43,7 @@ public void Run (CancellationToken token)
             {
                 var dt = Now ();
 
-                if (Peek ())
+                while (Peek ())
                 {
                     foreach (var r in Read ())
                     {
@@ -50,6 +54,8 @@ public void Run (CancellationToken token)
                 var took = Now () - dt;
                 var sleepFor = TimeSpan.FromMilliseconds (20) - took;
 
+                drainInputStream.Record (took.Milliseconds);
+
                 if (sleepFor.Milliseconds > 0)
                 {
                     Task.Delay (sleepFor, token).Wait (token);
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 6c81228545..72139fa006 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -27,7 +27,7 @@ public class MainLoop<T> : IMainLoop<T>
     /// </summary>
     public Func<DateTime> Now { get; set; } = () => DateTime.Now;
 
-    static readonly Histogram<int> totalIterationMetric = Logging.Meter.CreateHistogram<int> ("Total Iteration Time");
+    static readonly Histogram<int> totalIterationMetric = Logging.Meter.CreateHistogram<int> ("Iteration (ms)");
 
     public void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput)
     {

From 31cec86d753657b2074e601dcfb0e3715b2f3f87 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Fri, 27 Dec 2024 16:24:55 +0000
Subject: [PATCH 092/198] Log screen size changes

---
 Terminal.Gui/ConsoleDrivers/V2/WindowSizeMonitor.cs | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowSizeMonitor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowSizeMonitor.cs
index 9e75cf5234..28c522a859 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowSizeMonitor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowSizeMonitor.cs
@@ -1,4 +1,6 @@
-namespace Terminal.Gui;
+using Microsoft.Extensions.Logging;
+
+namespace Terminal.Gui;
 
 internal class WindowSizeMonitor : IWindowSizeMonitor
 {
@@ -20,10 +22,11 @@ public void Poll ()
     {
         var size = _consoleOut.GetWindowSize ();
 
-        _outputBuffer.SetWindowSize (size.Width, size.Height);
 
         if (size != _lastSize)
         {
+            Logging.Logger.LogInformation ($"Console size changes from '{_lastSize}' to {size}");
+            _outputBuffer.SetWindowSize (size.Width, size.Height);
             _lastSize = size;
             SizeChanging?.Invoke (this,new (size));
         }

From 8866b7311d34064d1eeea388d9ff0f266a1b31ac Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Fri, 27 Dec 2024 16:48:51 +0000
Subject: [PATCH 093/198] Fix net resizing not redrawing

---
 Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs         | 2 +-
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs          | 5 ++---
 Terminal.Gui/ConsoleDrivers/V2/WindowSizeMonitor.cs | 4 +++-
 3 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
index c99eaeb913..28636be078 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
@@ -34,5 +34,5 @@ public interface IWindowSizeMonitor
     /// <summary>Invoked when the terminal's size changed. The new size of the terminal is provided.</summary>
     event EventHandler<SizeChangedEventArgs>? SizeChanging;
 
-    void Poll ();
+    bool Poll ();
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 72139fa006..09e103e0a6 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -68,10 +68,9 @@ public void IterationImpl ()
 
             bool needsDrawOrLayout = AnySubviewsNeedDrawn(Application.Top);
 
-            // TODO: throttle this
-            WindowSizeMonitor.Poll ();
+            bool sizeChanged = WindowSizeMonitor.Poll ();
 
-            if (needsDrawOrLayout)
+            if (needsDrawOrLayout || sizeChanged)
             {
                 // TODO: Test only
                 Application.LayoutAndDraw (true);
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowSizeMonitor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowSizeMonitor.cs
index 28c522a859..ca4f568f4c 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowSizeMonitor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowSizeMonitor.cs
@@ -18,7 +18,7 @@ public WindowSizeMonitor (IConsoleOutput consoleOut, IOutputBuffer outputBuffer)
     }
 
     /// <inheritdoc />
-    public void Poll ()
+    public bool Poll ()
     {
         var size = _consoleOut.GetWindowSize ();
 
@@ -29,6 +29,8 @@ public void Poll ()
             _outputBuffer.SetWindowSize (size.Width, size.Height);
             _lastSize = size;
             SizeChanging?.Invoke (this,new (size));
+            return true;
         }
+        return false;
     }
 }

From 72a80bbef4bb386d9ad339d023c2163ed139aaad Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Fri, 27 Dec 2024 16:58:09 +0000
Subject: [PATCH 094/198] Log crit if net input boot goes badly - even if unit
 tests

---
 Terminal.Gui/ConsoleDrivers/V2/NetInput.cs | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
index 2c49d649db..8342192a1d 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
@@ -1,4 +1,6 @@
-namespace Terminal.Gui;
+using Microsoft.Extensions.Logging;
+
+namespace Terminal.Gui;
 
 public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
 {
@@ -13,9 +15,10 @@ public NetInput ()
             {
                 _adjustConsole = new NetWinVTConsole ();
             }
-            catch (ApplicationException)
+            catch (ApplicationException ex)
             {
                 // Likely running as a unit test, or in a non-interactive session.
+                Logging.Logger.LogCritical (ex,"NetWinVTConsole could not be constructed i.e. could not configure terminal modes. May indicate running in non-interactive session e.g. unit testing CI");
             }
         }
 

From 35e9b3e01eec061e052ee2255dbcefcd88462de6 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Fri, 27 Dec 2024 19:39:22 +0000
Subject: [PATCH 095/198] Add metric for invokes etc

---
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 09e103e0a6..9a8814139d 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -29,6 +29,8 @@ public class MainLoop<T> : IMainLoop<T>
 
     static readonly Histogram<int> totalIterationMetric = Logging.Meter.CreateHistogram<int> ("Iteration (ms)");
 
+    static readonly Histogram<int> iterationInvokesAndTimeouts = Logging.Meter.CreateHistogram<int> ("Invokes & Timers (ms)");
+
     public void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput)
     {
         InputBuffer = inputBuffer;
@@ -79,9 +81,13 @@ public void IterationImpl ()
             }
         }
 
+        var swCallbacks = Stopwatch.StartNew ();
+
         TimedEvents.LockAndRunTimers ();
 
         TimedEvents.LockAndRunIdles ();
+
+        iterationInvokesAndTimeouts.Record (swCallbacks.Elapsed.Milliseconds);
     }
 
 

From 1913154a8cc1b8c305beec5e59e3128eea4abdb0 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Fri, 27 Dec 2024 20:33:07 +0000
Subject: [PATCH 096/198] Rewrite test for Theory

---
 .../V2/MouseInterpreterTests.cs               | 54 +++++++++++++------
 1 file changed, 37 insertions(+), 17 deletions(-)

diff --git a/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs b/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
index 5fc89d79e7..8913eff4ec 100644
--- a/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
+++ b/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
@@ -3,26 +3,46 @@
 namespace UnitTests.ConsoleDrivers.V2;
 public class MouseInterpreterTests
 {
-    [Fact]
-    public void Mouse1Click ()
+    [Theory]
+    [MemberData (nameof (SequenceTests))]
+    public void TestMouseEventSequences_InterpretedOnlyAsFlag (List<MouseEventArgs> events, MouseFlags expected)
     {
-        var v = Mock.Of<IViewFinder> ();
-        var interpreter = new MouseInterpreter (null, v);
+        // Arrange: Mock dependencies and set up the interpreter
+        var viewFinder = Mock.Of<IViewFinder> ();
+        var interpreter = new MouseInterpreter (null, viewFinder);
 
-        Assert.Empty (interpreter.Process (
-                             new MouseEventArgs ()
-                             {
-                                 Flags = MouseFlags.Button1Pressed
-                             }));
+        // Act and Assert: Process all but the last event and ensure they yield no results
+        for (int i = 0; i < events.Count - 1; i++)
+        {
+            var intermediateResult = interpreter.Process (events [i]);
+            Assert.Empty (intermediateResult);
+        }
+
+        // Process the final event and verify the expected result
+        var finalResult = interpreter.Process (events [^1]).ToArray (); // ^1 is the last item in the list
+        var singleResult = Assert.Single (finalResult); // Ensure only one result is produced
+        Assert.Equal (expected, singleResult.Flags);
+    }
 
-        var result = interpreter.Process (
-                             new MouseEventArgs ()
-                             {
-                                 Flags = MouseFlags.Button1Released
-                             }).ToArray ();
-        var e = Assert.Single (result);
 
-        // TODO: Ultimately will not be the case as we will be dealing with double click and triple
-        Assert.Equal (MouseFlags.Button1Clicked,e.Flags);
+    public static IEnumerable<object []> SequenceTests ()
+    {
+        yield return new object []
+        {
+            new List<MouseEventArgs> ()
+            {
+                // Mouse was down
+                new ()
+                {
+                    Flags = MouseFlags.Button1Pressed
+                },
+
+                // Then it wasn't
+                new()
+            },
+            // Means click
+            MouseFlags.Button1Clicked
+        };
     }
+
 }

From cf1e7896086e861ee402d5cf6ff6d73426a5900f Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 28 Dec 2024 08:48:32 +0000
Subject: [PATCH 097/198] Work on MouseInterpreter

---
 .../V2/AlreadyResolvedException.cs            |  9 +++
 .../ConsoleDrivers/V2/MouseButtonSequence.cs  | 59 ++++++++++++++-----
 .../ConsoleDrivers/V2/MouseInterpreter.cs     | 51 +++++++---------
 3 files changed, 75 insertions(+), 44 deletions(-)
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/AlreadyResolvedException.cs

diff --git a/Terminal.Gui/ConsoleDrivers/V2/AlreadyResolvedException.cs b/Terminal.Gui/ConsoleDrivers/V2/AlreadyResolvedException.cs
new file mode 100644
index 0000000000..b3974a595c
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/AlreadyResolvedException.cs
@@ -0,0 +1,9 @@
+#nullable enable
+namespace Terminal.Gui;
+
+internal class AlreadyResolvedException : Exception
+{
+    public AlreadyResolvedException ():base("MouseButtonSequence already resolved")
+    {
+    }
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseButtonSequence.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseButtonSequence.cs
index 3476b87e3a..ede636dffe 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MouseButtonSequence.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseButtonSequence.cs
@@ -10,6 +10,7 @@ namespace Terminal.Gui;
 /// user clicked then user double-clicked then user triple clicked</remarks>
 internal class MouseButtonSequence
 {
+    private readonly MouseInterpreter _parent;
     private readonly IViewFinder _viewFinder;
     public int NumberOfClicks { get; set; }
 
@@ -22,23 +23,34 @@ internal class MouseButtonSequence
     public int ButtonIdx { get; }
 
     /// <summary>
-    /// Function for returning the current time. Use in unit tests to
-    /// ensure repeatable tests.
+    /// True if <see cref="Resolve"/> has happened. After this,
+    /// new state changes are forbidden
     /// </summary>
-    public Func<DateTime> Now { get; set; }
+    public bool IsResolved { get; private set; }
 
-    public MouseButtonSequence (int buttonIdx, Func<DateTime> now, IViewFinder viewFinder)
+    public MouseButtonSequence (MouseInterpreter parent, int buttonIdx,  IViewFinder viewFinder)
     {
         ButtonIdx = buttonIdx;
-        Now = now;
+        _parent = parent;
         _viewFinder = viewFinder;
     }
 
     public MouseEventArgs? Process (Point position, bool pressed)
     {
-        var last = MouseStates.Last ();
+        if (IsResolved)
+        {
+            throw new AlreadyResolvedException ();
+        }
+
+        var last = MouseStates.LastOrDefault ()
+                   ?? throw new Exception("Can only process new positions after baseline is established");
+
+        if (IsExpired ())
+        {
+            return Resolve ();
+        }
 
-        // Still pressed
+        // Still pressed/released
         if (last.Pressed && pressed)
         {
             // No change
@@ -47,11 +59,10 @@ public MouseButtonSequence (int buttonIdx, Func<DateTime> now, IViewFinder viewF
 
         var view = _viewFinder.GetViewAt (position, out var viewport);
 
-        NumberOfClicks++;
-        MouseStates.Add (new MouseButtonStateEx
+        var nextState = new MouseButtonStateEx
         {
             Button = ButtonIdx,
-            At = Now(),
+            At = _parent.Now (),
             Pressed = false,
             Position = position,
 
@@ -63,8 +74,10 @@ public MouseButtonSequence (int buttonIdx, Func<DateTime> now, IViewFinder viewF
             Ctrl = false,
             Alt = false,
 
-        });
+        };
 
+        NumberOfClicks++;
+        MouseStates.Add (nextState);
 
         if (IsResolveable ())
         {
@@ -74,9 +87,25 @@ public MouseButtonSequence (int buttonIdx, Func<DateTime> now, IViewFinder viewF
         return null;
     }
 
+    private bool IsExpired ()
+    {
+        var last = MouseStates.LastOrDefault ();
+
+        if (last == null || NumberOfClicks == 0)
+        {
+            return false;
+        }
+
+        return _parent.Now() - last.At > _parent.RepeatedClickThreshold;
+    }
+
     public bool IsResolveable ()
     {
+        // TODO: ultimately allow for more
         return NumberOfClicks > 0;
+
+        // Once we hit triple click we have to stop (no quad click event in MouseFlags)
+        return NumberOfClicks >= 3;
     }
     /// <summary>
     /// Resolves the narrative completely with immediate effect.
@@ -85,11 +114,13 @@ public bool IsResolveable ()
     /// <returns></returns>
     public MouseEventArgs? Resolve ()
     {
-        if (!IsResolveable ())
+        if (IsResolved)
         {
-            return null;
+            throw new AlreadyResolvedException ();
         }
 
+        IsResolved = true;
+
         if (NumberOfClicks == 1)
         {
             var last = MouseStates.Last ();
@@ -144,4 +175,4 @@ private MouseFlags ToClicks (int buttonIdx, int numberOfClicks)
                    _ => throw new ArgumentOutOfRangeException (nameof (buttonIdx), "Unsupported button index")
                };
     }
-}
+}
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
index afb74fe88c..50f6f1d44c 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
@@ -10,19 +10,13 @@ internal class MouseInterpreter
     /// Function for returning the current time. Use in unit tests to
     /// ensure repeatable tests.
     /// </summary>
-    private Func<DateTime> Now { get; set; }
+    public Func<DateTime> Now { get; set; }
 
     /// <summary>
-    /// How long to wait for a second click after the first before giving up and
+    /// How long to wait for a second, third, fourth click after the first before giving up and
     /// releasing event as a 'click'
     /// </summary>
-    public TimeSpan DoubleClickThreshold { get; set; }
-
-    /// <summary>
-    /// How long to wait for a third click after the second before giving up and
-    /// releasing event as a 'double click'
-    /// </summary>
-    public TimeSpan TripleClickThreshold { get; set; }
+    public TimeSpan RepeatedClickThreshold { get; set; }
 
     /// <summary>
     /// How far between a mouse down and mouse up before it is considered a 'drag' rather
@@ -34,6 +28,7 @@ internal class MouseInterpreter
     public MouseState CurrentState { get; private set; }
 
     private MouseButtonSequence? [] _ongoingSequences = new MouseButtonSequence? [4];
+    private readonly bool[] _lastPressed = new bool [4];
 
     public Action<MouseButtonSequence> Click { get; set; }
 
@@ -41,14 +36,12 @@ public MouseInterpreter (
         Func<DateTime>? now = null,
         IViewFinder viewFinder = null,
         TimeSpan? doubleClickThreshold = null,
-        TimeSpan? tripleClickThreshold = null,
         int dragThreshold = 5
     )
     {
         _viewFinder = viewFinder ?? new StaticViewFinder ();
         Now = now ?? (() => DateTime.Now);
-        DoubleClickThreshold = doubleClickThreshold ?? TimeSpan.FromMilliseconds (500);
-        TripleClickThreshold = tripleClickThreshold ?? TimeSpan.FromMilliseconds (1000);
+        RepeatedClickThreshold = doubleClickThreshold ?? TimeSpan.FromMilliseconds (500);
         DragThreshold = dragThreshold;
     }
 
@@ -58,36 +51,34 @@ public IEnumerable<MouseEventArgs> Process (MouseEventArgs e)
         for (int i = 0; i < 4; i++)
         {
             bool isPressed = IsPressed (i, e.Flags);
+            var sequence = _ongoingSequences [i];
 
-            // Update narratives
-            if (isPressed)
+            // If we have no ongoing narratives
+            if (sequence == null)
             {
-                if (_ongoingSequences [i] == null)
+                // Changing from not pressed to pressed
+                if (isPressed && isPressed != _lastPressed [i])
                 {
+                    // Begin sequence that leads to click/double click/triple click etc
                     _ongoingSequences [i] = BeginPressedNarrative (i, e);
                 }
-                else
-                {
-                    var resolve = _ongoingSequences [i]?.Process (e.Position, true);
-
-                    if (resolve != null)
-                    {
-                        _ongoingSequences [i] = null;
-                        yield return resolve;
-                    }
-                }
             }
             else
             {
-                var resolve = _ongoingSequences [i]?.Process (e.Position, false);
+                var resolve = sequence.Process (e.Position, isPressed);
 
-                if (resolve != null)
+                if (sequence.IsResolved)
                 {
-
                     _ongoingSequences [i] = null;
+                }
+
+                if (resolve != null)
+                {
                     yield return resolve;
                 }
             }
+
+            _lastPressed [i] = isPressed;
         }
     }
 
@@ -129,7 +120,7 @@ private MouseButtonSequence BeginPressedNarrative (int buttonIdx, MouseEventArgs
     {
         var view = _viewFinder.GetViewAt (e.Position, out var viewport);
 
-        return new MouseButtonSequence(buttonIdx, Now,_viewFinder)
+        return new MouseButtonSequence(this,buttonIdx,_viewFinder)
         {
             NumberOfClicks = 0,
             MouseStates =
@@ -161,4 +152,4 @@ public static double DistanceTo (Point p1, Point p2)
         int deltaY = p2.Y - p1.Y;
         return Math.Sqrt (deltaX * deltaX + deltaY * deltaY);
     }*/
-}
+}
\ No newline at end of file

From 617e9d95b35d80fab07ef48dc9b26423d106257c Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 28 Dec 2024 13:27:55 +0000
Subject: [PATCH 098/198] Vastly simplify MouseInterpreter

---
 .../ConsoleDrivers/V2/InputProcessor.cs       |  10 +-
 .../ConsoleDrivers/V2/MouseButtonSequence.cs  | 178 ------------------
 .../ConsoleDrivers/V2/MouseButtonState.cs     |  91 ++++++---
 .../ConsoleDrivers/V2/MouseInterpreter.cs     | 160 ++++++----------
 Terminal.Gui/ConsoleDrivers/V2/MouseState.cs  |  10 -
 5 files changed, 118 insertions(+), 331 deletions(-)
 delete mode 100644 Terminal.Gui/ConsoleDrivers/V2/MouseButtonSequence.cs
 delete mode 100644 Terminal.Gui/ConsoleDrivers/V2/MouseState.cs

diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index 282efa920b..a1bc6a944f 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -52,14 +52,10 @@ public void OnMouseEvent (MouseEventArgs a)
         // Ensure ScreenPosition is set
         a.ScreenPosition = a.Position;
 
-        // Pass on basic state
-        MouseEvent?.Invoke (this, a);
+        _mouseInterpreter.Process (a);
 
-        // Pass on any interpreted states e.g. click/double click etc
-        foreach (var e in _mouseInterpreter.Process (a))
-        {
-            MouseEvent?.Invoke (this, e);
-        }
+        // Pass on
+        MouseEvent?.Invoke (this, a);
     }
 
     public InputProcessor (ConcurrentQueue<T> inputBuffer)
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseButtonSequence.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseButtonSequence.cs
deleted file mode 100644
index ede636dffe..0000000000
--- a/Terminal.Gui/ConsoleDrivers/V2/MouseButtonSequence.cs
+++ /dev/null
@@ -1,178 +0,0 @@
-#nullable enable
-
-namespace Terminal.Gui;
-
-/// <summary>
-/// Describes a completed narrative e.g. 'user triple clicked'.
-/// </summary>
-/// <remarks>Internally we can have a double click narrative that becomes
-/// a triple click narrative. But we will not release both i.e. we don't say
-/// user clicked then user double-clicked then user triple clicked</remarks>
-internal class MouseButtonSequence
-{
-    private readonly MouseInterpreter _parent;
-    private readonly IViewFinder _viewFinder;
-    public int NumberOfClicks { get; set; }
-
-    /// <summary>
-    /// Mouse states during which click was generated.
-    /// N = 2x<see cref="NumberOfClicks"/>
-    /// </summary>
-    public List<MouseButtonStateEx> MouseStates { get; set; } = new ();
-
-    public int ButtonIdx { get; }
-
-    /// <summary>
-    /// True if <see cref="Resolve"/> has happened. After this,
-    /// new state changes are forbidden
-    /// </summary>
-    public bool IsResolved { get; private set; }
-
-    public MouseButtonSequence (MouseInterpreter parent, int buttonIdx,  IViewFinder viewFinder)
-    {
-        ButtonIdx = buttonIdx;
-        _parent = parent;
-        _viewFinder = viewFinder;
-    }
-
-    public MouseEventArgs? Process (Point position, bool pressed)
-    {
-        if (IsResolved)
-        {
-            throw new AlreadyResolvedException ();
-        }
-
-        var last = MouseStates.LastOrDefault ()
-                   ?? throw new Exception("Can only process new positions after baseline is established");
-
-        if (IsExpired ())
-        {
-            return Resolve ();
-        }
-
-        // Still pressed/released
-        if (last.Pressed && pressed)
-        {
-            // No change
-            return null;
-        }
-
-        var view = _viewFinder.GetViewAt (position, out var viewport);
-
-        var nextState = new MouseButtonStateEx
-        {
-            Button = ButtonIdx,
-            At = _parent.Now (),
-            Pressed = false,
-            Position = position,
-
-            View = view,
-            ViewportPosition = viewport,
-
-            /* TODO: Need these probably*/
-            Shift = false,
-            Ctrl = false,
-            Alt = false,
-
-        };
-
-        NumberOfClicks++;
-        MouseStates.Add (nextState);
-
-        if (IsResolveable ())
-        {
-            return Resolve ();
-        }
-
-        return null;
-    }
-
-    private bool IsExpired ()
-    {
-        var last = MouseStates.LastOrDefault ();
-
-        if (last == null || NumberOfClicks == 0)
-        {
-            return false;
-        }
-
-        return _parent.Now() - last.At > _parent.RepeatedClickThreshold;
-    }
-
-    public bool IsResolveable ()
-    {
-        // TODO: ultimately allow for more
-        return NumberOfClicks > 0;
-
-        // Once we hit triple click we have to stop (no quad click event in MouseFlags)
-        return NumberOfClicks >= 3;
-    }
-    /// <summary>
-    /// Resolves the narrative completely with immediate effect.
-    /// This may return null if e.g. so far all that has accumulated is a mouse down.
-    /// </summary>
-    /// <returns></returns>
-    public MouseEventArgs? Resolve ()
-    {
-        if (IsResolved)
-        {
-            throw new AlreadyResolvedException ();
-        }
-
-        IsResolved = true;
-
-        if (NumberOfClicks == 1)
-        {
-            var last = MouseStates.Last ();
-
-            // its a click
-            return new MouseEventArgs
-            {
-                Handled = false,
-                Flags = ToClicks(this.ButtonIdx,NumberOfClicks),
-                ScreenPosition = last.Position,
-                Position = last.ViewportPosition,
-                View = last.View,
-            };
-        }
-
-        return null;
-    }
-
-    private MouseFlags ToClicks (int buttonIdx, int numberOfClicks)
-    {
-        if (numberOfClicks == 0)
-        {
-            throw new ArgumentOutOfRangeException (nameof (numberOfClicks), "Zero clicks are not valid.");
-        }
-
-        return buttonIdx switch
-               {
-                   0 => numberOfClicks switch
-                        {
-                            1 => MouseFlags.Button1Clicked,
-                            2 => MouseFlags.Button1DoubleClicked,
-                            _ => MouseFlags.Button1TripleClicked
-                        },
-                   1 => numberOfClicks switch
-                        {
-                            1 => MouseFlags.Button2Clicked,
-                            2 => MouseFlags.Button2DoubleClicked,
-                            _ => MouseFlags.Button2TripleClicked
-                        },
-                   2 => numberOfClicks switch
-                        {
-                            1 => MouseFlags.Button3Clicked,
-                            2 => MouseFlags.Button3DoubleClicked,
-                            _ => MouseFlags.Button3TripleClicked
-                        },
-                   3 => numberOfClicks switch
-                        {
-                            1 => MouseFlags.Button4Clicked,
-                            2 => MouseFlags.Button4DoubleClicked,
-                            _ => MouseFlags.Button4TripleClicked
-                        },
-                   _ => throw new ArgumentOutOfRangeException (nameof (buttonIdx), "Unsupported button index")
-               };
-    }
-}
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseButtonState.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseButtonState.cs
index f190321034..13dd36be10 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MouseButtonState.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseButtonState.cs
@@ -6,7 +6,11 @@ namespace Terminal.Gui;
 /// </summary>
 internal class MouseButtonStateEx
 {
-    public required int Button { get; set; }
+    private readonly Func<DateTime> _now;
+    private readonly TimeSpan _repeatClickThreshold;
+    private readonly int _buttonIdx;
+    private int _consecutiveClicks = 0;
+
     /// <summary>
     ///     When the button entered its current state.
     /// </summary>
@@ -17,38 +21,65 @@ internal class MouseButtonStateEx
     /// </summary>
     public bool Pressed { get; set; }
 
-    /// <summary>
-    /// The screen location when the mouse button entered its current state
-    /// (became pressed or was released)
-    /// </summary>
-    public Point Position { get; set; }
+    public MouseButtonStateEx (Func<DateTime> now,TimeSpan repeatClickThreshold, int buttonIdx)
+    {
+        _now = now;
+        _repeatClickThreshold = repeatClickThreshold;
+        _buttonIdx = buttonIdx;
+    }
 
-    /// <summary>
-    /// The <see cref="View"/> (if any) that was at the <see cref="Position"/>
-    /// when the button entered its current state.
-    /// </summary>
-    public View? View { get; set; }
+    public void UpdateState (MouseEventArgs e, out int? numClicks)
+    {
+        bool isPressedNow = IsPressed (_buttonIdx, e.Flags);
 
-    /// <summary>
-    /// Viewport relative position within <see cref="View"/> (if there is one)
-    /// </summary>
-    public Point ViewportPosition { get; set; }
+        var elapsed =_now() - At;
 
-    /// <summary>
-    /// True if shift was provided by the console at the time the mouse
-    ///  button entered its current state.
-    /// </summary>
-    public bool Shift { get; set; }
+        if (elapsed > _repeatClickThreshold)
+        {
+            // Expired
+            OverwriteState (e);
+            _consecutiveClicks = 0;
+            numClicks = null;
+        }
+        else
+        {
+            if (isPressedNow == Pressed)
+            {
+                // No change in button state so do nothing
+                numClicks = null;
+                return;
+            }
 
-    /// <summary>
-    /// True if control was provided by the console at the time the mouse
-    /// button entered its current state.
-    /// </summary>
-    public bool Ctrl { get; set; }
+            if (Pressed)
+            {
+                // Click released
+                numClicks = ++_consecutiveClicks;
+            }
+            else
+            {
+                numClicks = null;
+            }
 
-    /// <summary>
-    /// True if alt was held down at the time the mouse
-    /// button entered its current state.
-    /// </summary>
-    public bool Alt { get; set; }
+            // Record new state
+            OverwriteState (e);
+        }
+    }
+
+    private void OverwriteState (MouseEventArgs e)
+    {
+        Pressed = IsPressed (_buttonIdx, e.Flags);
+        At = _now ();
+    }
+
+    private bool IsPressed (int btn, MouseFlags eFlags)
+    {
+        return btn switch
+               {
+                   0 => eFlags.HasFlag (MouseFlags.Button1Pressed),
+                   1 => eFlags.HasFlag (MouseFlags.Button2Pressed),
+                   2 => eFlags.HasFlag (MouseFlags.Button3Pressed),
+                   3 => eFlags.HasFlag (MouseFlags.Button4Pressed),
+                   _ => throw new ArgumentOutOfRangeException (nameof (btn))
+               };
+    }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
index 50f6f1d44c..2acba64a45 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
@@ -1,10 +1,11 @@
 #nullable enable
 
+using static Terminal.Gui.SpinnerStyle;
+
 namespace Terminal.Gui;
 
 internal class MouseInterpreter
 {
-    private readonly IViewFinder _viewFinder;
 
     /// <summary>
     /// Function for returning the current time. Use in unit tests to
@@ -18,138 +19,85 @@ internal class MouseInterpreter
     /// </summary>
     public TimeSpan RepeatedClickThreshold { get; set; }
 
-    /// <summary>
-    /// How far between a mouse down and mouse up before it is considered a 'drag' rather
-    /// than a 'click'. Console row counts for 2 units while column counts for only 1. Distance is
-    /// measured in Euclidean distance.
-    /// </summary>
-    public double DragThreshold { get; set; }
-
-    public MouseState CurrentState { get; private set; }
+    private MouseButtonStateEx [] _buttonStates ;
 
-    private MouseButtonSequence? [] _ongoingSequences = new MouseButtonSequence? [4];
-    private readonly bool[] _lastPressed = new bool [4];
-
-    public Action<MouseButtonSequence> Click { get; set; }
 
     public MouseInterpreter (
         Func<DateTime>? now = null,
-        IViewFinder viewFinder = null,
-        TimeSpan? doubleClickThreshold = null,
-        int dragThreshold = 5
+        TimeSpan? doubleClickThreshold = null
     )
     {
-        _viewFinder = viewFinder ?? new StaticViewFinder ();
         Now = now ?? (() => DateTime.Now);
         RepeatedClickThreshold = doubleClickThreshold ?? TimeSpan.FromMilliseconds (500);
-        DragThreshold = dragThreshold;
+
+        _buttonStates = new []
+        {
+            new MouseButtonStateEx (this.Now,this.RepeatedClickThreshold,0),
+            new MouseButtonStateEx (this.Now,this.RepeatedClickThreshold,1),
+            new MouseButtonStateEx (this.Now,this.RepeatedClickThreshold,2),
+            new MouseButtonStateEx (this.Now,this.RepeatedClickThreshold,3),
+        };
     }
 
-    public IEnumerable<MouseEventArgs> Process (MouseEventArgs e)
+    public MouseEventArgs Process (MouseEventArgs e)
     {
         // For each mouse button
         for (int i = 0; i < 4; i++)
         {
-            bool isPressed = IsPressed (i, e.Flags);
-            var sequence = _ongoingSequences [i];
 
-            // If we have no ongoing narratives
-            if (sequence == null)
+            _buttonStates [i].UpdateState (e, out var numClicks);
+
+            if (numClicks.HasValue)
             {
-                // Changing from not pressed to pressed
-                if (isPressed && isPressed != _lastPressed [i])
-                {
-                    // Begin sequence that leads to click/double click/triple click etc
-                    _ongoingSequences [i] = BeginPressedNarrative (i, e);
-                }
+                return RaiseClick (i,numClicks.Value, e);
             }
-            else
-            {
-                var resolve = sequence.Process (e.Position, isPressed);
+        }
 
-                if (sequence.IsResolved)
-                {
-                    _ongoingSequences [i] = null;
-                }
+        return e;
+    }
 
-                if (resolve != null)
-                {
-                    yield return resolve;
-                }
-            }
+    private MouseEventArgs RaiseClick (int button, int numberOfClicks, MouseEventArgs mouseEventArgs)
+    {
+        mouseEventArgs.Flags |= ToClicks (button, numberOfClicks);
 
-            _lastPressed [i] = isPressed;
-        }
+        return mouseEventArgs;
     }
 
-    public IEnumerable<MouseEventArgs> Release ()
+
+    private MouseFlags ToClicks (int buttonIdx, int numberOfClicks)
     {
-        for (var i = 0; i < _ongoingSequences.Length; i++)
+        if (numberOfClicks == 0)
         {
-            MouseButtonSequence? narrative = _ongoingSequences [i];
-
-            if (narrative != null)
-            {
-                if (narrative.IsResolveable ())
-                {
-                    var args = narrative.Resolve ();
-
-                    if (args != null)
-                    {
-                        yield return args;
-                    }
-                    _ongoingSequences [i] = null;
-                }
-            }
+            throw new ArgumentOutOfRangeException (nameof (numberOfClicks), "Zero clicks are not valid.");
         }
-    }
 
-    private bool IsPressed (int btn, MouseFlags eFlags)
-    {
-        return btn switch
+        return buttonIdx switch
                {
-                   0=>eFlags.HasFlag (MouseFlags.Button1Pressed),
-                   1 => eFlags.HasFlag (MouseFlags.Button2Pressed),
-                   2 => eFlags.HasFlag (MouseFlags.Button3Pressed),
-                   3 => eFlags.HasFlag (MouseFlags.Button4Pressed),
-                   _ => throw new ArgumentOutOfRangeException(nameof(btn))
+                   0 => numberOfClicks switch
+                        {
+                            1 => MouseFlags.Button1Clicked,
+                            2 => MouseFlags.Button1DoubleClicked,
+                            _ => MouseFlags.Button1TripleClicked
+                        },
+                   1 => numberOfClicks switch
+                        {
+                            1 => MouseFlags.Button2Clicked,
+                            2 => MouseFlags.Button2DoubleClicked,
+                            _ => MouseFlags.Button2TripleClicked
+                        },
+                   2 => numberOfClicks switch
+                        {
+                            1 => MouseFlags.Button3Clicked,
+                            2 => MouseFlags.Button3DoubleClicked,
+                            _ => MouseFlags.Button3TripleClicked
+                        },
+                   3 => numberOfClicks switch
+                        {
+                            1 => MouseFlags.Button4Clicked,
+                            2 => MouseFlags.Button4DoubleClicked,
+                            _ => MouseFlags.Button4TripleClicked
+                        },
+                   _ => throw new ArgumentOutOfRangeException (nameof (buttonIdx), "Unsupported button index")
                };
     }
-
-    private MouseButtonSequence BeginPressedNarrative (int buttonIdx, MouseEventArgs e)
-    {
-        var view = _viewFinder.GetViewAt (e.Position, out var viewport);
-
-        return new MouseButtonSequence(this,buttonIdx,_viewFinder)
-        {
-            NumberOfClicks = 0,
-            MouseStates =
-            [
-                new MouseButtonStateEx()
-                {
-                    Button = buttonIdx,
-                    At = Now(),
-                    Pressed = true,
-                    Position = e.ScreenPosition,
-                    View = view,
-                    ViewportPosition = viewport,
-
-                    /* TODO: Do these too*/
-                    Shift = false,
-                    Ctrl = false,
-                    Alt = false
-                }
-            ]
-        };
-    }
-
-    public Point ViewportPosition { get; set; }
-
-    /* TODO: Probably need this at some point
-    public static double DistanceTo (Point p1, Point p2)
-    {
-        int deltaX = p2.X - p1.X;
-        int deltaY = p2.Y - p1.Y;
-        return Math.Sqrt (deltaX * deltaX + deltaY * deltaY);
-    }*/
 }
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseState.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseState.cs
deleted file mode 100644
index 63a6d51cc0..0000000000
--- a/Terminal.Gui/ConsoleDrivers/V2/MouseState.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-#nullable enable
-
-namespace Terminal.Gui;
-
-internal class MouseState
-{
-    public MouseButtonStateEx [] ButtonStates = new MouseButtonStateEx? [4];
-
-    public Point Position;
-}
\ No newline at end of file

From db8942d19946bcf19791a38b531bf39799fb3c95 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 28 Dec 2024 13:52:58 +0000
Subject: [PATCH 099/198] AnsiResponseParser now handles net event keyboard
 inputs for cursor keys

---
 .../AnsiResponseParser/AnsiKeyboardParser.cs  | 44 +++++++++++++++++++
 .../AnsiResponseParser/AnsiMouseParser.cs     | 12 +++++
 .../AnsiResponseParser/AnsiResponseParser.cs  | 37 ++++++++++++++--
 .../ConsoleDrivers/V2/InputProcessor.cs       |  8 ++++
 .../V2/MouseInterpreterTests.cs               | 10 ++---
 5 files changed, 102 insertions(+), 9 deletions(-)
 create mode 100644 Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
new file mode 100644
index 0000000000..edb97d516e
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
@@ -0,0 +1,44 @@
+using System.Text.RegularExpressions;
+
+namespace Terminal.Gui;
+
+public class AnsiKeyboardParser
+{
+    // Regex patterns for ANSI arrow keys (Up, Down, Left, Right)
+    private readonly Regex _arrowKeyPattern = new (@"\u001b\[(A|B|C|D)", RegexOptions.Compiled);
+
+    /// <summary>
+    ///     Parses an ANSI escape sequence into a keyboard event. Returns null if input
+    ///     is not a recognized keyboard event or its syntax is not understood.
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    public Key ProcessKeyboardInput (string input)
+    {
+        // Match arrow key events
+        Match match = _arrowKeyPattern.Match (input);
+
+        if (match.Success)
+        {
+            char direction = match.Groups [1].Value [0];
+
+            return direction switch
+                   {
+                       'A' => Key.CursorUp,
+                       'B' => Key.CursorDown,
+                       'C' => Key.CursorRight,
+                       'D' => Key.CursorDown,
+                       _ => default(Key)
+                   };
+
+        }
+
+        // It's an unrecognized keyboard event
+        return null;
+    }
+
+    public bool IsKeyboard (string cur)
+    {
+        return _arrowKeyPattern.IsMatch (cur);
+    }
+}
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs
index fbb9c0984f..cb95740484 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs
@@ -13,6 +13,18 @@ public class AnsiMouseParser
     // Regex patterns for button press/release, wheel scroll, and mouse position reporting
     private readonly Regex _mouseEventPattern = new (@"\u001b\[<(\d+);(\d+);(\d+)(M|m)", RegexOptions.Compiled);
 
+    /// <summary>
+    /// Returns true if it is a mouse event
+    /// </summary>
+    /// <param name="cur"></param>
+    /// <returns></returns>
+    public bool IsMouse (string cur)
+    {
+        // Typically in this format
+        // ESC [ < {button_code};{x_pos};{y_pos}{final_byte}
+        return cur.EndsWith ('M') || cur.EndsWith ('m');
+    }
+
     /// <summary>
     ///     Parses a mouse ansi escape sequence into a mouse event. Returns null if input
     ///     is not a mouse event or its syntax is not understood.
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
index e8a9a40576..64938a3f32 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
@@ -7,6 +7,7 @@ namespace Terminal.Gui;
 internal abstract class AnsiResponseParserBase : IAnsiResponseParser
 {
     private readonly AnsiMouseParser _mouseParser = new  ();
+    private readonly AnsiKeyboardParser _keyboardParser = new ();
     protected object _lockExpectedResponses = new();
 
     protected object _lockState = new ();
@@ -16,12 +17,22 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
     /// </summary>
     public event EventHandler<MouseEventArgs> Mouse;
 
+    /// <summary>
+    /// Event raised when keyboard event is detected (e.g. cursors) - requires setting <see cref="HandleKeyboard"/>
+    /// </summary>
+    public event Action<object, Key> Keyboard;
+
     /// <summary>
     /// True to explicitly handle mouse escape sequences by passing them to <see cref="Mouse"/> event.
     /// Defaults to <see langword="false"/>
     /// </summary>
     public bool HandleMouse { get; set; } = false;
 
+    /// <summary>
+    /// True to explicitly handle keyboard escape sequences (such as cursor keys) by passing them to <see cref="Keyboard"/> event
+    /// </summary>
+    public bool HandleKeyboard { get; set; } = false;
+
     /// <summary>
     ///     Responses we are expecting to come in.
     /// </summary>
@@ -208,6 +219,15 @@ protected bool ShouldReleaseHeldContent ()
                 return false;
             }
 
+            if (HandleKeyboard && IsKeyboard (cur))
+            {
+                RaiseKeyboardEvent (cur);
+                ResetState ();
+
+                Logging.Logger.LogTrace ($"AnsiResponseParser handled as keyboard '{cur}'");
+                return false;
+            }
+
             lock (_lockExpectedResponses)
             {
                 // Look for an expected response for what is accumulated so far (since Esc)
@@ -281,11 +301,20 @@ private void RaiseMouseEvent (string cur)
 
     private bool IsMouse (string cur)
     {
-        // Typically in this format
-        // ESC [ < {button_code};{x_pos};{y_pos}{final_byte}
-        return cur.EndsWith ('M') || cur.EndsWith ('m');
+        return _mouseParser.IsMouse (cur);
+    }
+    private void RaiseKeyboardEvent (string cur)
+    {
+        var k = _keyboardParser.ProcessKeyboardInput (cur);
+        Keyboard?.Invoke (this, k);
+    }
+    private bool IsKeyboard (string cur)
+    {
+        return _keyboardParser.IsKeyboard (cur);
     }
 
+
+
     /// <summary>
     ///     <para>
     ///         When overriden in a derived class, indicates whether the unexpected response
@@ -390,6 +419,7 @@ public AnsiResponseParser () : base (new GenericHeld<T> ()) { }
     /// <inheritdoc cref="AnsiResponseParser.UnknownResponseHandler"/>
     public Func<IEnumerable<Tuple<char, T>>, bool> UnexpectedResponseHandler { get; set; } = _ => false;
 
+
     public IEnumerable<Tuple<char, T>> ProcessInput (params Tuple<char, T> [] input)
     {
         List<Tuple<char, T>> output = new ();
@@ -451,6 +481,7 @@ public void ExpectResponseT (string terminator, Action<IEnumerable<Tuple<char, T
 
     /// <inheritdoc/>
     protected override bool ShouldSwallowUnexpectedResponse () { return UnexpectedResponseHandler.Invoke (HeldToEnumerable ()); }
+
 }
 
 internal class AnsiResponseParser () : AnsiResponseParserBase (new StringHeld ())
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index a1bc6a944f..8df1dd9c32 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -63,6 +63,14 @@ public InputProcessor (ConcurrentQueue<T> inputBuffer)
         InputBuffer = inputBuffer;
         Parser.HandleMouse = true;
         Parser.Mouse += (s, e) => OnMouseEvent (e);
+
+        Parser.HandleKeyboard = true;
+        Parser.Keyboard += (s,k)=>
+                          {
+                              OnKeyDown (k);
+                              OnKeyUp (k);
+                          };
+
         // TODO: For now handle all other escape codes with ignore
         Parser.UnexpectedResponseHandler = str => { return true; };
     }
diff --git a/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs b/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
index 8913eff4ec..7d32e2916d 100644
--- a/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
+++ b/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
@@ -8,20 +8,18 @@ public class MouseInterpreterTests
     public void TestMouseEventSequences_InterpretedOnlyAsFlag (List<MouseEventArgs> events, MouseFlags expected)
     {
         // Arrange: Mock dependencies and set up the interpreter
-        var viewFinder = Mock.Of<IViewFinder> ();
-        var interpreter = new MouseInterpreter (null, viewFinder);
+        var interpreter = new MouseInterpreter (null);
 
         // Act and Assert: Process all but the last event and ensure they yield no results
         for (int i = 0; i < events.Count - 1; i++)
         {
             var intermediateResult = interpreter.Process (events [i]);
-            Assert.Empty (intermediateResult);
+            Assert.Equal (events [i].Flags,intermediateResult.Flags);
         }
 
         // Process the final event and verify the expected result
-        var finalResult = interpreter.Process (events [^1]).ToArray (); // ^1 is the last item in the list
-        var singleResult = Assert.Single (finalResult); // Ensure only one result is produced
-        Assert.Equal (expected, singleResult.Flags);
+        var finalResult = interpreter.Process (events [^1]); // ^1 is the last item in the list
+        Assert.Equal (expected, finalResult.Flags);
     }
 
 

From 587c5a464c6df55d6459cd45f340be082b43250b Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 28 Dec 2024 15:39:13 +0000
Subject: [PATCH 100/198] Log what type of driver is being created and fix
 'bug' where v2net would switch to win on future calls to Init()

---
 .../ConsoleDrivers/V2/ApplicationV2.cs        | 16 ++++++--
 Terminal.Gui/ConsoleDrivers/V2/NetInput.cs    |  2 +
 Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs   | 41 +++++++++++--------
 .../ConsoleDrivers/V2/WindowsInput.cs         |  3 +-
 .../ConsoleDrivers/V2/WindowsOutput.cs        |  2 +
 5 files changed, 42 insertions(+), 22 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index e9672f564c..8d6d53720b 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -1,4 +1,5 @@
-using System.Collections.Concurrent;
+#nullable enable
+using System.Collections.Concurrent;
 using Microsoft.Extensions.Logging;
 using System.Runtime.ConstrainedExecution;
 
@@ -12,6 +13,7 @@ public class ApplicationV2 : ApplicationImpl
     private readonly Func<IWindowsInput> _winInputFactory;
     private readonly Func<IConsoleOutput> _winOutputFactory;
     private IMainLoopCoordinator _coordinator;
+    private string? _driverName;
     public ITimedEvents TimedEvents { get; } = new TimedEvents ();
     public ApplicationV2 () : this (
                                     ()=>new NetInput (),
@@ -36,20 +38,26 @@ Func<IConsoleOutput> winOutputFactory
     }
 
     /// <inheritdoc />
-    public override void Init (IConsoleDriver driver = null, string driverName = null)
+    public override void Init (IConsoleDriver? driver = null, string? driverName = null)
     {
+        if (!string.IsNullOrWhiteSpace (driverName))
+        {
+            _driverName = driverName;
+        }
         Application.Navigation = new ();
 
         Application.AddKeyBindings ();
 
-        CreateDriver (driverName);
+        // This is consistent with Application.ForceDriver which magnetically picks up driverName
+        // making it use custom driver in future shutdown/init calls where no driver is specified
+        CreateDriver (driverName ?? _driverName);
 
         Application.Initialized = true;
 
         Application.SubscribeDriverEvents ();
     }
 
-    private void CreateDriver (string driverName)
+    private void CreateDriver (string? driverName)
     {
 
         PlatformID p = Environment.OSVersion.Platform;
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
index 8342192a1d..a906b994a3 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
@@ -8,6 +8,8 @@ public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
 
     public NetInput ()
     {
+
+        Logging.Logger.LogInformation ($"Creating {nameof (NetInput)}");
         var p = Environment.OSVersion.Platform;
         if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
         {
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
index 663883896f..3dcc3401f9 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
@@ -1,4 +1,6 @@
-namespace Terminal.Gui;
+using Microsoft.Extensions.Logging;
+
+namespace Terminal.Gui;
 public class NetOutput : IConsoleOutput
 {
     public bool IsWinPlatform { get; }
@@ -6,6 +8,8 @@ public class NetOutput : IConsoleOutput
     private CursorVisibility? _cachedCursorVisibility;
     public NetOutput ()
     {
+        Logging.Logger.LogInformation ($"Creating {nameof(NetOutput)}");
+
         PlatformID p = Environment.OSVersion.Platform;
 
         if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
@@ -54,7 +58,7 @@ public void Write (IOutputBuffer buffer)
                 continue;
             }
 
-            if (!SetCursorPosition (0, row))
+            if (!SetCursorPositionImpl (0, row))
             {
                 return;
             }
@@ -137,7 +141,7 @@ public void Write (IOutputBuffer buffer)
                     else if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
                     {
                         WriteToConsole (output, ref lastCol, row, ref outputWidth);
-                        SetCursorPosition (col - 1, row);
+                        SetCursorPositionImpl (col - 1, row);
                     }
 
                     buffer.Contents [row, col].IsDirty = false;
@@ -146,21 +150,21 @@ public void Write (IOutputBuffer buffer)
 
             if (output.Length > 0)
             {
-                SetCursorPosition (lastCol, row);
+                SetCursorPositionImpl (lastCol, row);
                 Console.Write (output);
             }
+        }
 
-            foreach (var s in Application.Sixel)
+        foreach (var s in Application.Sixel)
+        {
+            if (!string.IsNullOrWhiteSpace (s.SixelData))
             {
-                if (!string.IsNullOrWhiteSpace (s.SixelData))
-                {
-                    SetCursorPosition (s.ScreenPosition.X, s.ScreenPosition.Y);
-                    Console.Write (s.SixelData);
-                }
+                SetCursorPositionImpl (s.ScreenPosition.X, s.ScreenPosition.Y);
+                Console.Write (s.SixelData);
             }
         }
 
-        SetCursorPosition (0, 0);
+        SetCursorPositionImpl (0, 0);
 
         _cachedCursorVisibility = savedVisibility;
     }
@@ -173,7 +177,7 @@ public Size GetWindowSize ()
 
     void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
     {
-        SetCursorPosition (lastCol, row);
+        SetCursorPositionImpl (lastCol, row);
         Console.Write (output);
         output.Clear ();
         lastCol += outputWidth;
@@ -187,7 +191,14 @@ public bool SetCursorVisibility (CursorVisibility visibility)
 
         return visibility == CursorVisibility.Default;
     }
-    private bool SetCursorPosition (int col, int row)
+
+    /// <inheritdoc />
+    public void SetCursorPosition (int col, int row)
+    {
+        SetCursorPositionImpl (col, row);
+    }
+
+    private bool SetCursorPositionImpl (int col, int row)
     {
         if (IsWinPlatform)
         {
@@ -222,8 +233,4 @@ void IConsoleOutput.SetCursorVisibility (CursorVisibility visibility)
         Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
     }
 
-    void IConsoleOutput.SetCursorPosition (int col, int row)
-    {
-        Console.SetCursorPosition (col, row);
-    }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
index 3dc82ffac2..06ad70f30b 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
@@ -1,4 +1,5 @@
 using System.Runtime.InteropServices;
+using Microsoft.Extensions.Logging;
 using static Terminal.Gui.WindowsConsole;
 
 namespace Terminal.Gui;
@@ -37,9 +38,9 @@ out uint lpNumberOfEventsRead
 
     public WindowsInput ()
     {
+        Logging.Logger.LogInformation ($"Creating {nameof (WindowsInput)}");
         _inputHandle = GetStdHandle (STD_INPUT_HANDLE);
 
-
         GetConsoleMode (_inputHandle, out uint v);
         _originalConsoleMode = v;
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
index bc84db9fd6..e08eecf9b9 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
@@ -1,6 +1,7 @@
 using System.ComponentModel;
 using System.Runtime.InteropServices;
 using System.Text;
+using Microsoft.Extensions.Logging;
 using static Terminal.Gui.WindowsConsole;
 
 namespace Terminal.Gui;
@@ -58,6 +59,7 @@ private enum DesiredAccess : uint
 
     public WindowsOutput ()
     {
+        Logging.Logger.LogInformation ($"Creating {nameof (WindowsOutput)}");
         _screenBuffer = CreateConsoleScreenBuffer (
                                                    DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
                                                    ShareMode.FileShareRead | ShareMode.FileShareWrite,

From 26e47767c8478fd9ba7625e6036345d32d8bf88d Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 28 Dec 2024 19:10:50 +0000
Subject: [PATCH 101/198] warning fixes

---
 Terminal.Gui/Application/ApplicationImpl.cs   | 12 +++++-----
 Terminal.Gui/Application/IApplication.cs      | 24 +++++++++----------
 .../ConsoleDrivers/V2/ConsoleDriverFacade.cs  | 10 ++++----
 Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs   |  1 -
 .../ConsoleDrivers/V2/WindowsInput.cs         |  2 +-
 5 files changed, 24 insertions(+), 25 deletions(-)

diff --git a/Terminal.Gui/Application/ApplicationImpl.cs b/Terminal.Gui/Application/ApplicationImpl.cs
index ad2d2c49ee..6362de053b 100644
--- a/Terminal.Gui/Application/ApplicationImpl.cs
+++ b/Terminal.Gui/Application/ApplicationImpl.cs
@@ -99,14 +99,14 @@ public virtual T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDri
     ///     </para>
     ///     <para>
     ///         Calling <see cref="Run(Terminal.Gui.Toplevel,System.Func{System.Exception,bool})"/> is equivalent to calling
-    ///         <see cref="Begin(Toplevel)"/>, followed by <see cref="RunLoop(RunState)"/>, and then calling
-    ///         <see cref="End(RunState)"/>.
+    ///         <see cref="Application.Begin(Toplevel)"/>, followed by <see cref="Application.RunLoop(RunState)"/>, and then calling
+    ///         <see cref="Application.End(RunState)"/>.
     ///     </para>
     ///     <para>
     ///         Alternatively, to have a program control the main loop and process events manually, call
-    ///         <see cref="Begin(Toplevel)"/> to set things up manually and then repeatedly call
-    ///         <see cref="RunLoop(RunState)"/> with the wait parameter set to false. By doing this the
-    ///         <see cref="RunLoop(RunState)"/> method will only process any pending events, timers, idle handlers and then
+    ///         <see cref="Application.Begin(Toplevel)"/> to set things up manually and then repeatedly call
+    ///         <see cref="Application.RunLoop(RunState)"/> with the wait parameter set to false. By doing this the
+    ///         <see cref="Application.RunLoop(RunState)"/> method will only process any pending events, timers, idle handlers and then
     ///         return control immediately.
     ///     </para>
     ///     <para>When using <see cref="Run{T}"/> or
@@ -116,7 +116,7 @@ public virtual T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDri
     ///     <para>
     ///         RELEASE builds only: When <paramref name="errorHandler"/> is <see langword="null"/> any exceptions will be
     ///         rethrown. Otherwise, if <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/>
-    ///         returns <see langword="true"/> the <see cref="RunLoop(RunState)"/> will resume; otherwise this method will
+    ///         returns <see langword="true"/> the <see cref="Application.RunLoop(RunState)"/> will resume; otherwise this method will
     ///         exit.
     ///     </para>
     /// </remarks>
diff --git a/Terminal.Gui/Application/IApplication.cs b/Terminal.Gui/Application/IApplication.cs
index b46ba20079..1fc2b9c350 100644
--- a/Terminal.Gui/Application/IApplication.cs
+++ b/Terminal.Gui/Application/IApplication.cs
@@ -1,19 +1,19 @@
-using System;
-using System.Collections.Generic;
+#nullable enable
 using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
 
 namespace Terminal.Gui;
 
+/// <summary>
+/// Interface for instances that provide backing functionality to static
+/// gateway class <see cref="Application"/>.
+/// </summary>
 public interface IApplication
 {
     /// <summary>Initializes a new instance of <see cref="Terminal.Gui"/> Application.</summary>
     /// <para>Call this method once per instance (or after <see cref="Shutdown"/> has been called).</para>
     /// <para>
     ///     This function loads the right <see cref="IConsoleDriver"/> for the platform, Creates a <see cref="Toplevel"/>. and
-    ///     assigns it to <see cref="Top"/>
+    ///     assigns it to <see cref="Application.Top"/>
     /// </para>
     /// <para>
     ///     <see cref="Shutdown"/> must be called when the application is closing (typically after
@@ -99,14 +99,14 @@ public T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? dri
     ///     </para>
     ///     <para>
     ///         Calling <see cref="Run(Terminal.Gui.Toplevel,System.Func{System.Exception,bool})"/> is equivalent to calling
-    ///         <see cref="Begin(Toplevel)"/>, followed by <see cref="RunLoop(RunState)"/>, and then calling
-    ///         <see cref="End(RunState)"/>.
+    ///         <see cref="Application.Begin(Toplevel)"/>, followed by <see cref="Application.RunLoop(RunState)"/>, and then calling
+    ///         <see cref="Application.End(RunState)"/>.
     ///     </para>
     ///     <para>
     ///         Alternatively, to have a program control the main loop and process events manually, call
-    ///         <see cref="Begin(Toplevel)"/> to set things up manually and then repeatedly call
-    ///         <see cref="RunLoop(RunState)"/> with the wait parameter set to false. By doing this the
-    ///         <see cref="RunLoop(RunState)"/> method will only process any pending events, timers, idle handlers and then
+    ///         <see cref="Application.Begin(Toplevel)"/> to set things up manually and then repeatedly call
+    ///         <see cref="Application.RunLoop(RunState)"/> with the wait parameter set to false. By doing this the
+    ///         <see cref="Application.RunLoop(RunState)"/> method will only process any pending events, timers, idle handlers and then
     ///         return control immediately.
     ///     </para>
     ///     <para>When using <see cref="Run{T}"/> or
@@ -116,7 +116,7 @@ public T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? dri
     ///     <para>
     ///         RELEASE builds only: When <paramref name="errorHandler"/> is <see langword="null"/> any exceptions will be
     ///         rethrown. Otherwise, if <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/>
-    ///         returns <see langword="true"/> the <see cref="RunLoop(RunState)"/> will resume; otherwise this method will
+    ///         returns <see langword="true"/> the <see cref="Application.RunLoop(RunState)"/> will resume; otherwise this method will
     ///         exit.
     ///     </para>
     /// </remarks>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
index b776652243..bba097983c 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
@@ -1,5 +1,4 @@
-using System.Drawing;
-using System.Runtime.InteropServices;
+using System.Runtime.InteropServices;
 
 namespace Terminal.Gui;
 class ConsoleDriverFacade<T> : IConsoleDriver
@@ -10,6 +9,9 @@ class ConsoleDriverFacade<T> : IConsoleDriver
     private readonly AnsiRequestScheduler _ansiRequestScheduler;
     private CursorVisibility _lastCursor = CursorVisibility.Default;
 
+    /// <summary>The event fired when the terminal is resized.</summary>
+    public event EventHandler<SizeChangedEventArgs> SizeChanged;
+
     public ConsoleDriverFacade (IInputProcessor inputProcessor, IOutputBuffer outputBuffer, IConsoleOutput output, AnsiRequestScheduler ansiRequestScheduler,IWindowSizeMonitor windowSizeMonitor)
     {
         _inputProcessor = inputProcessor;
@@ -21,7 +23,7 @@ public ConsoleDriverFacade (IInputProcessor inputProcessor, IOutputBuffer output
         _inputProcessor.KeyUp += (s, e) => KeyUp?.Invoke (s, e);
         _inputProcessor.MouseEvent += (s, e) => MouseEvent?.Invoke (s, e);
 
-        windowSizeMonitor.SizeChanging += (_, e) => Application.OnSizeChanging (e);
+        windowSizeMonitor.SizeChanging += (_, e) => SizeChanged?.Invoke (this,e);
 
         CreateClipboard ();
     }
@@ -265,8 +267,6 @@ public bool GetCursorVisibility (out CursorVisibility current)
 
         return true;
     }
-    /// <summary>The event fired when the terminal is resized.</summary>
-    public event EventHandler<SizeChangedEventArgs> SizeChanged;
 
     /// <inheritdoc />
     public void Suspend ()
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
index 3dcc3401f9..cd705f1d7b 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
@@ -27,7 +27,6 @@ public void Write (string text)
     /// <inheritdoc />
     public void Write (IOutputBuffer buffer)
     {
-        bool updated = false;
         if ( Console.WindowHeight < 1
             || buffer.Contents.Length != buffer.Rows * buffer.Cols
             || buffer.Rows != Console.WindowHeight)
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
index 06ad70f30b..57c871e334 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
@@ -108,7 +108,7 @@ protected override IEnumerable<InputRecord> Read ()
             Marshal.FreeHGlobal (pRecord);
         }
     }
-    public void Dispose ()
+    public override void Dispose ()
     {
         SetConsoleMode (_inputHandle, _originalConsoleMode);
     }

From 5ca3c39915f0b252ac0b12b433ec5b178abdce64 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 28 Dec 2024 19:41:56 +0000
Subject: [PATCH 102/198] warnings and xml doc

---
 Terminal.Gui/Application/Application.cs       |  6 ++++-
 Terminal.Gui/Application/ApplicationImpl.cs   | 17 ++++++++++----
 Terminal.Gui/Application/IApplication.cs      | 23 +++++++++++++++++++
 Terminal.Gui/Application/MainLoop.cs          |  7 ++++--
 Terminal.Gui/Application/TimeoutEventArgs.cs  |  2 +-
 .../AnsiRequestScheduler.cs                   |  2 +-
 Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs |  3 +--
 .../ConsoleDrivers/V2/ConsoleDriverFacade.cs  |  3 +--
 8 files changed, 49 insertions(+), 14 deletions(-)

diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs
index c885bd41b8..cc0f84e2eb 100644
--- a/Terminal.Gui/Application/Application.cs
+++ b/Terminal.Gui/Application/Application.cs
@@ -226,6 +226,10 @@ internal static void ResetState (bool ignoreDisposed = false)
         SynchronizationContext.SetSynchronizationContext (null);
     }
 
-    // Only return true if the Current has changed.
+
+    /// <summary>
+    ///     Adds specified idle handler function to main iteration processing. The handler function will be called
+    ///     once per iteration of the main loop after other events have been handled.
+    /// </summary>
     public static void AddIdle (Func<bool> func) => ApplicationImpl.Instance.AddIdle (func);
 }
diff --git a/Terminal.Gui/Application/ApplicationImpl.cs b/Terminal.Gui/Application/ApplicationImpl.cs
index 6362de053b..af92ded621 100644
--- a/Terminal.Gui/Application/ApplicationImpl.cs
+++ b/Terminal.Gui/Application/ApplicationImpl.cs
@@ -3,22 +3,29 @@
 
 namespace Terminal.Gui;
 
+/// <summary>
+/// Original Terminal.Gui implementation of core <see cref="Application"/> methods.
+/// </summary>
 public class ApplicationImpl : IApplication
 {
     // Private static readonly Lazy instance of Application
-    private static Lazy<IApplication> lazyInstance = new (() => new ApplicationImpl ());
+    private static Lazy<IApplication> _lazyInstance = new (() => new ApplicationImpl ());
 
-    // Public static property to access the instance
-    public static IApplication Instance => lazyInstance.Value;
+    /// <summary>
+    /// Gets the currently configured backend implementation of <see cref="Application"/> gateway methods.
+    /// Change to your own implementation by using <see cref="ChangeInstance"/> (before init).
+    /// </summary>
+    public static IApplication Instance => _lazyInstance.Value;
 
     /// <summary>
     /// Change the singleton implementation, should not be called except before application
-    /// startup.
+    /// startup. This method lets you provide alternative implementations of core static gateway
+    /// methods of <see cref="Application"/>.
     /// </summary>
     /// <param name="newApplication"></param>
     public static void ChangeInstance (IApplication newApplication)
     {
-        lazyInstance = new Lazy<IApplication> (newApplication);
+        _lazyInstance = new Lazy<IApplication> (newApplication);
     }
 
     /// <inheritdoc/>
diff --git a/Terminal.Gui/Application/IApplication.cs b/Terminal.Gui/Application/IApplication.cs
index 1fc2b9c350..2383a12c44 100644
--- a/Terminal.Gui/Application/IApplication.cs
+++ b/Terminal.Gui/Application/IApplication.cs
@@ -152,7 +152,30 @@ public T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? dri
     void Invoke (Action action);
 
     bool IsLegacy { get; }
+
+    /// <summary>
+    ///     Adds specified idle handler function to main iteration processing. The handler function will be called
+    ///     once per iteration of the main loop after other events have been handled.
+    /// </summary>
     void AddIdle (Func<bool> func);
+
+    /// <summary>Adds a timeout to the application.</summary>
+    /// <remarks>
+    ///     When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be
+    ///     reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a
+    ///     token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
+    /// </remarks>
     object AddTimeout (TimeSpan time, Func<bool> callback);
+
+    /// <summary>Removes a previously scheduled timeout</summary>
+    /// <remarks>The token parameter is the value returned by <see cref="AddTimeout"/>.</remarks>
+    /// <returns>
+    /// <c>true</c>
+    /// if the timeout is successfully removed; otherwise,
+    /// <c>false</c>
+    /// .
+    /// This method also returns
+    /// <c>false</c>
+    /// if the timeout is not found.</returns>
     bool RemoveTimeout (object token);
 }
\ No newline at end of file
diff --git a/Terminal.Gui/Application/MainLoop.cs b/Terminal.Gui/Application/MainLoop.cs
index 7eb9197a74..0081cadd5b 100644
--- a/Terminal.Gui/Application/MainLoop.cs
+++ b/Terminal.Gui/Application/MainLoop.cs
@@ -39,6 +39,9 @@ internal interface IMainLoopDriver
 /// </remarks>
 public class MainLoop : IDisposable
 {
+    /// <summary>
+    /// Gets the class responsible for handling idles and timeouts
+    /// </summary>
     public ITimedEvents TimedEvents { get; } = new TimedEvents();
 
     /// <summary>Creates a new MainLoop.</summary>
@@ -77,13 +80,13 @@ public void Dispose ()
     ///     once per iteration of the main loop after other events have been handled.
     /// </summary>
     /// <remarks>
-    ///     <para>Remove an idle handler by calling <see cref="RemoveIdle(Func{bool})"/> with the token this method returns.</para>
+    ///     <para>Remove an idle handler by calling <see cref="TimedEvents.RemoveIdle(Func{bool})"/> with the token this method returns.</para>
     ///     <para>
     ///         If the <paramref name="idleHandler"/> returns  <see langword="false"/> it will be removed and not called
     ///         subsequently.
     ///     </para>
     /// </remarks>
-    /// <param name="idleHandler">Token that can be used to remove the idle handler with <see cref="RemoveIdle(Func{bool})"/> .</param>
+    /// <param name="idleHandler">Token that can be used to remove the idle handler with <see cref="TimedEvents.RemoveIdle(Func{bool})"/> .</param>
     // QUESTION: Why are we re-inventing the event wheel here?
     // PERF: This is heavy.
     // CONCURRENCY: Race conditions exist here.
diff --git a/Terminal.Gui/Application/TimeoutEventArgs.cs b/Terminal.Gui/Application/TimeoutEventArgs.cs
index 6a2ca70674..2e01228c19 100644
--- a/Terminal.Gui/Application/TimeoutEventArgs.cs
+++ b/Terminal.Gui/Application/TimeoutEventArgs.cs
@@ -1,6 +1,6 @@
 namespace Terminal.Gui;
 
-/// <summary><see cref="EventArgs"/> for timeout events (e.g. <see cref="MainLoop.TimeoutAdded"/>)</summary>
+/// <summary><see cref="EventArgs"/> for timeout events (e.g. <see cref="TimedEvents.TimeoutAdded"/>)</summary>
 public class TimeoutEventArgs : EventArgs
 {
     /// <summary>Creates a new instance of the <see cref="TimeoutEventArgs"/> class.</summary>
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiRequestScheduler.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiRequestScheduler.cs
index d2a7841c44..6ef0bed588 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiRequestScheduler.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiRequestScheduler.cs
@@ -68,7 +68,7 @@ public AnsiRequestScheduler (IAnsiResponseParser parser, Func<DateTime>? now = n
 
     /// <summary>
     ///     Sends the <paramref name="request"/> immediately or queues it if there is already
-    ///     an outstanding request for the given <see cref="AnsiEscapeSequenceRequest.Terminator"/>.
+    ///     an outstanding request for the given <see cref="AnsiEscapeSequence.Terminator"/>.
     /// </summary>
     /// <param name="request"></param>
     /// <returns><see langword="true"/> if request was sent immediately. <see langword="false"/> if it was queued.</returns>
diff --git a/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
index 7bc1d3638e..a661b84393 100644
--- a/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/IConsoleDriver.cs
@@ -34,8 +34,7 @@ public interface IConsoleDriver
 
     // BUGBUG: This should not be publicly settable.
     /// <summary>
-    ///     Gets or sets the contents of the application output. The driver outputs this buffer to the terminal when
-    ///     <see cref="UpdateScreen"/> is called.
+    ///     Gets or sets the contents of the application output. The driver outputs this buffer to the terminal.
     ///     <remarks>The format of the array is rows, columns. The first index is the row, the second index is the column.</remarks>
     /// </summary>
     Cell [,]? Contents { get; set; }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
index bba097983c..8cdbbbaded 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
@@ -80,8 +80,7 @@ public int Cols
     }
 
     /// <summary>
-    ///     The contents of the application output. The driver outputs this buffer to the terminal when
-    ///     <see cref="UpdateScreen"/> is called.
+    ///     The contents of the application output. The driver outputs this buffer to the terminal.
     ///     <remarks>The format of the array is rows, columns. The first index is the row, the second index is the column.</remarks>
     /// </summary>
     public Cell [,] Contents

From 3aae34638b2bd6f6748d99bb206c107fa4c7b112 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 28 Dec 2024 20:00:58 +0000
Subject: [PATCH 103/198] warnings and xmldoc

---
 Terminal.Gui/Application/ApplicationImpl.cs       | 3 ++-
 Terminal.Gui/Application/IApplication.cs          | 4 ++++
 Terminal.Gui/Application/TimedEvents.cs           | 5 +++--
 Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs   | 8 ++++++--
 Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs | 1 +
 Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs       | 3 ++-
 Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs   | 3 ++-
 Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs  | 3 ++-
 Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs    | 3 ++-
 9 files changed, 24 insertions(+), 9 deletions(-)

diff --git a/Terminal.Gui/Application/ApplicationImpl.cs b/Terminal.Gui/Application/ApplicationImpl.cs
index af92ded621..85525feccc 100644
--- a/Terminal.Gui/Application/ApplicationImpl.cs
+++ b/Terminal.Gui/Application/ApplicationImpl.cs
@@ -1,4 +1,5 @@
-using System.Diagnostics;
+#nullable enable
+using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 
 namespace Terminal.Gui;
diff --git a/Terminal.Gui/Application/IApplication.cs b/Terminal.Gui/Application/IApplication.cs
index 2383a12c44..4090c607cf 100644
--- a/Terminal.Gui/Application/IApplication.cs
+++ b/Terminal.Gui/Application/IApplication.cs
@@ -151,6 +151,10 @@ public T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? dri
     /// <param name="action">the action to be invoked on the main processing thread.</param>
     void Invoke (Action action);
 
+    /// <summary>
+    /// <see langword="true"/> if implementation is 'old'. <see langword="false"/> if implementation
+    /// is cutting edge.
+    /// </summary>
     bool IsLegacy { get; }
 
     /// <summary>
diff --git a/Terminal.Gui/Application/TimedEvents.cs b/Terminal.Gui/Application/TimedEvents.cs
index 7e304dd341..38815488fa 100644
--- a/Terminal.Gui/Application/TimedEvents.cs
+++ b/Terminal.Gui/Application/TimedEvents.cs
@@ -1,4 +1,5 @@
-using System.Collections.ObjectModel;
+#nullable enable
+using System.Collections.ObjectModel;
 
 namespace Terminal.Gui;
 
@@ -102,7 +103,7 @@ private void RunIdle ()
         }
     }
 
-
+    /// <inheritdoc/>
     public void LockAndRunTimers ()
     {
         lock (_timeoutsLockToken)
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index 8d6d53720b..c65d6b4653 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -6,6 +6,10 @@
 namespace Terminal.Gui;
 
 
+/// <summary>
+/// Implementation of <see cref="IApplication"/> that boots the new 'v2'
+/// main loop architecture.
+/// </summary>
 public class ApplicationV2 : ApplicationImpl
 {
     private readonly Func<INetInput> _netInputFactory;
@@ -117,7 +121,7 @@ private void CreateNetSubcomponents ()
     }
 
     /// <inheritdoc />
-    public override T Run<T> (Func<Exception, bool> errorHandler = null, IConsoleDriver driver = null)
+    public override T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
     {
         var top = new T ();
 
@@ -127,7 +131,7 @@ public override T Run<T> (Func<Exception, bool> errorHandler = null, IConsoleDri
     }
 
     /// <inheritdoc />
-    public override void Run (Toplevel view, Func<Exception, bool> errorHandler = null)
+    public override void Run (Toplevel view, Func<Exception, bool>? errorHandler = null)
     {
         Logging.Logger.LogInformation ($"Run '{view}'");
         ArgumentNullException.ThrowIfNull (view);
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
index e1dcb357be..bf2aaec08c 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
@@ -1,4 +1,5 @@
 
+#nullable enable
 namespace Terminal.Gui;
 
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
index 28636be078..35a731797a 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
@@ -1,4 +1,5 @@
-using System.Collections.Concurrent;
+#nullable enable
+using System.Collections.Concurrent;
 using static Unix.Terminal.Curses;
 
 namespace Terminal.Gui;
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
index d1034e339b..b798bf0965 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
@@ -1,4 +1,5 @@
-namespace Terminal.Gui;
+#nullable enable
+namespace Terminal.Gui;
 
 public interface IOutputBuffer
 {
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index 8df1dd9c32..5e65a42ed6 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -1,4 +1,5 @@
-using System.Collections.Concurrent;
+#nullable enable
+using System.Collections.Concurrent;
 
 namespace Terminal.Gui;
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
index 429437d7e4..2391055de6 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
@@ -1,4 +1,5 @@
-using System.Diagnostics;
+#nullable enable
+using System.Diagnostics;
 
 namespace Terminal.Gui;
 

From 0bfcdca000ac775968d8c26ec00a1a9bb52e638c Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 28 Dec 2024 20:13:46 +0000
Subject: [PATCH 104/198] Code Cleanup

---
 Terminal.Gui/Application/Application.Run.cs   |   6 +-
 Terminal.Gui/Application/IApplication.cs      |   6 +-
 Terminal.Gui/Application/ITimedEvents.cs      |  66 +++++++++
 Terminal.Gui/Application/MainLoop.cs          |   2 +-
 Terminal.Gui/Application/TimedEvents.cs       |  70 +--------
 .../AnsiResponseParser/AnsiKeyboardParser.cs  |   8 +-
 .../AnsiResponseParser/AnsiMouseParser.cs     |   5 +-
 .../AnsiRequestScheduler.cs                   |   2 +-
 .../AnsiResponseParser/AnsiResponseParser.cs  |  49 +++----
 .../V2/AlreadyResolvedException.cs            |   4 +-
 .../ConsoleDrivers/V2/ApplicationV2.cs        | 109 +++++++-------
 .../ConsoleDrivers/V2/ConsoleDriverFacade.cs  | 134 ++++++++++--------
 .../ConsoleDrivers/V2/ConsoleInput.cs         |  44 +++---
 .../ConsoleDrivers/V2/IConsoleInput.cs        |  11 +-
 .../ConsoleDrivers/V2/IConsoleOutput.cs       |   3 +-
 .../ConsoleDrivers/V2/IInputProcessor.cs      |  11 +-
 Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs   |   6 +-
 Terminal.Gui/ConsoleDrivers/V2/INetInput.cs   |   2 +-
 .../ConsoleDrivers/V2/IOutputBuffer.cs        |  22 +--
 Terminal.Gui/ConsoleDrivers/V2/IViewFinder.cs |  16 +--
 .../ConsoleDrivers/V2/InputProcessor.cs       |  25 ++--
 Terminal.Gui/ConsoleDrivers/V2/Logging.cs     |  22 +--
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    |  44 +++---
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  |  65 +++++----
 .../ConsoleDrivers/V2/MouseButtonState.cs     |  11 +-
 .../ConsoleDrivers/V2/MouseInterpreter.cs     |  32 ++---
 Terminal.Gui/ConsoleDrivers/V2/NetInput.cs    |  23 +--
 .../ConsoleDrivers/V2/NetInputProcessor.cs    |  11 +-
 Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs   |  44 +++---
 .../ConsoleDrivers/V2/OutputBuffer.cs         |  30 ++--
 Terminal.Gui/ConsoleDrivers/V2/V2.cd          |  88 +++++-------
 .../ConsoleDrivers/V2/WindowSizeMonitor.cs    |  11 +-
 .../ConsoleDrivers/V2/WindowsInput.cs         |  13 +-
 .../V2/WindowsInputProcessor.cs               |  17 +--
 .../ConsoleDrivers/V2/WindowsOutput.cs        |  45 +++---
 35 files changed, 510 insertions(+), 547 deletions(-)
 create mode 100644 Terminal.Gui/Application/ITimedEvents.cs

diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs
index 431036fb16..a70c372802 100644
--- a/Terminal.Gui/Application/Application.Run.cs
+++ b/Terminal.Gui/Application/Application.Run.cs
@@ -386,12 +386,12 @@ public static void Run (Toplevel view, Func<Exception, bool>? errorHandler = nul
     /// <summary>Removes a previously scheduled timeout</summary>
     /// <remarks>The token parameter is the value returned by <see cref="AddTimeout"/>.</remarks>
     /// Returns
-    /// <c>true</c>
+    /// <see langword="true"/>
     /// if the timeout is successfully removed; otherwise,
-    /// <c>false</c>
+    /// <see langword="false"/>
     /// .
     /// This method also returns
-    /// <c>false</c>
+    /// <see langword="false"/>
     /// if the timeout is not found.
     public static bool RemoveTimeout (object token) => ApplicationImpl.Instance.RemoveTimeout (token);
 
diff --git a/Terminal.Gui/Application/IApplication.cs b/Terminal.Gui/Application/IApplication.cs
index 4090c607cf..f31442b5fc 100644
--- a/Terminal.Gui/Application/IApplication.cs
+++ b/Terminal.Gui/Application/IApplication.cs
@@ -174,12 +174,12 @@ public T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? dri
     /// <summary>Removes a previously scheduled timeout</summary>
     /// <remarks>The token parameter is the value returned by <see cref="AddTimeout"/>.</remarks>
     /// <returns>
-    /// <c>true</c>
+    /// <see langword="true"/>
     /// if the timeout is successfully removed; otherwise,
-    /// <c>false</c>
+    /// <see langword="false"/>
     /// .
     /// This method also returns
-    /// <c>false</c>
+    /// <see langword="false"/>
     /// if the timeout is not found.</returns>
     bool RemoveTimeout (object token);
 }
\ No newline at end of file
diff --git a/Terminal.Gui/Application/ITimedEvents.cs b/Terminal.Gui/Application/ITimedEvents.cs
new file mode 100644
index 0000000000..8d067af117
--- /dev/null
+++ b/Terminal.Gui/Application/ITimedEvents.cs
@@ -0,0 +1,66 @@
+#nullable enable
+using System.Collections.ObjectModel;
+
+namespace Terminal.Gui;
+
+public interface ITimedEvents
+{
+    void AddIdle (Func<bool> idleHandler);
+    void LockAndRunIdles ();
+    void LockAndRunTimers ();
+
+    /// <summary>
+    ///     Called from <see cref="IMainLoopDriver.EventsPending"/> to check if there are any outstanding timers or idle
+    ///     handlers.
+    /// </summary>
+    /// <param name="waitTimeout">
+    ///     Returns the number of milliseconds remaining in the current timer (if any). Will be -1 if
+    ///     there are no active timers.
+    /// </param>
+    /// <returns><see langword="true"/> if there is a timer or idle handler active.</returns>
+    bool CheckTimersAndIdleHandlers (out int waitTimeout);
+
+    /// <summary>Adds a timeout to the application.</summary>
+    /// <remarks>
+    ///     When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be
+    ///     reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a
+    ///     token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
+    /// </remarks>
+    object AddTimeout (TimeSpan time, Func<bool> callback);
+
+    /// <summary>Removes a previously scheduled timeout</summary>
+    /// <remarks>The token parameter is the value returned by AddTimeout.</remarks>
+    /// <returns>
+    /// Returns
+    /// <see langword="true"/>
+    /// if the timeout is successfully removed; otherwise,
+    /// <see langword="false"/>
+    /// .
+    /// This method also returns
+    /// <see langword="false"/>
+    /// if the timeout is not found.
+    /// </returns>
+    bool RemoveTimeout (object token);
+
+    ReadOnlyCollection<Func<bool>> IdleHandlers { get;}
+
+    SortedList<long, Timeout> Timeouts { get; }
+
+
+    /// <summary>Removes an idle handler added with <see cref="AddIdle(Func{bool})"/> from processing.</summary>
+    /// <returns>
+    /// <see langword="true"/>
+    /// if the idle handler is successfully removed; otherwise,
+    /// <see langword="false"/>
+    /// .
+    /// This method also returns
+    /// <see langword="false"/>
+    /// if the idle handler is not found.</returns>
+    bool RemoveIdle (Func<bool> fnTrue);
+
+    /// <summary>
+    ///     Invoked when a new timeout is added. To be used in the case when
+    ///     <see cref="Application.EndAfterFirstIteration"/> is <see langword="true"/>.
+    /// </summary>
+    event EventHandler<TimeoutEventArgs>? TimeoutAdded;
+}
diff --git a/Terminal.Gui/Application/MainLoop.cs b/Terminal.Gui/Application/MainLoop.cs
index 0081cadd5b..3b6cf3b9d3 100644
--- a/Terminal.Gui/Application/MainLoop.cs
+++ b/Terminal.Gui/Application/MainLoop.cs
@@ -14,7 +14,7 @@ namespace Terminal.Gui;
 internal interface IMainLoopDriver
 {
     /// <summary>Must report whether there are any events pending, or even block waiting for events.</summary>
-    /// <returns><c>true</c>, if there were pending events, <c>false</c> otherwise.</returns>
+    /// <returns><see langword="true"/>, if there were pending events, <see langword="false"/> otherwise.</returns>
     bool EventsPending ();
 
     /// <summary>The iteration function.</summary>
diff --git a/Terminal.Gui/Application/TimedEvents.cs b/Terminal.Gui/Application/TimedEvents.cs
index 38815488fa..fec19e4ad0 100644
--- a/Terminal.Gui/Application/TimedEvents.cs
+++ b/Terminal.Gui/Application/TimedEvents.cs
@@ -41,10 +41,7 @@ public void AddIdle (Func<bool> idleHandler)
         }
     }
 
-    /// <summary>
-    ///     Invoked when a new timeout is added. To be used in the case when
-    ///     <see cref="Application.EndAfterFirstIteration"/> is <see langword="true"/>.
-    /// </summary>
+    /// <inheritdoc/>
     public event EventHandler<TimeoutEventArgs>? TimeoutAdded;
 
 
@@ -164,17 +161,7 @@ private void RunTimers ()
         }
     }
 
-
-    /// <summary>Removes an idle handler added with <see cref="AddIdle(Func{bool})"/> from processing.</summary>
-    /// <param name="token">A token returned by <see cref="AddIdle(Func{bool})"/></param>
-    /// Returns
-    /// <c>true</c>
-    /// if the idle handler is successfully removed; otherwise,
-    /// <c>false</c>
-    /// .
-    /// This method also returns
-    /// <c>false</c>
-    /// if the idle handler is not found.
+    /// <inheritdoc/>
     public bool RemoveIdle (Func<bool> token)
     {
         lock (_idleHandlersLock)
@@ -186,12 +173,12 @@ public bool RemoveIdle (Func<bool> token)
     /// <summary>Removes a previously scheduled timeout</summary>
     /// <remarks>The token parameter is the value returned by AddTimeout.</remarks>
     /// Returns
-    /// <c>true</c>
+    /// <see langword="true"/>
     /// if the timeout is successfully removed; otherwise,
-    /// <c>false</c>
+    /// <see langword="false"/>
     /// .
     /// This method also returns
-    /// <c>false</c>
+    /// <see langword="false"/>
     /// if the timeout is not found.
     public bool RemoveTimeout (object token)
     {
@@ -227,15 +214,7 @@ public object AddTimeout (TimeSpan time, Func<bool> callback)
         return timeout;
     }
 
-    /// <summary>
-    ///     Called from <see cref="IMainLoopDriver.EventsPending"/> to check if there are any outstanding timers or idle
-    ///     handlers.
-    /// </summary>
-    /// <param name="waitTimeout">
-    ///     Returns the number of milliseconds remaining in the current timer (if any). Will be -1 if
-    ///     there are no active timers.
-    /// </param>
-    /// <returns><see langword="true"/> if there is a timer or idle handler active.</returns>
+    /// <inheritdoc/>
     public bool CheckTimersAndIdleHandlers (out int waitTimeout)
     {
         long now = DateTime.UtcNow.Ticks;
@@ -271,39 +250,4 @@ public bool CheckTimersAndIdleHandlers (out int waitTimeout)
             return _idleHandlers.Count > 0;
         }
     }
-}
-
-public interface ITimedEvents
-{
-    void AddIdle (Func<bool> idleHandler);
-    void LockAndRunIdles ();
-    void LockAndRunTimers ();
-    bool CheckTimersAndIdleHandlers (out int waitTimeout);
-
-    /// <summary>Adds a timeout to the application.</summary>
-    /// <remarks>
-    ///     When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be
-    ///     reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a
-    ///     token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
-    /// </remarks>
-    object AddTimeout (TimeSpan time, Func<bool> callback);
-
-    /// <summary>Removes a previously scheduled timeout</summary>
-    /// <remarks>The token parameter is the value returned by AddTimeout.</remarks>
-    /// Returns
-    /// <c>true</c>
-    /// if the timeout is successfully removed; otherwise,
-    /// <c>false</c>
-    /// .
-    /// This method also returns
-    /// <c>false</c>
-    /// if the timeout is not found.
-    bool RemoveTimeout (object token);
-
-    ReadOnlyCollection<Func<bool>> IdleHandlers { get;}
-
-    SortedList<long, Timeout> Timeouts { get; }
-    bool RemoveIdle (Func<bool> fnTrue);
-
-    event EventHandler<TimeoutEventArgs>? TimeoutAdded;
-}
+}
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
index edb97d516e..901cc5c4a1 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
@@ -28,17 +28,13 @@ public Key ProcessKeyboardInput (string input)
                        'B' => Key.CursorDown,
                        'C' => Key.CursorRight,
                        'D' => Key.CursorDown,
-                       _ => default(Key)
+                       _ => default (Key)
                    };
-
         }
 
         // It's an unrecognized keyboard event
         return null;
     }
 
-    public bool IsKeyboard (string cur)
-    {
-        return _arrowKeyPattern.IsMatch (cur);
-    }
+    public bool IsKeyboard (string cur) { return _arrowKeyPattern.IsMatch (cur); }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs
index cb95740484..ee3d3a3624 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs
@@ -1,5 +1,4 @@
 #nullable enable
-using System.Buffers.Text;
 using System.Text.RegularExpressions;
 
 namespace Terminal.Gui;
@@ -14,7 +13,7 @@ public class AnsiMouseParser
     private readonly Regex _mouseEventPattern = new (@"\u001b\[<(\d+);(\d+);(\d+)(M|m)", RegexOptions.Compiled);
 
     /// <summary>
-    /// Returns true if it is a mouse event
+    ///     Returns true if it is a mouse event
     /// </summary>
     /// <param name="cur"></param>
     /// <returns></returns>
@@ -47,7 +46,7 @@ public bool IsMouse (string cur)
             int y = int.Parse (match.Groups [3].Value) - 1;
             char terminator = match.Groups [4].Value.Single ();
 
-            return new()
+            return new ()
             {
                 Position = new (x, y),
                 Flags = GetFlags (buttonCode, terminator)
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiRequestScheduler.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiRequestScheduler.cs
index 6ef0bed588..aec693de55 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiRequestScheduler.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiRequestScheduler.cs
@@ -213,4 +213,4 @@ private bool ShouldThrottle (AnsiEscapeSequenceRequest r)
 
         return false;
     }
-}
\ No newline at end of file
+}
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
index 64938a3f32..2a2cef7d5f 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
@@ -6,30 +6,31 @@ namespace Terminal.Gui;
 
 internal abstract class AnsiResponseParserBase : IAnsiResponseParser
 {
-    private readonly AnsiMouseParser _mouseParser = new  ();
+    private readonly AnsiMouseParser _mouseParser = new ();
     private readonly AnsiKeyboardParser _keyboardParser = new ();
-    protected object _lockExpectedResponses = new();
+    protected object _lockExpectedResponses = new ();
 
     protected object _lockState = new ();
 
     /// <summary>
-    /// Event raised when mouse events are detected - requires setting <see cref="HandleMouse"/> to true
+    ///     Event raised when mouse events are detected - requires setting <see cref="HandleMouse"/> to true
     /// </summary>
     public event EventHandler<MouseEventArgs> Mouse;
 
     /// <summary>
-    /// Event raised when keyboard event is detected (e.g. cursors) - requires setting <see cref="HandleKeyboard"/>
+    ///     Event raised when keyboard event is detected (e.g. cursors) - requires setting <see cref="HandleKeyboard"/>
     /// </summary>
     public event Action<object, Key> Keyboard;
 
     /// <summary>
-    /// True to explicitly handle mouse escape sequences by passing them to <see cref="Mouse"/> event.
-    /// Defaults to <see langword="false"/>
+    ///     True to explicitly handle mouse escape sequences by passing them to <see cref="Mouse"/> event.
+    ///     Defaults to <see langword="false"/>
     /// </summary>
     public bool HandleMouse { get; set; } = false;
 
     /// <summary>
-    /// True to explicitly handle keyboard escape sequences (such as cursor keys) by passing them to <see cref="Keyboard"/> event
+    ///     True to explicitly handle keyboard escape sequences (such as cursor keys) by passing them to <see cref="Keyboard"/>
+    ///     event
     /// </summary>
     public bool HandleKeyboard { get; set; } = false;
 
@@ -210,12 +211,13 @@ protected bool ShouldReleaseHeldContent ()
         {
             string cur = _heldContent.HeldToString ();
 
-            if (HandleMouse && IsMouse(cur))
+            if (HandleMouse && IsMouse (cur))
             {
-                RaiseMouseEvent(cur);
-                ResetState();
+                RaiseMouseEvent (cur);
+                ResetState ();
 
                 Logging.Logger.LogTrace ($"AnsiResponseParser handled as mouse '{cur}'");
+
                 return false;
             }
 
@@ -225,6 +227,7 @@ protected bool ShouldReleaseHeldContent ()
                 ResetState ();
 
                 Logging.Logger.LogTrace ($"AnsiResponseParser handled as keyboard '{cur}'");
+
                 return false;
             }
 
@@ -291,29 +294,23 @@ protected bool ShouldReleaseHeldContent ()
 
     private void RaiseMouseEvent (string cur)
     {
-        var ev = _mouseParser.ProcessMouseInput (cur);
+        MouseEventArgs? ev = _mouseParser.ProcessMouseInput (cur);
 
         if (ev != null)
         {
-            Mouse?.Invoke (this,ev);
+            Mouse?.Invoke (this, ev);
         }
     }
 
-    private bool IsMouse (string cur)
-    {
-        return _mouseParser.IsMouse (cur);
-    }
+    private bool IsMouse (string cur) { return _mouseParser.IsMouse (cur); }
+
     private void RaiseKeyboardEvent (string cur)
     {
-        var k = _keyboardParser.ProcessKeyboardInput (cur);
+        Key? k = _keyboardParser.ProcessKeyboardInput (cur);
         Keyboard?.Invoke (this, k);
     }
-    private bool IsKeyboard (string cur)
-    {
-        return _keyboardParser.IsKeyboard (cur);
-    }
-
 
+    private bool IsKeyboard (string cur) { return _keyboardParser.IsKeyboard (cur); }
 
     /// <summary>
     ///     <para>
@@ -419,7 +416,6 @@ public AnsiResponseParser () : base (new GenericHeld<T> ()) { }
     /// <inheritdoc cref="AnsiResponseParser.UnknownResponseHandler"/>
     public Func<IEnumerable<Tuple<char, T>>, bool> UnexpectedResponseHandler { get; set; } = _ => false;
 
-
     public IEnumerable<Tuple<char, T>> ProcessInput (params Tuple<char, T> [] input)
     {
         List<Tuple<char, T>> output = new ();
@@ -427,7 +423,7 @@ public IEnumerable<Tuple<char, T>> ProcessInput (params Tuple<char, T> [] input)
         ProcessInputBase (
                           i => input [i].Item1,
                           i => input [i],
-                          c => AppendOutput(output,c),
+                          c => AppendOutput (output, c),
                           input.Length);
 
         return output;
@@ -435,7 +431,7 @@ public IEnumerable<Tuple<char, T>> ProcessInput (params Tuple<char, T> [] input)
 
     private void AppendOutput (List<Tuple<char, T>> output, object c)
     {
-        var tuple = (Tuple<char, T>)c;
+        Tuple<char, T> tuple = (Tuple<char, T>)c;
 
         Logging.Logger.LogTrace ($"AnsiResponseParser releasing '{tuple.Item1}'");
         output.Add (tuple);
@@ -481,7 +477,6 @@ public void ExpectResponseT (string terminator, Action<IEnumerable<Tuple<char, T
 
     /// <inheritdoc/>
     protected override bool ShouldSwallowUnexpectedResponse () { return UnexpectedResponseHandler.Invoke (HeldToEnumerable ()); }
-
 }
 
 internal class AnsiResponseParser () : AnsiResponseParserBase (new StringHeld ())
@@ -506,7 +501,7 @@ public string ProcessInput (string input)
         ProcessInputBase (
                           i => input [i],
                           i => input [i], // For string there is no T so object is same as char
-                          c => AppendOutput(output,(char)c),
+                          c => AppendOutput (output, (char)c),
                           input.Length);
 
         return output.ToString ();
diff --git a/Terminal.Gui/ConsoleDrivers/V2/AlreadyResolvedException.cs b/Terminal.Gui/ConsoleDrivers/V2/AlreadyResolvedException.cs
index b3974a595c..68292cb426 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/AlreadyResolvedException.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/AlreadyResolvedException.cs
@@ -3,7 +3,5 @@ namespace Terminal.Gui;
 
 internal class AlreadyResolvedException : Exception
 {
-    public AlreadyResolvedException ():base("MouseButtonSequence already resolved")
-    {
-    }
+    public AlreadyResolvedException () : base ("MouseButtonSequence already resolved") { }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index c65d6b4653..af6020ea4c 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -1,14 +1,12 @@
 #nullable enable
 using System.Collections.Concurrent;
 using Microsoft.Extensions.Logging;
-using System.Runtime.ConstrainedExecution;
 
 namespace Terminal.Gui;
 
-
 /// <summary>
-/// Implementation of <see cref="IApplication"/> that boots the new 'v2'
-/// main loop architecture.
+///     Implementation of <see cref="IApplication"/> that boots the new 'v2'
+///     main loop architecture.
 /// </summary>
 public class ApplicationV2 : ApplicationImpl
 {
@@ -19,20 +17,21 @@ public class ApplicationV2 : ApplicationImpl
     private IMainLoopCoordinator _coordinator;
     private string? _driverName;
     public ITimedEvents TimedEvents { get; } = new TimedEvents ();
+
     public ApplicationV2 () : this (
-                                    ()=>new NetInput (),
-                                    ()=>new NetOutput (),
-                                    ()=>new WindowsInput (),
-                                    ()=>new WindowsOutput ()
-                                    )
-    {
-    }
+                                    () => new NetInput (),
+                                    () => new NetOutput (),
+                                    () => new WindowsInput (),
+                                    () => new WindowsOutput ()
+                                   )
+    { }
+
     internal ApplicationV2 (
         Func<INetInput> netInputFactory,
         Func<IConsoleOutput> netOutputFactory,
         Func<IWindowsInput> winInputFactory,
         Func<IConsoleOutput> winOutputFactory
-        )
+    )
     {
         _netInputFactory = netInputFactory;
         _netOutputFactory = netOutputFactory;
@@ -41,13 +40,14 @@ Func<IConsoleOutput> winOutputFactory
         IsLegacy = false;
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public override void Init (IConsoleDriver? driver = null, string? driverName = null)
     {
         if (!string.IsNullOrWhiteSpace (driverName))
         {
             _driverName = driverName;
         }
+
         Application.Navigation = new ();
 
         Application.AddKeyBindings ();
@@ -63,23 +63,20 @@ public override void Init (IConsoleDriver? driver = null, string? driverName = n
 
     private void CreateDriver (string? driverName)
     {
-
         PlatformID p = Environment.OSVersion.Platform;
 
-        var definetlyWin = driverName?.Contains ("win") ?? false;
-        var definetlyNet = driverName?.Contains ("net") ?? false;
+        bool definetlyWin = driverName?.Contains ("win") ?? false;
+        bool definetlyNet = driverName?.Contains ("net") ?? false;
 
         if (definetlyWin)
         {
             CreateWindowsSubcomponents ();
-
         }
         else if (definetlyNet)
         {
             CreateNetSubcomponents ();
         }
-        else
-        if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
+        else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
         {
             CreateWindowsSubcomponents ();
         }
@@ -88,31 +85,35 @@ private void CreateDriver (string? driverName)
             CreateNetSubcomponents ();
         }
 
-        _coordinator.StartAsync().Wait();
+        _coordinator.StartAsync ().Wait ();
 
         if (Application.Driver == null)
         {
-            throw new Exception ("Application.Driver was null even after booting MainLoopCoordinator");
+            throw new ("Application.Driver was null even after booting MainLoopCoordinator");
         }
     }
 
-
     private void CreateWindowsSubcomponents ()
     {
-        var inputBuffer = new ConcurrentQueue<WindowsConsole.InputRecord> ();
-        var loop = new MainLoop<WindowsConsole.InputRecord> ();
-        _coordinator = new MainLoopCoordinator<WindowsConsole.InputRecord> (TimedEvents,
+        ConcurrentQueue<WindowsConsole.InputRecord> inputBuffer = new ConcurrentQueue<WindowsConsole.InputRecord> ();
+        MainLoop<WindowsConsole.InputRecord> loop = new MainLoop<WindowsConsole.InputRecord> ();
+
+        _coordinator = new MainLoopCoordinator<WindowsConsole.InputRecord> (
+                                                                            TimedEvents,
                                                                             _winInputFactory,
                                                                             inputBuffer,
                                                                             new WindowsInputProcessor (inputBuffer),
                                                                             _winOutputFactory,
                                                                             loop);
     }
+
     private void CreateNetSubcomponents ()
     {
-        var inputBuffer = new ConcurrentQueue<ConsoleKeyInfo> ();
-        var loop = new MainLoop<ConsoleKeyInfo> ();
-        _coordinator = new MainLoopCoordinator<ConsoleKeyInfo> (TimedEvents,
+        ConcurrentQueue<ConsoleKeyInfo> inputBuffer = new ConcurrentQueue<ConsoleKeyInfo> ();
+        MainLoop<ConsoleKeyInfo> loop = new MainLoop<ConsoleKeyInfo> ();
+
+        _coordinator = new MainLoopCoordinator<ConsoleKeyInfo> (
+                                                                TimedEvents,
                                                                 _netInputFactory,
                                                                 inputBuffer,
                                                                 new NetInputProcessor (inputBuffer),
@@ -120,7 +121,7 @@ private void CreateNetSubcomponents ()
                                                                 loop);
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public override T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
     {
         var top = new T ();
@@ -130,7 +131,7 @@ public override T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDr
         return top;
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public override void Run (Toplevel view, Func<Exception, bool>? errorHandler = null)
     {
         Logging.Logger.LogInformation ($"Run '{view}'");
@@ -138,7 +139,7 @@ public override void Run (Toplevel view, Func<Exception, bool>? errorHandler = n
 
         if (!Application.Initialized)
         {
-            throw new Exception ("App not Initialized");
+            throw new ("App not Initialized");
         }
 
         Application.Top = view;
@@ -146,13 +147,13 @@ public override void Run (Toplevel view, Func<Exception, bool>? errorHandler = n
         Application.Begin (view);
 
         // TODO : how to know when we are done?
-        while (Application.TopLevels.TryPeek (out var found) && found == view)
+        while (Application.TopLevels.TryPeek (out Toplevel? found) && found == view)
         {
             _coordinator.RunIteration ();
         }
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public override void Shutdown ()
     {
         _coordinator.Stop ();
@@ -160,7 +161,7 @@ public override void Shutdown ()
         Application.Driver = null;
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public override void RequestStop (Toplevel top)
     {
         Logging.Logger.LogInformation ($"RequestStop '{top}'");
@@ -168,7 +169,7 @@ public override void RequestStop (Toplevel top)
         // TODO: This definition of stop seems sketchy
         Application.TopLevels.TryPop (out _);
 
-        if(Application.TopLevels.Count>0)
+        if (Application.TopLevels.Count > 0)
         {
             Application.Top = Application.TopLevels.Peek ();
         }
@@ -178,33 +179,25 @@ public override void RequestStop (Toplevel top)
         }
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public override void Invoke (Action action)
     {
-        TimedEvents.AddIdle (() =>
-                               {
-                                   action ();
-
-                                   return false;
-                               }
-                              );
+        TimedEvents.AddIdle (
+                             () =>
+                             {
+                                 action ();
+
+                                 return false;
+                             }
+                            );
     }
 
-    /// <inheritdoc />
-    public override void AddIdle (Func<bool> func)
-    {
-        TimedEvents.AddIdle (func);
-    }
+    /// <inheritdoc/>
+    public override void AddIdle (Func<bool> func) { TimedEvents.AddIdle (func); }
 
-    /// <inheritdoc />
-    public override object AddTimeout (TimeSpan time, Func<bool> callback)
-    {
-        return TimedEvents.AddTimeout(time,callback);
-    }
+    /// <inheritdoc/>
+    public override object AddTimeout (TimeSpan time, Func<bool> callback) { return TimedEvents.AddTimeout (time, callback); }
 
-    /// <inheritdoc />
-    public override bool RemoveTimeout (object token)
-    {
-        return TimedEvents.RemoveTimeout (token);
-    }
+    /// <inheritdoc/>
+    public override bool RemoveTimeout (object token) { return TimedEvents.RemoveTimeout (token); }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
index 8cdbbbaded..caa9e0b033 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
@@ -1,7 +1,8 @@
 using System.Runtime.InteropServices;
 
 namespace Terminal.Gui;
-class ConsoleDriverFacade<T> : IConsoleDriver
+
+internal class ConsoleDriverFacade<T> : IConsoleDriver
 {
     private readonly IInputProcessor _inputProcessor;
     private readonly IConsoleOutput _output;
@@ -12,7 +13,13 @@ class ConsoleDriverFacade<T> : IConsoleDriver
     /// <summary>The event fired when the terminal is resized.</summary>
     public event EventHandler<SizeChangedEventArgs> SizeChanged;
 
-    public ConsoleDriverFacade (IInputProcessor inputProcessor, IOutputBuffer outputBuffer, IConsoleOutput output, AnsiRequestScheduler ansiRequestScheduler,IWindowSizeMonitor windowSizeMonitor)
+    public ConsoleDriverFacade (
+        IInputProcessor inputProcessor,
+        IOutputBuffer outputBuffer,
+        IConsoleOutput output,
+        AnsiRequestScheduler ansiRequestScheduler,
+        IWindowSizeMonitor windowSizeMonitor
+    )
     {
         _inputProcessor = inputProcessor;
         _output = output;
@@ -23,7 +30,7 @@ public ConsoleDriverFacade (IInputProcessor inputProcessor, IOutputBuffer output
         _inputProcessor.KeyUp += (s, e) => KeyUp?.Invoke (s, e);
         _inputProcessor.MouseEvent += (s, e) => MouseEvent?.Invoke (s, e);
 
-        windowSizeMonitor.SizeChanging += (_, e) => SizeChanged?.Invoke (this,e);
+        windowSizeMonitor.SizeChanging += (_, e) => SizeChanged?.Invoke (this, e);
 
         CreateClipboard ();
     }
@@ -51,14 +58,15 @@ private void CreateClipboard ()
     }
 
     /// <summary>Gets the location and size of the terminal screen.</summary>
-    public Rectangle Screen => new (new (0,0),_output.GetWindowSize ());
+    public Rectangle Screen => new (new (0, 0), _output.GetWindowSize ());
 
     /// <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 Region Clip {
+    public Region Clip
+    {
         get => _outputBuffer.Clip;
         set => _outputBuffer.Clip = value;
     }
@@ -92,7 +100,9 @@ public int Cols
     /// <summary>The leftmost column in the terminal.</summary>
     public int Left
     {
-        get => _outputBuffer.Left; set => _outputBuffer.Left = value; }
+        get => _outputBuffer.Left;
+        set => _outputBuffer.Left = value;
+    }
 
     /// <summary>
     ///     Gets the row last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/> are used by
@@ -119,7 +129,6 @@ public int Top
     /// <summary>Gets whether the <see cref="ConsoleDriver"/> supports TrueColor output.</summary>
     public bool SupportsTrueColor => true;
 
-
     // TODO: Currently ignored
     /// <summary>
     ///     Gets or sets whether the <see cref="ConsoleDriver"/> should use 16 colors instead of the default TrueColors.
@@ -147,42 +156,47 @@ public Attribute CurrentAttribute
     /// <remarks>
     ///     <para>
     ///         When the method returns, <see cref="ConsoleDriver.Col"/> will be incremented by the number of columns
-    ///         <paramref name="rune"/> required, even if the new column value is outside of the <see cref="ConsoleDriver.Clip"/> or screen
+    ///         <paramref name="rune"/> required, even if the new column value is outside of the
+    ///         <see cref="ConsoleDriver.Clip"/> or screen
     ///         dimensions defined by <see cref="ConsoleDriver.Cols"/>.
     ///     </para>
     ///     <para>
-    ///         If <paramref name="rune"/> requires more than one column, and <see cref="ConsoleDriver.Col"/> plus the number of columns
-    ///         needed exceeds the <see cref="ConsoleDriver.Clip"/> or screen dimensions, the default Unicode replacement character (U+FFFD)
+    ///         If <paramref name="rune"/> requires more than one column, and <see cref="ConsoleDriver.Col"/> plus the number
+    ///         of columns
+    ///         needed exceeds the <see cref="ConsoleDriver.Clip"/> or screen dimensions, the default Unicode replacement
+    ///         character (U+FFFD)
     ///         will be added instead.
     ///     </para>
     /// </remarks>
     /// <param name="rune">Rune to add.</param>
-    public void AddRune (Rune rune) => _outputBuffer.AddRune (rune);
+    public void AddRune (Rune rune) { _outputBuffer.AddRune (rune); }
 
     /// <summary>
     ///     Adds the specified <see langword="char"/> to the display at the current cursor position. This method is a
-    ///     convenience method that calls <see cref="ConsoleDriver.AddRune(System.Text.Rune)"/> with the <see cref="Rune"/> constructor.
+    ///     convenience method that calls <see cref="ConsoleDriver.AddRune(System.Text.Rune)"/> with the <see cref="Rune"/>
+    ///     constructor.
     /// </summary>
     /// <param name="c">Character to add.</param>
-    public void AddRune (char c) => _outputBuffer.AddRune (c);
+    public void AddRune (char c) { _outputBuffer.AddRune (c); }
 
     /// <summary>Adds the <paramref name="str"/> to the display at the cursor position.</summary>
     /// <remarks>
     ///     <para>
     ///         When the method returns, <see cref="ConsoleDriver.Col"/> will be incremented by the number of columns
-    ///         <paramref name="str"/> required, unless the new column value is outside of the <see cref="ConsoleDriver.Clip"/> or screen
+    ///         <paramref name="str"/> required, unless the new column value is outside of the <see cref="ConsoleDriver.Clip"/>
+    ///         or screen
     ///         dimensions defined by <see cref="ConsoleDriver.Cols"/>.
     ///     </para>
     ///     <para>If <paramref name="str"/> requires more columns than are available, the output will be clipped.</para>
     /// </remarks>
     /// <param name="str">String.</param>
-    public void AddStr (string str) => _outputBuffer.AddStr (str);
+    public void AddStr (string str) { _outputBuffer.AddStr (str); }
 
     /// <summary>Clears the <see cref="ConsoleDriver.Contents"/> of the driver.</summary>
     public void ClearContents ()
     {
         _outputBuffer.ClearContents ();
-        ClearedContents?.Invoke (this,new MouseEventArgs ());
+        ClearedContents?.Invoke (this, new MouseEventArgs ());
     }
 
     /// <summary>
@@ -190,14 +204,16 @@ public void ClearContents ()
     /// </summary>
     public event EventHandler<EventArgs> ClearedContents;
 
-
-    /// <summary>Fills the specified rectangle with the specified rune, using <see cref="ConsoleDriver.CurrentAttribute"/></summary>
+    /// <summary>
+    ///     Fills the specified rectangle with the specified rune, using <see cref="ConsoleDriver.CurrentAttribute"/>
+    /// </summary>
     /// <remarks>
-    /// The value of <see cref="ConsoleDriver.Clip"/> is honored. Any parts of the rectangle not in the clip will not be drawn.
+    ///     The value of <see cref="ConsoleDriver.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) => _outputBuffer.FillRect (rect, rune);
+    public void FillRect (Rectangle rect, Rune rune = default) { _outputBuffer.FillRect (rect, rune); }
 
     /// <summary>
     ///     Fills the specified rectangle with the specified <see langword="char"/>. This method is a convenience method
@@ -205,9 +221,7 @@ public void ClearContents ()
     /// </summary>
     /// <param name="rect"></param>
     /// <param name="c"></param>
-    public void FillRect (Rectangle rect, char c) => _outputBuffer.FillRect (rect, c);
-
-
+    public void FillRect (Rectangle rect, char c) { _outputBuffer.FillRect (rect, c); }
 
     /// <summary>Returns the name of the driver and relevant library version information.</summary>
     /// <returns></returns>
@@ -219,32 +233,36 @@ public void ClearContents ()
     ///     <see langword="true"/> if the rune can be properly presented; <see langword="false"/> if the driver does not
     ///     support displaying this rune.
     /// </returns>
-    public bool IsRuneSupported (Rune rune) => Rune.IsValid(rune.Value);
+    public bool IsRuneSupported (Rune rune) { return Rune.IsValid (rune.Value); }
 
     /// <summary>Tests whether the specified coordinate are valid for drawing the specified Rune.</summary>
     /// <param name="rune">Used to determine if one or two columns are required.</param>
     /// <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="ConsoleDriver.Clip"/>.
+    ///     <see langword="false"/> if the coordinate is outside the screen bounds or outside of
+    ///     <see cref="ConsoleDriver.Clip"/>.
     ///     <see langword="true"/> otherwise.
     /// </returns>
-    public bool IsValidLocation (Rune rune, int col, int row) => _outputBuffer.IsValidLocation (rune, col, row);
+    public bool IsValidLocation (Rune rune, int col, int row) { return _outputBuffer.IsValidLocation (rune, col, row); }
 
     /// <summary>
-    ///     Updates <see cref="ConsoleDriver.Col"/> and <see cref="ConsoleDriver.Row"/> to the specified column and row in <see cref="ConsoleDriver.Contents"/>.
-    ///     Used by <see cref="ConsoleDriver.AddRune(System.Text.Rune)"/> and <see cref="ConsoleDriver.AddStr"/> to determine where to add content.
+    ///     Updates <see cref="ConsoleDriver.Col"/> and <see cref="ConsoleDriver.Row"/> to the specified column and row in
+    ///     <see cref="ConsoleDriver.Contents"/>.
+    ///     Used by <see cref="ConsoleDriver.AddRune(System.Text.Rune)"/> and <see cref="ConsoleDriver.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="ConsoleDriver.Cols"/> and
+    ///         If <paramref name="col"/> or <paramref name="row"/> are negative or beyond  <see cref="ConsoleDriver.Cols"/>
+    ///         and
     ///         <see cref="ConsoleDriver.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 void Move (int col, int row) => _outputBuffer.Move (col, row);
+    public void Move (int col, int row) { _outputBuffer.Move (col, row); }
 
     // TODO: Probably part of output
 
@@ -259,7 +277,7 @@ public bool SetCursorVisibility (CursorVisibility visibility)
         return true;
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public bool GetCursorVisibility (out CursorVisibility current)
     {
         current = _lastCursor;
@@ -267,24 +285,18 @@ public bool GetCursorVisibility (out CursorVisibility current)
         return true;
     }
 
-    /// <inheritdoc />
-    public void Suspend ()
-    {
-
-    }
+    /// <inheritdoc/>
+    public void Suspend () { }
 
-    /// <summary>Sets the position of the terminal cursor to <see cref="ConsoleDriver.Col"/> and <see cref="ConsoleDriver.Row"/>.</summary>
-    public void UpdateCursor ()
-    {
-        _output.SetCursorPosition (Col, Row);
-    }
+    /// <summary>
+    ///     Sets the position of the terminal cursor to <see cref="ConsoleDriver.Col"/> and
+    ///     <see cref="ConsoleDriver.Row"/>.
+    /// </summary>
+    public void UpdateCursor () { _output.SetCursorPosition (Col, Row); }
 
     /// <summary>Initializes the driver</summary>
     /// <returns>Returns an instance of <see cref="MainLoop"/> using the <see cref="IMainLoopDriver"/> for the driver.</returns>
-    public MainLoop Init ()
-    {
-        throw new NotSupportedException ();
-    }
+    public MainLoop Init () { throw new NotSupportedException (); }
 
     /// <summary>Ends the execution of the console driver.</summary>
     public void End ()
@@ -295,11 +307,11 @@ public void End ()
     /// <summary>Selects the specified attribute as the attribute to use for future calls to AddRune and AddString.</summary>
     /// <remarks>Implementations should call <c>base.SetAttribute(c)</c>.</remarks>
     /// <param name="c">C.</param>
-    public Attribute SetAttribute (Attribute c) => _outputBuffer.CurrentAttribute = c;
+    public Attribute SetAttribute (Attribute c) { return _outputBuffer.CurrentAttribute = c; }
 
     /// <summary>Gets the current <see cref="Attribute"/>.</summary>
     /// <returns>The current attribute.</returns>
-    public Attribute GetAttribute () => _outputBuffer.CurrentAttribute;
+    public Attribute GetAttribute () { return _outputBuffer.CurrentAttribute; }
 
     /// <summary>Makes an <see cref="Attribute"/>.</summary>
     /// <param name="foreground">The foreground color.</param>
@@ -308,11 +320,11 @@ public void End ()
     public Attribute MakeColor (in Color foreground, in Color background)
     {
         // TODO: what even is this? why Attribute constructor wants to call Driver method which must return an instance of Attribute? ?!?!?!
-        return new Attribute (
-                              -1, // only used by cursesdriver!
-                              foreground,
-                              background
-                             );
+        return new (
+                    -1, // only used by cursesdriver!
+                    foreground,
+                    background
+                   );
     }
 
     /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="ConsoleDriver.KeyUp"/>.</summary>
@@ -320,7 +332,8 @@ public Attribute MakeColor (in Color foreground, in Color background)
 
     /// <summary>Event fired when a key is released.</summary>
     /// <remarks>
-    ///     Drivers that do not support key release events will fire this event after <see cref="ConsoleDriver.KeyDown"/> processing is
+    ///     Drivers that do not support key release events will fire this event after <see cref="ConsoleDriver.KeyDown"/>
+    ///     processing is
     ///     complete.
     /// </remarks>
     public event EventHandler<Key> KeyUp;
@@ -337,28 +350,23 @@ public Attribute MakeColor (in Color foreground, in Color background)
     public void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl)
     {
         // TODO: implement
-
     }
 
     /// <summary>
     ///     Provide proper writing to send escape sequence recognized by the <see cref="ConsoleDriver"/>.
     /// </summary>
     /// <param name="ansi"></param>
-    public void WriteRaw (string ansi)
-    {
-        _output.Write (ansi);
-    }
-
+    public void WriteRaw (string ansi) { _output.Write (ansi); }
 
     /// <summary>
-    /// Queues the given <paramref name="request"/> for execution
+    ///     Queues the given <paramref name="request"/> for execution
     /// </summary>
     /// <param name="request"></param>
-    public void QueueAnsiRequest (AnsiEscapeSequenceRequest request) => _ansiRequestScheduler.SendOrSchedule (request);
+    public void QueueAnsiRequest (AnsiEscapeSequenceRequest request) { _ansiRequestScheduler.SendOrSchedule (request); }
 
-    public AnsiRequestScheduler GetRequestScheduler () => _ansiRequestScheduler;
+    public AnsiRequestScheduler GetRequestScheduler () { return _ansiRequestScheduler; }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public void Refresh ()
     {
         // No need we will always draw when dirty
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
index b89f8630cc..f8786fa47e 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
@@ -9,27 +9,20 @@ public abstract class ConsoleInput<T> : IConsoleInput<T>
     private ConcurrentQueue<T>? _inputBuffer;
 
     /// <summary>
-    /// Determines how to get the current system type, adjust
-    /// in unit tests to simulate specific timings.
+    ///     Determines how to get the current system type, adjust
+    ///     in unit tests to simulate specific timings.
     /// </summary>
-    public Func<DateTime> Now { get; set; } = ()=>DateTime.Now;
+    public Func<DateTime> Now { get; set; } = () => DateTime.Now;
 
-    private Histogram<int> drainInputStream = Logging.Meter.CreateHistogram<int> ("Drain Input (ms)");
+    private readonly Histogram<int> drainInputStream = Logging.Meter.CreateHistogram<int> ("Drain Input (ms)");
 
+    /// <inheritdoc/>
+    public virtual void Dispose () { }
 
-    /// <inheritdoc />
-    public virtual void Dispose ()
-    {
-
-    }
-
-    /// <inheritdoc />
-    public void Initialize (ConcurrentQueue<T> inputBuffer)
-    {
-        _inputBuffer = inputBuffer;
-    }
+    /// <inheritdoc/>
+    public void Initialize (ConcurrentQueue<T> inputBuffer) { _inputBuffer = inputBuffer; }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public void Run (CancellationToken token)
     {
         try
@@ -41,18 +34,18 @@ public void Run (CancellationToken token)
 
             do
             {
-                var dt = Now ();
+                DateTime dt = Now ();
 
                 while (Peek ())
                 {
-                    foreach (var r in Read ())
+                    foreach (T r in Read ())
                     {
                         _inputBuffer.Enqueue (r);
                     }
                 }
 
-                var took = Now () - dt;
-                var sleepFor = TimeSpan.FromMilliseconds (20) - took;
+                TimeSpan took = Now () - dt;
+                TimeSpan sleepFor = TimeSpan.FromMilliseconds (20) - took;
 
                 drainInputStream.Record (took.Milliseconds);
 
@@ -66,20 +59,19 @@ public void Run (CancellationToken token)
             while (!token.IsCancellationRequested);
         }
         catch (OperationCanceledException)
-        {
-        }
+        { }
     }
 
     /// <summary>
-    /// When implemented in a derived class, returns true if there is data available
-    /// to read from console.
+    ///     When implemented in a derived class, returns true if there is data available
+    ///     to read from console.
     /// </summary>
     /// <returns></returns>
     protected abstract bool Peek ();
 
     /// <summary>
-    /// Returns the available data without blocking, called when <see cref="Peek"/>
-    /// returns <see langword="true"/>.
+    ///     Returns the available data without blocking, called when <see cref="Peek"/>
+    ///     returns <see langword="true"/>.
     /// </summary>
     /// <returns></returns>
     protected abstract IEnumerable<T> Read ();
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs b/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs
index 7c90a07f1a..d1fed29e5f 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs
@@ -5,17 +5,18 @@ namespace Terminal.Gui;
 public interface IConsoleInput<T> : IDisposable
 {
     /// <summary>
-    /// Initializes the input with a buffer into which to put data read
+    ///     Initializes the input with a buffer into which to put data read
     /// </summary>
     /// <param name="inputBuffer"></param>
     void Initialize (ConcurrentQueue<T> inputBuffer);
 
     /// <summary>
-    /// Runs in an infinite input loop.
+    ///     Runs in an infinite input loop.
     /// </summary>
     /// <param name="token"></param>
-    /// <exception cref="OperationCanceledException">Raised when token is
-    /// cancelled. This is the only means of exiting the input.</exception>
+    /// <exception cref="OperationCanceledException">
+    ///     Raised when token is
+    ///     cancelled. This is the only means of exiting the input.
+    /// </exception>
     void Run (CancellationToken token);
-
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
index 1019a944f5..e50094b468 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
@@ -1,7 +1,8 @@
 namespace Terminal.Gui;
+
 public interface IConsoleOutput : IDisposable
 {
-    void Write(string text);
+    void Write (string text);
     void Write (IOutputBuffer buffer);
     public Size GetWindowSize ();
     void SetCursorVisibility (CursorVisibility visibility);
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
index bf2aaec08c..dfb6be7a99 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
@@ -1,8 +1,6 @@
-
-#nullable enable
+#nullable enable
 namespace Terminal.Gui;
 
-
 public interface IInputProcessor
 {
     /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="KeyUp"/>.</summary>
@@ -10,7 +8,8 @@ public interface IInputProcessor
 
     /// <summary>Event fired when a key is released.</summary>
     /// <remarks>
-    ///     Drivers that do not support key release events will fire this event after <see cref="KeyDown"/> processing is complete.
+    ///     Drivers that do not support key release events will fire this event after <see cref="KeyDown"/> processing is
+    ///     complete.
     /// </remarks>
     event EventHandler<Key>? KeyUp;
 
@@ -41,9 +40,9 @@ public interface IInputProcessor
     void OnMouseEvent (MouseEventArgs mouseEventArgs);
 
     /// <summary>
-    /// Drains the input buffer, processing all available keystrokes
+    ///     Drains the input buffer, processing all available keystrokes
     /// </summary>
     void ProcessQueue ();
-    public IAnsiResponseParser GetParser ();
 
+    public IAnsiResponseParser GetParser ();
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
index 35a731797a..14ca18600d 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
@@ -1,12 +1,10 @@
 #nullable enable
 using System.Collections.Concurrent;
-using static Unix.Terminal.Curses;
 
 namespace Terminal.Gui;
 
 public interface IMainLoop<T> : IDisposable
 {
-
     public ITimedEvents TimedEvents { get; }
     public IOutputBuffer OutputBuffer { get; }
     public IInputProcessor InputProcessor { get; }
@@ -16,7 +14,7 @@ public interface IMainLoop<T> : IDisposable
     public IWindowSizeMonitor WindowSizeMonitor { get; }
 
     /// <summary>
-    /// Initializes the loop with a buffer from which data can be read
+    ///     Initializes the loop with a buffer from which data can be read
     /// </summary>
     /// <param name="timedEvents"></param>
     /// <param name="inputBuffer"></param>
@@ -25,7 +23,7 @@ public interface IMainLoop<T> : IDisposable
     void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput);
 
     /// <summary>
-    /// Perform a single iteration of the main loop without blocking anywhere.
+    ///     Perform a single iteration of the main loop without blocking anywhere.
     /// </summary>
     public void Iteration ();
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/INetInput.cs b/Terminal.Gui/ConsoleDrivers/V2/INetInput.cs
index 9b3842ff2e..610eee236b 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/INetInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/INetInput.cs
@@ -1,4 +1,4 @@
 namespace Terminal.Gui;
 
-internal interface INetInput :IConsoleInput<ConsoleKeyInfo>
+internal interface INetInput : IConsoleInput<ConsoleKeyInfo>
 { }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
index b798bf0965..4f1d99f0d1 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
@@ -4,13 +4,13 @@ namespace Terminal.Gui;
 public interface IOutputBuffer
 {
     /// <summary>
-    /// 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.
+    ///     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.
     /// </summary>
     public bool [] DirtyLines { get; }
 
     /// <summary>
-    /// The contents of the application output. The driver outputs this buffer to the terminal when UpdateScreen is called.
+    ///     The contents of the application output. The driver outputs this buffer to the terminal when UpdateScreen is called.
     /// </summary>
     Cell [,] Contents { get; set; }
 
@@ -22,7 +22,7 @@ public interface IOutputBuffer
     public Region? Clip { get; set; }
 
     /// <summary>
-    /// The <see cref="Attribute"/> that will be used for the next AddRune or AddStr call.
+    ///     The <see cref="Attribute"/> that will be used for the next AddRune or AddStr call.
     /// </summary>
     Attribute CurrentAttribute { get; set; }
 
@@ -36,8 +36,7 @@ public interface IOutputBuffer
     ///     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.
     /// </summary>
-    public int Row { get;}
-
+    public int Row { get; }
 
     /// <summary>
     ///     Gets the column last set by <see cref="Move"/>. <see cref="Col"/> and <see cref="Row"/> are used by
@@ -49,7 +48,7 @@ public interface IOutputBuffer
     int Top { get; set; }
 
     /// <summary>
-    /// Updates the column and row to the specified location in the buffer.
+    ///     Updates the column and row to the specified location in the buffer.
     /// </summary>
     /// <param name="col">The column to move to.</param>
     /// <param name="row">The row to move to.</param>
@@ -60,7 +59,8 @@ public interface IOutputBuffer
     void AddRune (Rune rune);
 
     /// <summary>
-    /// Adds the specified character to the display at the current cursor position. This is a convenience method for AddRune.
+    ///     Adds the specified character to the display at the current cursor position. This is a convenience method for
+    ///     AddRune.
     /// </summary>
     /// <param name="c">Character to add.</param>
     void AddRune (char c);
@@ -73,18 +73,18 @@ public interface IOutputBuffer
     void ClearContents ();
 
     /// <summary>
-    /// Tests whether the specified coordinate is valid for drawing the specified Rune.
+    ///     Tests whether the specified coordinate is valid for drawing the specified Rune.
     /// </summary>
     /// <param name="rune">Used to determine if one or two columns are required.</param>
     /// <param name="col">The column.</param>
     /// <param name="row">The row.</param>
     /// <returns>
-    /// True if the coordinate is valid for the Rune; false otherwise.
+    ///     True if the coordinate is valid for the Rune; false otherwise.
     /// </returns>
     bool IsValidLocation (Rune rune, int col, int row);
 
     /// <summary>
-    /// Changes the size of the buffer to the given size
+    ///     Changes the size of the buffer to the given size
     /// </summary>
     /// <param name="cols"></param>
     /// <param name="rows"></param>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IViewFinder.cs b/Terminal.Gui/ConsoleDrivers/V2/IViewFinder.cs
index 9d737cbedb..a4e5ac8636 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IViewFinder.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IViewFinder.cs
@@ -1,25 +1,19 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Terminal.Gui;
+namespace Terminal.Gui;
 
 public interface IViewFinder
 {
     View GetViewAt (Point screenPosition, out Point viewportPoint);
 }
 
-class StaticViewFinder : IViewFinder
+internal class StaticViewFinder : IViewFinder
 {
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public View GetViewAt (Point screenPosition, out Point viewportPoint)
     {
-        var hit = View.GetViewsUnderMouse (screenPosition).LastOrDefault ();
+        View hit = View.GetViewsUnderMouse (screenPosition).LastOrDefault ();
 
         viewportPoint = hit?.ScreenToViewport (screenPosition) ?? Point.Empty;
 
         return hit;
     }
-}
\ No newline at end of file
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index 5e65a42ed6..43e6014f9a 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -5,16 +5,15 @@ namespace Terminal.Gui;
 
 public abstract class InputProcessor<T> : IInputProcessor
 {
-
     /// <summary>
-    /// How long after Esc has been pressed before we give up on getting an Ansi escape sequence
+    ///     How long after Esc has been pressed before we give up on getting an Ansi escape sequence
     /// </summary>
-    TimeSpan _escTimeout = TimeSpan.FromMilliseconds (50);
+    private readonly TimeSpan _escTimeout = TimeSpan.FromMilliseconds (50);
 
     internal AnsiResponseParser<T> Parser { get; } = new ();
     public ConcurrentQueue<T> InputBuffer { get; }
 
-    public IAnsiResponseParser GetParser () => Parser;
+    public IAnsiResponseParser GetParser () { return Parser; }
 
     private MouseInterpreter _mouseInterpreter { get; } = new ();
 
@@ -66,11 +65,12 @@ public InputProcessor (ConcurrentQueue<T> inputBuffer)
         Parser.Mouse += (s, e) => OnMouseEvent (e);
 
         Parser.HandleKeyboard = true;
-        Parser.Keyboard += (s,k)=>
-                          {
-                              OnKeyDown (k);
-                              OnKeyUp (k);
-                          };
+
+        Parser.Keyboard += (s, k) =>
+                           {
+                               OnKeyDown (k);
+                               OnKeyUp (k);
+                           };
 
         // TODO: For now handle all other escape codes with ignore
         Parser.UnexpectedResponseHandler = str => { return true; };
@@ -88,23 +88,22 @@ public void ProcessQueue ()
             Process (input);
         }
 
-        foreach (var input in ShouldReleaseParserHeldKeys ())
+        foreach (T input in ShouldReleaseParserHeldKeys ())
         {
             ProcessAfterParsing (input);
         }
     }
 
-
     public IEnumerable<T> ShouldReleaseParserHeldKeys ()
     {
-        if (Parser.State == AnsiResponseParserState.ExpectingBracket &&
-            DateTime.Now - Parser.StateChangedAt > _escTimeout)
+        if (Parser.State == AnsiResponseParserState.ExpectingBracket && DateTime.Now - Parser.StateChangedAt > _escTimeout)
         {
             return Parser.Release ().Select (o => o.Item2);
         }
 
         return [];
     }
+
     protected abstract void Process (T result);
 
     protected abstract void ProcessAfterParsing (T input);
diff --git a/Terminal.Gui/ConsoleDrivers/V2/Logging.cs b/Terminal.Gui/ConsoleDrivers/V2/Logging.cs
index a4149cd1ae..300dac1c5a 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/Logging.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/Logging.cs
@@ -5,24 +5,26 @@
 namespace Terminal.Gui;
 
 /// <summary>
-/// Singleton logging instance class. Do not use console loggers
-/// with this class as it will interfere with Terminal.Gui
-/// screen output (i.e. use a file logger).
+///     Singleton logging instance class. Do not use console loggers
+///     with this class as it will interfere with Terminal.Gui
+///     screen output (i.e. use a file logger).
 /// </summary>
-/// <remarks>Also contains the
-/// <see cref="Meter"/> instance that should be used for internal metrics
-/// (iteration timing etc).</remarks>
+/// <remarks>
+///     Also contains the
+///     <see cref="Meter"/> instance that should be used for internal metrics
+///     (iteration timing etc).
+/// </remarks>
 public static class Logging
 {
     /// <summary>
-    /// Logger, defaults to NullLogger (i.e. no logging).  Set this to a
-    /// file logger to enable logging of Terminal.Gui internals.
+    ///     Logger, defaults to NullLogger (i.e. no logging).  Set this to a
+    ///     file logger to enable logging of Terminal.Gui internals.
     /// </summary>
     public static ILogger Logger { get; set; } = NullLogger.Instance;
 
     /// <summary>
-    /// Metrics reporting meter for internal Terminal.Gui processes. To use
-    /// create your own static instrument e.g. CreateCounter, CreateHistogram etc
+    ///     Metrics reporting meter for internal Terminal.Gui processes. To use
+    ///     create your own static instrument e.g. CreateCounter, CreateHistogram etc
     /// </summary>
     internal static readonly Meter Meter = new ("Terminal.Gui");
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 9a8814139d..2bd580817a 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -14,22 +14,22 @@ public class MainLoop<T> : IMainLoop<T>
 
     public IInputProcessor InputProcessor { get; private set; }
 
-    public IOutputBuffer OutputBuffer { get; private set; } = new OutputBuffer();
+    public IOutputBuffer OutputBuffer { get; } = new OutputBuffer ();
 
-    public IConsoleOutput Out { get;private set; }
+    public IConsoleOutput Out { get; private set; }
     public AnsiRequestScheduler AnsiRequestScheduler { get; private set; }
 
     public IWindowSizeMonitor WindowSizeMonitor { get; private set; }
 
     /// <summary>
-    /// Determines how to get the current system type, adjust
-    /// in unit tests to simulate specific timings.
+    ///     Determines how to get the current system type, adjust
+    ///     in unit tests to simulate specific timings.
     /// </summary>
     public Func<DateTime> Now { get; set; } = () => DateTime.Now;
 
-    static readonly Histogram<int> totalIterationMetric = Logging.Meter.CreateHistogram<int> ("Iteration (ms)");
+    private static readonly Histogram<int> totalIterationMetric = Logging.Meter.CreateHistogram<int> ("Iteration (ms)");
 
-    static readonly Histogram<int> iterationInvokesAndTimeouts = Logging.Meter.CreateHistogram<int> ("Invokes & Timers (ms)");
+    private static readonly Histogram<int> iterationInvokesAndTimeouts = Logging.Meter.CreateHistogram<int> ("Invokes & Timers (ms)");
 
     public void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput)
     {
@@ -38,27 +38,27 @@ public void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer
         InputProcessor = inputProcessor;
 
         TimedEvents = timedEvents;
-        AnsiRequestScheduler = new AnsiRequestScheduler (InputProcessor.GetParser ());
+        AnsiRequestScheduler = new (InputProcessor.GetParser ());
 
-        WindowSizeMonitor = new WindowSizeMonitor (Out,OutputBuffer);
+        WindowSizeMonitor = new WindowSizeMonitor (Out, OutputBuffer);
     }
 
     /// <inheritdoc/>
     public void Iteration ()
     {
-            var dt = Now();
+        DateTime dt = Now ();
 
-            IterationImpl ();
+        IterationImpl ();
 
-            var took = Now() - dt;
-            var sleepFor = TimeSpan.FromMilliseconds (50) - took;
+        TimeSpan took = Now () - dt;
+        TimeSpan sleepFor = TimeSpan.FromMilliseconds (50) - took;
 
-            totalIterationMetric.Record (took.Milliseconds);
+        totalIterationMetric.Record (took.Milliseconds);
 
-            if (sleepFor.Milliseconds > 0)
-            {
-                Task.Delay (sleepFor).Wait ();
-            }
+        if (sleepFor.Milliseconds > 0)
+        {
+            Task.Delay (sleepFor).Wait ();
+        }
     }
 
     public void IterationImpl ()
@@ -67,8 +67,7 @@ public void IterationImpl ()
 
         if (Application.Top != null)
         {
-
-            bool needsDrawOrLayout = AnySubviewsNeedDrawn(Application.Top);
+            bool needsDrawOrLayout = AnySubviewsNeedDrawn (Application.Top);
 
             bool sizeChanged = WindowSizeMonitor.Poll ();
 
@@ -90,7 +89,6 @@ public void IterationImpl ()
         iterationInvokesAndTimeouts.Record (swCallbacks.Elapsed.Milliseconds);
     }
 
-
     private bool AnySubviewsNeedDrawn (View v)
     {
         if (v.NeedsDraw || v.NeedsLayout)
@@ -98,7 +96,7 @@ private bool AnySubviewsNeedDrawn (View v)
             return true;
         }
 
-        foreach(var subview in v.Subviews )
+        foreach (View subview in v.Subviews)
         {
             if (AnySubviewsNeedDrawn (subview))
             {
@@ -109,8 +107,8 @@ private bool AnySubviewsNeedDrawn (View v)
         return false;
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public void Dispose ()
     { // TODO release managed resources here
     }
-}
\ No newline at end of file
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index 941260263b..6b6fb33186 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -9,32 +9,42 @@ public class MainLoopCoordinator<T> : IMainLoopCoordinator
     private readonly ConcurrentQueue<T> _inputBuffer;
     private readonly IInputProcessor _inputProcessor;
     private readonly IMainLoop<T> _loop;
-    private CancellationTokenSource tokenSource = new ();
+    private readonly CancellationTokenSource tokenSource = new ();
     private readonly Func<IConsoleOutput> _outputFactory;
     private IConsoleInput<T> _input;
     private IConsoleOutput _output;
-    object oLockInitialization = new ();
+    private readonly object oLockInitialization = new ();
     private ConsoleDriverFacade<T> _facade;
     private Task _inputTask;
-    private ITimedEvents _timedEvents;
-
-    private SemaphoreSlim _startupSemaphore = new (0, 1);
+    private readonly ITimedEvents _timedEvents;
 
+    private readonly SemaphoreSlim _startupSemaphore = new (0, 1);
 
     /// <summary>
-    /// Creates a new coordinator
+    ///     Creates a new coordinator
     /// </summary>
     /// <param name="timedEvents"></param>
-    /// <param name="inputFactory">Function to create a new input. This must call <see langword="new"/>
+    /// <param name="inputFactory">
+    ///     Function to create a new input. This must call <see langword="new"/>
     ///     explicitly and cannot return an existing instance. This requirement arises because Windows
-    ///     console screen buffer APIs are thread-specific for certain operations.</param>
+    ///     console screen buffer APIs are thread-specific for certain operations.
+    /// </param>
     /// <param name="inputBuffer"></param>
     /// <param name="inputProcessor"></param>
-    /// <param name="outputFactory">Function to create a new output. This must call <see langword="new"/>
+    /// <param name="outputFactory">
+    ///     Function to create a new output. This must call <see langword="new"/>
     ///     explicitly and cannot return an existing instance. This requirement arises because Windows
-    ///     console screen buffer APIs are thread-specific for certain operations.</param>
+    ///     console screen buffer APIs are thread-specific for certain operations.
+    /// </param>
     /// <param name="loop"></param>
-    public MainLoopCoordinator (ITimedEvents timedEvents, Func<IConsoleInput<T>> inputFactory, ConcurrentQueue<T> inputBuffer,IInputProcessor inputProcessor, Func<IConsoleOutput> outputFactory, IMainLoop<T> loop)
+    public MainLoopCoordinator (
+        ITimedEvents timedEvents,
+        Func<IConsoleInput<T>> inputFactory,
+        ConcurrentQueue<T> inputBuffer,
+        IInputProcessor inputProcessor,
+        Func<IConsoleOutput> outputFactory,
+        IMainLoop<T> loop
+    )
     {
         _timedEvents = timedEvents;
         _inputFactory = inputFactory;
@@ -45,7 +55,7 @@ public MainLoopCoordinator (ITimedEvents timedEvents, Func<IConsoleInput<T>> inp
     }
 
     /// <summary>
-    /// Starts the input loop thread in separate task (returning immediately).
+    ///     Starts the input loop thread in separate task (returning immediately).
     /// </summary>
     public async Task StartAsync ()
     {
@@ -81,23 +91,18 @@ private void RunInput ()
                 _input.Run (tokenSource.Token);
             }
             catch (OperationCanceledException)
-            {
-            }
+            { }
+
             _input.Dispose ();
         }
         catch (Exception e)
         {
-            Logging.Logger.LogCritical (e,"Input loop crashed");
+            Logging.Logger.LogCritical (e, "Input loop crashed");
         }
     }
 
-    /// <inheritdoc />
-    public void RunIteration ()
-    {
-
-        _loop.Iteration ();
-    }
-
+    /// <inheritdoc/>
+    public void RunIteration () { _loop.Iteration (); }
 
     private void BootMainLoop ()
     {
@@ -115,12 +120,12 @@ private void BuildFacadeIfPossible ()
     {
         if (_input != null && _output != null)
         {
-            _facade = new ConsoleDriverFacade<T> (
-                                                  _inputProcessor,
-                                                  _loop.OutputBuffer,
-                                                  _output,
-                                                  _loop.AnsiRequestScheduler,
-                                                  _loop.WindowSizeMonitor);
+            _facade = new (
+                           _inputProcessor,
+                           _loop.OutputBuffer,
+                           _output,
+                           _loop.AnsiRequestScheduler,
+                           _loop.WindowSizeMonitor);
             Application.Driver = _facade;
 
             _startupSemaphore.Release ();
@@ -129,9 +134,9 @@ private void BuildFacadeIfPossible ()
 
     public void Stop ()
     {
-        tokenSource.Cancel();
+        tokenSource.Cancel ();
 
         // Wait for input infinite loop to exit
         Task.WhenAll (_inputTask).Wait ();
     }
-}
\ No newline at end of file
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseButtonState.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseButtonState.cs
index 13dd36be10..0a64108f98 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MouseButtonState.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseButtonState.cs
@@ -2,14 +2,14 @@
 namespace Terminal.Gui;
 
 /// <summary>
-/// Not to be confused with <see cref="NetEvents.MouseButtonState"/>
+///     Not to be confused with <see cref="NetEvents.MouseButtonState"/>
 /// </summary>
 internal class MouseButtonStateEx
 {
     private readonly Func<DateTime> _now;
     private readonly TimeSpan _repeatClickThreshold;
     private readonly int _buttonIdx;
-    private int _consecutiveClicks = 0;
+    private int _consecutiveClicks;
 
     /// <summary>
     ///     When the button entered its current state.
@@ -17,11 +17,11 @@ internal class MouseButtonStateEx
     public DateTime At { get; set; }
 
     /// <summary>
-    /// <see langword="true"/> if the button is currently down
+    ///     <see langword="true"/> if the button is currently down
     /// </summary>
     public bool Pressed { get; set; }
 
-    public MouseButtonStateEx (Func<DateTime> now,TimeSpan repeatClickThreshold, int buttonIdx)
+    public MouseButtonStateEx (Func<DateTime> now, TimeSpan repeatClickThreshold, int buttonIdx)
     {
         _now = now;
         _repeatClickThreshold = repeatClickThreshold;
@@ -32,7 +32,7 @@ public void UpdateState (MouseEventArgs e, out int? numClicks)
     {
         bool isPressedNow = IsPressed (_buttonIdx, e.Flags);
 
-        var elapsed =_now() - At;
+        TimeSpan elapsed = _now () - At;
 
         if (elapsed > _repeatClickThreshold)
         {
@@ -47,6 +47,7 @@ public void UpdateState (MouseEventArgs e, out int? numClicks)
             {
                 // No change in button state so do nothing
                 numClicks = null;
+
                 return;
             }
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
index 2acba64a45..224150a4d4 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
@@ -1,26 +1,22 @@
 #nullable enable
 
-using static Terminal.Gui.SpinnerStyle;
-
 namespace Terminal.Gui;
 
 internal class MouseInterpreter
 {
-
     /// <summary>
-    /// Function for returning the current time. Use in unit tests to
-    /// ensure repeatable tests.
+    ///     Function for returning the current time. Use in unit tests to
+    ///     ensure repeatable tests.
     /// </summary>
     public Func<DateTime> Now { get; set; }
 
     /// <summary>
-    /// How long to wait for a second, third, fourth click after the first before giving up and
-    /// releasing event as a 'click'
+    ///     How long to wait for a second, third, fourth click after the first before giving up and
+    ///     releasing event as a 'click'
     /// </summary>
     public TimeSpan RepeatedClickThreshold { get; set; }
 
-    private MouseButtonStateEx [] _buttonStates ;
-
+    private readonly MouseButtonStateEx [] _buttonStates;
 
     public MouseInterpreter (
         Func<DateTime>? now = null,
@@ -32,24 +28,23 @@ public MouseInterpreter (
 
         _buttonStates = new []
         {
-            new MouseButtonStateEx (this.Now,this.RepeatedClickThreshold,0),
-            new MouseButtonStateEx (this.Now,this.RepeatedClickThreshold,1),
-            new MouseButtonStateEx (this.Now,this.RepeatedClickThreshold,2),
-            new MouseButtonStateEx (this.Now,this.RepeatedClickThreshold,3),
+            new MouseButtonStateEx (Now, RepeatedClickThreshold, 0),
+            new MouseButtonStateEx (Now, RepeatedClickThreshold, 1),
+            new MouseButtonStateEx (Now, RepeatedClickThreshold, 2),
+            new MouseButtonStateEx (Now, RepeatedClickThreshold, 3)
         };
     }
 
     public MouseEventArgs Process (MouseEventArgs e)
     {
         // For each mouse button
-        for (int i = 0; i < 4; i++)
+        for (var i = 0; i < 4; i++)
         {
-
-            _buttonStates [i].UpdateState (e, out var numClicks);
+            _buttonStates [i].UpdateState (e, out int? numClicks);
 
             if (numClicks.HasValue)
             {
-                return RaiseClick (i,numClicks.Value, e);
+                return RaiseClick (i, numClicks.Value, e);
             }
         }
 
@@ -63,7 +58,6 @@ private MouseEventArgs RaiseClick (int button, int numberOfClicks, MouseEventArg
         return mouseEventArgs;
     }
 
-
     private MouseFlags ToClicks (int buttonIdx, int numberOfClicks)
     {
         if (numberOfClicks == 0)
@@ -100,4 +94,4 @@ private MouseFlags ToClicks (int buttonIdx, int numberOfClicks)
                    _ => throw new ArgumentOutOfRangeException (nameof (buttonIdx), "Unsupported button index")
                };
     }
-}
\ No newline at end of file
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
index a906b994a3..5183f0577f 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
@@ -4,33 +4,36 @@ namespace Terminal.Gui;
 
 public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
 {
-    private NetWinVTConsole _adjustConsole;
+    private readonly NetWinVTConsole _adjustConsole;
 
     public NetInput ()
     {
-
         Logging.Logger.LogInformation ($"Creating {nameof (NetInput)}");
-        var p = Environment.OSVersion.Platform;
+        PlatformID p = Environment.OSVersion.Platform;
+
         if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
         {
             try
             {
-                _adjustConsole = new NetWinVTConsole ();
+                _adjustConsole = new ();
             }
             catch (ApplicationException ex)
             {
                 // Likely running as a unit test, or in a non-interactive session.
-                Logging.Logger.LogCritical (ex,"NetWinVTConsole could not be constructed i.e. could not configure terminal modes. May indicate running in non-interactive session e.g. unit testing CI");
+                Logging.Logger.LogCritical (
+                                            ex,
+                                            "NetWinVTConsole could not be constructed i.e. could not configure terminal modes. May indicate running in non-interactive session e.g. unit testing CI");
             }
         }
 
         // Doesn't seem to work
         Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
     }
-    /// <inheritdoc />
-    protected override bool Peek () => Console.KeyAvailable;
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
+    protected override bool Peek () { return Console.KeyAvailable; }
+
+    /// <inheritdoc/>
     protected override IEnumerable<ConsoleKeyInfo> Read ()
     {
         while (Console.KeyAvailable)
@@ -39,10 +42,10 @@ protected override IEnumerable<ConsoleKeyInfo> Read ()
         }
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public override void Dispose ()
     {
         base.Dispose ();
         _adjustConsole?.Cleanup ();
     }
-}
\ No newline at end of file
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
index 398232deb8..71d94ae2e3 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
@@ -1,17 +1,16 @@
 using System.Collections.Concurrent;
-using Terminal.Gui.ConsoleDrivers;
 
 namespace Terminal.Gui;
 
 /// <summary>
-/// Input processor for <see cref="NetInput"/>, deals in <see cref="ConsoleKeyInfo"/> stream
+///     Input processor for <see cref="NetInput"/>, deals in <see cref="ConsoleKeyInfo"/> stream
 /// </summary>
 public class NetInputProcessor : InputProcessor<ConsoleKeyInfo>
 {
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public NetInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer) : base (inputBuffer) { }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     protected override void Process (ConsoleKeyInfo consoleKeyInfo)
     {
         foreach (Tuple<char, ConsoleKeyInfo> released in Parser.ProcessInput (Tuple.Create (consoleKeyInfo.KeyChar, consoleKeyInfo)))
@@ -20,10 +19,10 @@ protected override void Process (ConsoleKeyInfo consoleKeyInfo)
         }
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     protected override void ProcessAfterParsing (ConsoleKeyInfo input)
     {
-        var key = EscSeqUtils.MapKey (input);
+        KeyCode key = EscSeqUtils.MapKey (input);
         OnKeyDown (key);
         OnKeyUp (key);
     }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
index cd705f1d7b..ece30a5602 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
@@ -1,14 +1,16 @@
 using Microsoft.Extensions.Logging;
 
 namespace Terminal.Gui;
+
 public class NetOutput : IConsoleOutput
 {
     public bool IsWinPlatform { get; }
 
     private CursorVisibility? _cachedCursorVisibility;
+
     public NetOutput ()
     {
-        Logging.Logger.LogInformation ($"Creating {nameof(NetOutput)}");
+        Logging.Logger.LogInformation ($"Creating {nameof (NetOutput)}");
 
         PlatformID p = Environment.OSVersion.Platform;
 
@@ -18,20 +20,17 @@ public NetOutput ()
         }
     }
 
-    /// <inheritdoc />
-    public void Write (string text)
-    {
-        Console.Write (text);
-    }
+    /// <inheritdoc/>
+    public void Write (string text) { Console.Write (text); }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public void Write (IOutputBuffer buffer)
     {
-        if ( Console.WindowHeight < 1
+        if (Console.WindowHeight < 1
             || buffer.Contents.Length != buffer.Rows * buffer.Cols
             || buffer.Rows != Console.WindowHeight)
         {
-       //     return;
+            //     return;
         }
 
         var top = 0;
@@ -154,7 +153,7 @@ public void Write (IOutputBuffer buffer)
             }
         }
 
-        foreach (var s in Application.Sixel)
+        foreach (SixelToRender s in Application.Sixel)
         {
             if (!string.IsNullOrWhiteSpace (s.SixelData))
             {
@@ -168,13 +167,10 @@ public void Write (IOutputBuffer buffer)
         _cachedCursorVisibility = savedVisibility;
     }
 
-    /// <inheritdoc />
-    public Size GetWindowSize ()
-    {
-        return new Size (Console.WindowWidth, Console.WindowHeight);
-    }
+    /// <inheritdoc/>
+    public Size GetWindowSize () { return new (Console.WindowWidth, Console.WindowHeight); }
 
-    void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
+    private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
     {
         SetCursorPositionImpl (lastCol, row);
         Console.Write (output);
@@ -182,6 +178,7 @@ void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int out
         lastCol += outputWidth;
         outputWidth = 0;
     }
+
     public bool SetCursorVisibility (CursorVisibility visibility)
     {
         _cachedCursorVisibility = visibility;
@@ -191,11 +188,8 @@ public bool SetCursorVisibility (CursorVisibility visibility)
         return visibility == CursorVisibility.Default;
     }
 
-    /// <inheritdoc />
-    public void SetCursorPosition (int col, int row)
-    {
-        SetCursorPositionImpl (col, row);
-    }
+    /// <inheritdoc/>
+    public void SetCursorPosition (int col, int row) { SetCursorPositionImpl (col, row); }
 
     private bool SetCursorPositionImpl (int col, int row)
     {
@@ -221,15 +215,11 @@ private bool SetCursorPositionImpl (int col, int row)
         return true;
     }
 
-    /// <inheritdoc />
-    public void Dispose ()
-    {
-        Console.Clear ();
-    }
+    /// <inheritdoc/>
+    public void Dispose () { Console.Clear (); }
 
     void IConsoleOutput.SetCursorVisibility (CursorVisibility visibility)
     {
         Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
     }
-
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
index 2391055de6..cbb71b2471 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
@@ -4,9 +4,9 @@
 namespace Terminal.Gui;
 
 /// <summary>
-/// Stores the desired output state for the whole application. This is updated during
-/// draw operations before being flushed to the console as part of <see cref="MainLoop{T}"/>
-/// operation
+///     Stores the desired output state for the whole application. This is updated during
+///     draw operations before being flushed to the console as part of <see cref="MainLoop{T}"/>
+///     operation
 /// </summary>
 public class OutputBuffer : IOutputBuffer
 {
@@ -33,7 +33,7 @@ public Attribute CurrentAttribute
             // TODO: This makes IConsoleDriver 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;
             }
@@ -51,7 +51,6 @@ public Attribute CurrentAttribute
     /// </summary>
     public int Row { get; private set; }
 
-
     /// <summary>
     ///     Gets the column 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.
@@ -80,11 +79,9 @@ public int Cols
         }
     }
 
-
     /// <summary>The topmost row in the terminal.</summary>
     public virtual int Top { get; set; } = 0;
 
-
     // 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.
     public bool []? DirtyLines { get; set; }
@@ -348,13 +345,14 @@ public void ClearContents ()
             {
                 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;
             }
         }
@@ -362,6 +360,7 @@ public void ClearContents ()
         // TODO: Who uses this and why? I am removing for now - this class is a state class not an events class
         //ClearedContents?.Invoke (this, EventArgs.Empty);
     }
+
     /// <summary>Tests whether the specified coordinate are valid for drawing the specified Rune.</summary>
     /// <param name="rune">Used to determine if one or two columns are required.</param>
     /// <param name="col">The column.</param>
@@ -380,7 +379,7 @@ public bool IsValidLocation (Rune rune, int col, int row)
         return Clip!.Contains (col, row) || Clip!.Contains (col + 1, row);
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public void SetWindowSize (int cols, int rows)
     {
         Cols = cols;
@@ -388,11 +387,12 @@ public void SetWindowSize (int cols, int rows)
         ClearContents ();
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public void FillRect (Rectangle rect, Rune rune)
     {
         // BUGBUG: This should be a method on Region
         rect = Rectangle.Intersect (rect, Clip?.GetBounds () ?? Screen);
+
         lock (Contents!)
         {
             for (int r = rect.Y; r < rect.Y + rect.Height; r++)
@@ -403,9 +403,10 @@ public void FillRect (Rectangle rect, Rune rune)
                     {
                         continue;
                     }
-                    Contents [r, c] = new Cell
+
+                    Contents [r, c] = new()
                     {
-                        Rune = (rune != default ? rune : (Rune)' '),
+                        Rune = rune != default (Rune) ? rune : (Rune)' ',
                         Attribute = CurrentAttribute, IsDirty = true
                     };
                 }
@@ -413,7 +414,7 @@ public void FillRect (Rectangle rect, Rune rune)
         }
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public void FillRect (Rectangle rect, char rune)
     {
         for (int y = rect.Top; y < rect.Top + rect.Height; y++)
@@ -426,7 +427,6 @@ public void FillRect (Rectangle rect, char rune)
         }
     }
 
-
     // TODO: Make internal once Menu is upgraded
     /// <summary>
     ///     Updates <see cref="Col"/> and <see cref="Row"/> to the specified column and row in <see cref="Contents"/>.
@@ -447,4 +447,4 @@ public virtual void Move (int col, int row)
         Col = col;
         Row = row;
     }
-}
\ No newline at end of file
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index cb3a1cd70c..f566fc3c92 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -46,18 +46,13 @@
   <Class Name="Terminal.Gui.ConsoleInput&lt;T&gt;" Collapsed="true">
     <Position X="12.5" Y="2" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAACAEAQAAAAAAAAAgACAAAAAAAAAAAAAAAAo=</HashCode>
+      <HashCode>AAAAAAAAACAEAQAAAAAAAQAgACAAAAAAAAAAAAAAAAo=</HashCode>
       <FileName>ConsoleDrivers\V2\ConsoleInput.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.MainLoop&lt;T&gt;" Collapsed="true" BaseTypeListCollapsed="true">
     <Position X="11" Y="4.75" Width="1.5" />
-    <AssociationLine Name="TimedEvents" Type="Terminal.Gui.ITimedEvents">
-      <MemberNameLabel ManuallyPlaced="true">
-        <Position X="-1.14" Y="0.123" />
-      </MemberNameLabel>
-    </AssociationLine>
     <AssociationLine Name="Out" Type="Terminal.Gui.IConsoleOutput" ManuallyRouted="true">
       <Path>
         <Point X="11.852" Y="5.312" />
@@ -73,6 +68,10 @@
       <Path>
         <Point X="11.75" Y="4.75" />
         <Point X="11.75" Y="4.39" />
+        <Point X="13.667" Y="4.39" Type="JumpStart" />
+        <Point X="13.833" Y="4.39" Type="JumpEnd" />
+        <Point X="15.698" Y="4.39" Type="JumpStart" />
+        <Point X="15.865" Y="4.39" Type="JumpEnd" />
         <Point X="20.625" Y="4.39" />
         <Point X="20.625" Y="4.5" />
       </Path>
@@ -92,24 +91,29 @@
         <Position X="0.047" Y="-0.336" />
       </MemberNameLabel>
     </AssociationLine>
+    <AssociationLine Name="TimedEvents" Type="Terminal.Gui.ITimedEvents">
+      <MemberNameLabel ManuallyPlaced="true">
+        <Position X="-1.14" Y="0.123" />
+      </MemberNameLabel>
+    </AssociationLine>
     <TypeIdentifier>
-      <HashCode>QQQAAAAQACABIQQAAAAAAAAAACAAAAACAAAAAACAEAA=</HashCode>
+      <HashCode>QQQQAAAQACABIQQAAAAAAAAAACAAAAACAAAAAACAEAA=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoop.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
-      <Property Name="TimedEvents" />
       <Property Name="InputProcessor" />
       <Property Name="OutputBuffer" />
       <Property Name="Out" />
       <Property Name="AnsiRequestScheduler" />
       <Property Name="WindowSizeMonitor" />
+      <Property Name="TimedEvents" />
     </ShowAsAssociation>
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.MainLoopCoordinator&lt;T&gt;">
     <Position X="6.5" Y="2" Width="2" />
     <TypeIdentifier>
-      <HashCode>AAAAIAEACAIABAAAABQAAAAQABAQAAQAIQIABAAASgw=</HashCode>
+      <HashCode>AAAAIAEgCAIABAAAABQAAAAAABAQAAQAIQIABAAACgw=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoopCoordinator.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -120,7 +124,7 @@
   <Class Name="Terminal.Gui.AnsiResponseParser&lt;T&gt;" Collapsed="true">
     <Position X="19.75" Y="10" Width="2" />
     <TypeIdentifier>
-      <HashCode>AAQAAAAAAAAACIAAAAAAAAAAAAAgAABAAAAAABAAAAA=</HashCode>
+      <HashCode>AAQAAAAAAAAACIAAAAAAAAAAAAAgAABAAAAACBAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
     </TypeIdentifier>
   </Class>
@@ -139,7 +143,7 @@
   <Class Name="Terminal.Gui.NetOutput" Collapsed="true">
     <Position X="14.75" Y="8.25" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AEAAAAAAACEAAAAAAAAAAAAAAQAAQAAAMACAAAkAAAA=</HashCode>
+      <HashCode>AEAAAAAAACEAAAAAAAAAAAAAAQAAQAAAMACAAAEAAAE=</HashCode>
       <FileName>ConsoleDrivers\V2\NetOutput.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
@@ -159,8 +163,8 @@
         <Point X="18" Y="5.312" />
         <Point X="18" Y="10.031" />
         <Point X="15.99" Y="10.031" />
-        <Point X="15.99" Y="10.625" />
-        <Point X="15" Y="10.625" />
+        <Point X="15.99" Y="10.605" />
+        <Point X="15" Y="10.605" />
       </Path>
     </AssociationLine>
     <TypeIdentifier>
@@ -188,13 +192,13 @@
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.AnsiMouseParser">
-    <Position X="24.5" Y="9" Width="1.75" />
+    <Position X="24.25" Y="9.5" Width="1.75" />
     <TypeIdentifier>
-      <HashCode>BAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAA=</HashCode>
+      <HashCode>BAAAAAAAAAgAAAAAAAAAAAAAIAAAAAAAQAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiMouseParser.cs</FileName>
     </TypeIdentifier>
   </Class>
-  <Class Name="Terminal.Gui.ConsoleDrivers.V2.ConsoleDriverFacade&lt;T&gt;">
+  <Class Name="Terminal.Gui.ConsoleDriverFacade&lt;T&gt;">
     <Position X="6.5" Y="7.75" Width="2" />
     <Compartments>
       <Compartment Name="Methods" Collapsed="true" />
@@ -219,7 +223,7 @@
   <Class Name="Terminal.Gui.AnsiResponseParserBase" Collapsed="true">
     <Position X="20.5" Y="9" Width="2" />
     <TypeIdentifier>
-      <HashCode>UACASAAAEICQALAAQAAACAAAIAIAAABAAQIAJgAQACQ=</HashCode>
+      <HashCode>UAiASAAAEICQALAAQAAAKAAAoAIAAABAAQIAJgAQASU=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -231,33 +235,17 @@
   <Class Name="Terminal.Gui.MouseInterpreter">
     <Position X="13.25" Y="10.5" Width="1.75" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAAABAAIAAkAAACAAICgIAAAAAAAAABAAAhg=</HashCode>
+      <HashCode>AAAABAAAAAAAAAAAAgAAAAAAACAAAAAAAAUAAAAIAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\MouseInterpreter.cs</FileName>
     </TypeIdentifier>
-    <ShowAsAssociation>
-      <Field Name="_viewFinder" />
-    </ShowAsAssociation>
     <ShowAsCollectionAssociation>
-      <Field Name="_ongoingSequences" />
-    </ShowAsCollectionAssociation>
-  </Class>
-  <Class Name="Terminal.Gui.MouseButtonSequence">
-    <Position X="16.75" Y="12" Width="2" />
-    <TypeIdentifier>
-      <HashCode>IAAABAAAAACAAAAAAgAAAAAAICAAAEQAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\MouseButtonSequence.cs</FileName>
-    </TypeIdentifier>
-    <ShowAsAssociation>
-      <Field Name="_viewFinder" />
-    </ShowAsAssociation>
-    <ShowAsCollectionAssociation>
-      <Property Name="MouseStates" />
+      <Field Name="_buttonStates" />
     </ShowAsCollectionAssociation>
   </Class>
   <Class Name="Terminal.Gui.MouseButtonStateEx">
-    <Position X="20" Y="12" Width="2" />
+    <Position X="16.5" Y="11.75" Width="2" />
     <TypeIdentifier>
-      <HashCode>AEAAAAAAAAAAAAggAAAAAAAAAACJAAAAAoAAAAAEAAA=</HashCode>
+      <HashCode>AAAAAAAAAEwAIAAAAAAAAAAAABCAAAAAAAAABAAEAAg=</HashCode>
       <FileName>ConsoleDrivers\V2\MouseButtonState.cs</FileName>
     </TypeIdentifier>
   </Class>
@@ -294,7 +282,7 @@
   <Class Name="Terminal.Gui.AnsiResponseParser" Collapsed="true">
     <Position X="21.75" Y="10" Width="1.75" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAgACBAAAAAABAAAAA=</HashCode>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAgACBAAAAACBAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
     </TypeIdentifier>
   </Class>
@@ -308,7 +296,7 @@
   <Class Name="Terminal.Gui.ApplicationImpl" Collapsed="true">
     <Position X="2.75" Y="4.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AABAAAAAAAAIAgQQAAAAAQAAAAAAAAAAQAAKgACAAAI=</HashCode>
+      <HashCode>AABAAAAAIAAIAgQQAAAAAQAAAAAAAAAAQAAKgAAAAAI=</HashCode>
       <FileName>Application\ApplicationImpl.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -316,10 +304,10 @@
     </ShowAsAssociation>
     <Lollipop Position="0.2" />
   </Class>
-  <Class Name="Terminal.Gui.ConsoleDrivers.V2.ApplicationV2" Collapsed="true">
+  <Class Name="Terminal.Gui.ApplicationV2" Collapsed="true">
     <Position X="4.75" Y="4.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>QAAAAAgABAAIAgAQAAAAAQAAAAAAgAEAAAAKgIAAEgI=</HashCode>
+      <HashCode>QAAAAAgABAEIAgAQAAAAAQAAAAAAgAEAAAAKgIAAEgI=</HashCode>
       <FileName>ConsoleDrivers\V2\ApplicationV2.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -369,7 +357,7 @@
       <FileName>ConsoleDrivers\V2\IInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.ConsoleDrivers.V2.IViewFinder">
+  <Interface Name="Terminal.Gui.IViewFinder">
     <Position X="16.75" Y="10.25" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
@@ -403,17 +391,10 @@
   <Interface Name="Terminal.Gui.IMainLoopCoordinator" Collapsed="true">
     <Position X="6.5" Y="0.5" Width="2" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQIAAAAAQAA=</HashCode>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQIAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IMainLoopCoordinator.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.ITimedEvents" Collapsed="true">
-    <Position X="12.25" Y="5.75" Width="1.5" />
-    <TypeIdentifier>
-      <HashCode>BAAAIAAAAQAAAAAQACAAAIBAAQAAAAAAAAAIgAAAAAA=</HashCode>
-      <FileName>Application\TimedEvents.cs</FileName>
-    </TypeIdentifier>
-  </Interface>
   <Interface Name="Terminal.Gui.IWindowSizeMonitor" Collapsed="true">
     <Position X="13.25" Y="14.25" Width="1.75" />
     <TypeIdentifier>
@@ -421,6 +402,13 @@
       <FileName>ConsoleDrivers\V2\IMainLoop.cs</FileName>
     </TypeIdentifier>
   </Interface>
+  <Interface Name="Terminal.Gui.ITimedEvents" Collapsed="true">
+    <Position X="14.5" Y="4.75" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>BAAAIAAAAQAAAAAQACAAAIBAAQAAAAAAAAAIgAAAAAA=</HashCode>
+      <FileName>Application\ITimedEvents.cs</FileName>
+    </TypeIdentifier>
+  </Interface>
   <Enum Name="Terminal.Gui.AnsiResponseParserState">
     <Position X="20.5" Y="7.25" Width="2" />
     <TypeIdentifier>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowSizeMonitor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowSizeMonitor.cs
index ca4f568f4c..18707fa83a 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowSizeMonitor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowSizeMonitor.cs
@@ -6,7 +6,7 @@ internal class WindowSizeMonitor : IWindowSizeMonitor
 {
     private readonly IConsoleOutput _consoleOut;
     private readonly IOutputBuffer _outputBuffer;
-    private Size _lastSize = new Size (0,0);
+    private Size _lastSize = new (0, 0);
 
     /// <summary>Invoked when the terminal's size changed. The new size of the terminal is provided.</summary>
     public event EventHandler<SizeChangedEventArgs> SizeChanging;
@@ -17,20 +17,21 @@ public WindowSizeMonitor (IConsoleOutput consoleOut, IOutputBuffer outputBuffer)
         _outputBuffer = outputBuffer;
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public bool Poll ()
     {
-        var size = _consoleOut.GetWindowSize ();
-
+        Size size = _consoleOut.GetWindowSize ();
 
         if (size != _lastSize)
         {
             Logging.Logger.LogInformation ($"Console size changes from '{_lastSize}' to {size}");
             _outputBuffer.SetWindowSize (size.Width, size.Height);
             _lastSize = size;
-            SizeChanging?.Invoke (this,new (size));
+            SizeChanging?.Invoke (this, new (size));
+
             return true;
         }
+
         return false;
     }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
index 57c871e334..423c6f06a0 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInput.cs
@@ -33,7 +33,6 @@ out uint lpNumberOfEventsRead
     [DllImport ("kernel32.dll")]
     private static extern bool SetConsoleMode (nint hConsoleHandle, uint dwMode);
 
-
     private readonly uint _originalConsoleMode;
 
     public WindowsInput ()
@@ -48,7 +47,7 @@ public WindowsInput ()
         newConsoleMode |= (uint)(ConsoleModes.EnableMouseInput | ConsoleModes.EnableExtendedFlags);
         newConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode;
         newConsoleMode &= ~(uint)ConsoleModes.EnableProcessedInput;
-        SetConsoleMode (_inputHandle,newConsoleMode);
+        SetConsoleMode (_inputHandle, newConsoleMode);
     }
 
     protected override bool Peek ()
@@ -74,6 +73,7 @@ protected override bool Peek ()
         {
             // Optionally log the exception
             Console.WriteLine ($"Error in Peek: {ex.Message}");
+
             return false;
         }
         finally
@@ -82,6 +82,7 @@ protected override bool Peek ()
             Marshal.FreeHGlobal (pRecord);
         }
     }
+
     protected override IEnumerable<InputRecord> Read ()
     {
         const int bufferSize = 1;
@@ -108,8 +109,6 @@ protected override IEnumerable<InputRecord> Read ()
             Marshal.FreeHGlobal (pRecord);
         }
     }
-    public override void Dispose ()
-    {
-        SetConsoleMode (_inputHandle, _originalConsoleMode);
-    }
-}
\ No newline at end of file
+
+    public override void Dispose () { SetConsoleMode (_inputHandle, _originalConsoleMode); }
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
index 94480ec12e..ce507794cf 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
@@ -3,22 +3,22 @@
 using static Terminal.Gui.WindowsConsole;
 
 namespace Terminal.Gui;
+
 using InputRecord = InputRecord;
 
 /// <summary>
-/// Input processor for <see cref="WindowsInput"/>, deals in <see cref="WindowsConsole.InputRecord"/> stream.
+///     Input processor for <see cref="WindowsInput"/>, deals in <see cref="WindowsConsole.InputRecord"/> stream.
 /// </summary>
 internal class WindowsInputProcessor : InputProcessor<InputRecord>
 {
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public WindowsInputProcessor (ConcurrentQueue<InputRecord> inputBuffer) : base (inputBuffer) { }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     protected override void Process (InputRecord inputEvent)
     {
         switch (inputEvent.EventType)
         {
-
             case EventType.Key:
 
                 // TODO: For now ignore keyup because ANSI comes in as down+up which is confusing to try and parse/pair these things up
@@ -65,10 +65,11 @@ protected override void Process (InputRecord inputEvent)
         }
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     protected override void ProcessAfterParsing (InputRecord input)
     {
         Key key;
+
         if (input.KeyEvent.UnicodeChar == '\0')
         {
             key = input.KeyEvent.wVirtualKeyCode switch
@@ -103,7 +104,7 @@ protected override void ProcessAfterParsing (InputRecord input)
                       VK.F22 => Key.F22,
                       VK.F23 => Key.F23,
                       VK.F24 => Key.F24,
-                      _ => (Key)'\0'
+                      _ => '\0'
                   };
         }
         else
@@ -120,9 +121,9 @@ private MouseEventArgs ToDriverMouse (MouseEventRecord e)
         var result = new MouseEventArgs
         {
             Position = new (e.MousePosition.X, e.MousePosition.Y),
-            //Wrong but for POC ok
-            Flags = e.ButtonState.HasFlag (WindowsConsole.ButtonState.Button1Pressed) ? MouseFlags.Button1Pressed : MouseFlags.None,
 
+            //Wrong but for POC ok
+            Flags = e.ButtonState.HasFlag (ButtonState.Button1Pressed) ? MouseFlags.Button1Pressed : MouseFlags.None
         };
 
         // TODO: Return keys too
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
index e08eecf9b9..82682ef4b5 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
@@ -1,13 +1,12 @@
 using System.ComponentModel;
 using System.Runtime.InteropServices;
-using System.Text;
 using Microsoft.Extensions.Logging;
 using static Terminal.Gui.WindowsConsole;
 
 namespace Terminal.Gui;
+
 internal class WindowsOutput : IConsoleOutput
 {
-
     [DllImport ("kernel32.dll", EntryPoint = "WriteConsole", SetLastError = true, CharSet = CharSet.Unicode)]
     private static extern bool WriteConsole (
         nint hConsoleOutput,
@@ -29,7 +28,6 @@ private static extern nint CreateConsoleScreenBuffer (
         nint screenBufferData
     );
 
-
     [DllImport ("kernel32.dll", SetLastError = true)]
     private static extern bool GetConsoleScreenBufferInfoEx (nint hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFOEX csbi);
 
@@ -48,18 +46,19 @@ private enum DesiredAccess : uint
     }
 
     internal static nint INVALID_HANDLE_VALUE = new (-1);
-    
+
     [DllImport ("kernel32.dll", SetLastError = true)]
     private static extern bool SetConsoleActiveScreenBuffer (nint Handle);
 
     [DllImport ("kernel32.dll")]
     private static extern bool SetConsoleCursorPosition (nint hConsoleOutput, Coord dwCursorPosition);
 
-    private nint _screenBuffer;
+    private readonly nint _screenBuffer;
 
     public WindowsOutput ()
     {
         Logging.Logger.LogInformation ($"Creating {nameof (WindowsOutput)}");
+
         _screenBuffer = CreateConsoleScreenBuffer (
                                                    DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
                                                    ShareMode.FileShareRead | ShareMode.FileShareWrite,
@@ -83,6 +82,7 @@ public WindowsOutput ()
             throw new Win32Exception (Marshal.GetLastWin32Error ());
         }
     }
+
     public void Write (string str)
     {
         if (!WriteConsole (_screenBuffer, str, (uint)str.Length, out uint _, nint.Zero))
@@ -91,10 +91,9 @@ public void Write (string str)
         }
     }
 
-
     public void Write (IOutputBuffer buffer)
     {
-        var outputBuffer = new ExtendedCharInfo [buffer.Rows * buffer.Cols];
+        ExtendedCharInfo [] outputBuffer = new ExtendedCharInfo [buffer.Rows * buffer.Cols];
 
         // TODO: probably do need this right?
         /*
@@ -106,7 +105,7 @@ public void Write (IOutputBuffer buffer)
         var bufferCoords = new Coord
         {
             X = (short)buffer.Cols, //Clip.Width,
-            Y = (short)buffer.Rows, //Clip.Height
+            Y = (short)buffer.Rows //Clip.Height
         };
 
         for (var row = 0; row < buffer.Rows; row++)
@@ -161,12 +160,14 @@ public void Write (IOutputBuffer buffer)
             Bottom = (short)buffer.Rows,
             Right = (short)buffer.Cols
         };
+
         //size, ExtendedCharInfo [] charInfoBuffer, Coord , SmallRect window,
         if (!WriteToConsole (
-                                           size: new (buffer.Cols, buffer.Rows),
-                                           charInfoBuffer: outputBuffer,
-                                           bufferSize: bufferCoords,
-                                           window: damageRegion, false))
+                             new (buffer.Cols, buffer.Rows),
+                             outputBuffer,
+                             bufferCoords,
+                             damageRegion,
+                             false))
         {
             int err = Marshal.GetLastWin32Error ();
 
@@ -199,15 +200,15 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord
 
             foreach (ExtendedCharInfo info in charInfoBuffer)
             {
-                ci [i++] = new CharInfo
+                ci [i++] = new()
                 {
-                    Char = new CharUnion { UnicodeChar = info.Char },
+                    Char = new() { UnicodeChar = info.Char },
                     Attributes =
                         (ushort)((int)info.Attribute.Foreground.GetClosestNamedColor16 () | ((int)info.Attribute.Background.GetClosestNamedColor16 () << 4))
                 };
             }
 
-            result = WriteConsoleOutput (_screenBuffer, ci, bufferSize, new Coord { X = window.Left, Y = window.Top }, ref window);
+            result = WriteConsoleOutput (_screenBuffer, ci, bufferSize, new() { X = window.Left, Y = window.Top }, ref window);
         }
         else
         {
@@ -254,7 +255,7 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord
             // supply console with the new content
             result = WriteConsole (_screenBuffer, s, (uint)s.Length, out uint _, nint.Zero);
 
-            foreach (var sixel in Application.Sixel)
+            foreach (SixelToRender sixel in Application.Sixel)
             {
                 SetCursorPosition ((short)sixel.ScreenPosition.X, (short)sixel.ScreenPosition.Y);
                 WriteConsole (_screenBuffer, sixel.SixelData, (uint)sixel.SixelData.Length, out uint _, nint.Zero);
@@ -288,10 +289,11 @@ public Size GetWindowSize ()
         Size sz = new (
                        csbi.srWindow.Right - csbi.srWindow.Left + 1,
                        csbi.srWindow.Bottom - csbi.srWindow.Top + 1);
+
         return sz;
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public void SetCursorVisibility (CursorVisibility visibility)
     {
         var sb = new StringBuilder ();
@@ -299,13 +301,10 @@ public void SetCursorVisibility (CursorVisibility visibility)
         Write (sb.ToString ());
     }
 
-    /// <inheritdoc />
-    public void SetCursorPosition (int col, int row)
-    {
-        SetConsoleCursorPosition (_screenBuffer, new Coord ((short)col, (short)row));
-    }
+    /// <inheritdoc/>
+    public void SetCursorPosition (int col, int row) { SetConsoleCursorPosition (_screenBuffer, new ((short)col, (short)row)); }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public void Dispose ()
     {
         if (_screenBuffer != nint.Zero)

From 3929feb1686c5f592fe37f0cfa6c42efc96c4620 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 28 Dec 2024 20:16:58 +0000
Subject: [PATCH 105/198] nullability

---
 Terminal.Gui/Application/ApplicationImpl.cs    | 2 +-
 Terminal.Gui/Application/IApplication.cs       | 2 +-
 Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs | 4 ++--
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/Terminal.Gui/Application/ApplicationImpl.cs b/Terminal.Gui/Application/ApplicationImpl.cs
index 85525feccc..1d5b4f5119 100644
--- a/Terminal.Gui/Application/ApplicationImpl.cs
+++ b/Terminal.Gui/Application/ApplicationImpl.cs
@@ -226,7 +226,7 @@ public virtual void Shutdown ()
     }
 
     /// <inheritdoc />
-    public virtual void RequestStop (Toplevel top)
+    public virtual void RequestStop (Toplevel? top)
     {
         top ??= Application.Top;
 
diff --git a/Terminal.Gui/Application/IApplication.cs b/Terminal.Gui/Application/IApplication.cs
index f31442b5fc..bdd51046fe 100644
--- a/Terminal.Gui/Application/IApplication.cs
+++ b/Terminal.Gui/Application/IApplication.cs
@@ -145,7 +145,7 @@ public T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? dri
     ///         property on the currently running <see cref="Toplevel"/> to false.
     ///     </para>
     /// </remarks>
-    void RequestStop (Toplevel top);
+    void RequestStop (Toplevel? top);
 
     /// <summary>Runs <paramref name="action"/> on the main UI loop thread</summary>
     /// <param name="action">the action to be invoked on the main processing thread.</param>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
index cbb71b2471..dc920068f2 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
@@ -84,7 +84,7 @@ public int Cols
 
     // 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.
-    public bool []? DirtyLines { get; set; }
+    public bool [] DirtyLines { get; set; } = [];
 
     // QUESTION: When non-full screen apps are supported, will this represent the app size, or will that be in Application?
     /// <summary>Gets the location and size of the terminal screen.</summary>
@@ -268,7 +268,7 @@ public void AddRune (Rune rune)
                         Contents [Row, Col].IsDirty = false;
                     }
 
-                    DirtyLines! [Row] = true;
+                    DirtyLines [Row] = true;
                 }
             }
         }

From db990560d0e591acce1a5d577fe01944953eb165 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 29 Dec 2024 09:49:31 +0000
Subject: [PATCH 106/198] xml doc and NRT

---
 Terminal.Gui/Application/ApplicationImpl.cs   | 14 +++++++-
 Terminal.Gui/Application/ITimedEvents.cs      | 24 +++++++++++++
 Terminal.Gui/Application/TimedEvents.cs       |  3 ++
 .../AnsiResponseParser/AnsiKeyboardParser.cs  | 10 ++++++
 .../AnsiResponseParser/AnsiResponseParser.cs  |  5 ++-
 .../AnsiResponseParser/IAnsiResponseParser.cs |  2 +-
 .../ConsoleDrivers/V2/ApplicationV2.cs        | 31 ++++++++++------
 .../ConsoleDrivers/V2/ConsoleInput.cs         |  4 +++
 .../ConsoleDrivers/V2/IConsoleInput.cs        |  7 ++++
 .../ConsoleDrivers/V2/IConsoleOutput.cs       | 32 +++++++++++++++++
 .../ConsoleDrivers/V2/IInputProcessor.cs      |  9 +++++
 Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs   | 35 +++++++++++++------
 .../ConsoleDrivers/V2/IMainLoopCoordinator.cs | 13 +++++++
 .../ConsoleDrivers/V2/IWindowSizeMonitor.cs   | 10 ++++++
 .../ConsoleDrivers/V2/InputProcessor.cs       |  1 +
 15 files changed, 174 insertions(+), 26 deletions(-)
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/IWindowSizeMonitor.cs

diff --git a/Terminal.Gui/Application/ApplicationImpl.cs b/Terminal.Gui/Application/ApplicationImpl.cs
index 1d5b4f5119..d3c693a062 100644
--- a/Terminal.Gui/Application/ApplicationImpl.cs
+++ b/Terminal.Gui/Application/ApplicationImpl.cs
@@ -266,6 +266,13 @@ public virtual void Invoke (Action action)
     /// <inheritdoc />
     public virtual void AddIdle (Func<bool> func)
     {
+        if(Application.MainLoop is null)
+        {
+            throw new Exception ("Cannot add idle before main loop is initialized");
+        }
+
+        // Yes in this case we cannot go direct via TimedEvents because legacy main loop
+        // has established behaviour to do other stuff too e.g. 'wake up'.
         Application.MainLoop.AddIdle (func);
 
     }
@@ -273,7 +280,12 @@ public virtual void AddIdle (Func<bool> func)
     /// <inheritdoc />
     public virtual object AddTimeout (TimeSpan time, Func<bool> callback)
     {
-        return Application.MainLoop?.TimedEvents.AddTimeout (time, callback) ?? null;
+        if (Application.MainLoop is null)
+        {
+            throw new Exception ("Cannot add timeout before main loop is initialized");
+        }
+
+        return Application.MainLoop.TimedEvents.AddTimeout (time, callback);
     }
 
     /// <inheritdoc />
diff --git a/Terminal.Gui/Application/ITimedEvents.cs b/Terminal.Gui/Application/ITimedEvents.cs
index 8d067af117..80a2769336 100644
--- a/Terminal.Gui/Application/ITimedEvents.cs
+++ b/Terminal.Gui/Application/ITimedEvents.cs
@@ -3,10 +3,26 @@
 
 namespace Terminal.Gui;
 
+/// <summary>
+/// Manages timers and idles
+/// </summary>
 public interface ITimedEvents
 {
+    /// <summary>
+    ///     Adds specified idle handler function to main iteration processing. The handler function will be called
+    ///     once per iteration of the main loop after other events have been handled.
+    /// </summary>
+    /// <param name="idleHandler"></param>
     void AddIdle (Func<bool> idleHandler);
+
+    /// <summary>
+    /// Runs all idle hooks
+    /// </summary>
     void LockAndRunIdles ();
+
+    /// <summary>
+    /// Runs all timeouts that are due
+    /// </summary>
     void LockAndRunTimers ();
 
     /// <summary>
@@ -42,8 +58,16 @@ public interface ITimedEvents
     /// </returns>
     bool RemoveTimeout (object token);
 
+    /// <summary>
+    /// Returns all currently registered idles. May not include
+    /// actively executing idles.
+    /// </summary>
     ReadOnlyCollection<Func<bool>> IdleHandlers { get;}
 
+    /// <summary>
+    /// Returns the next planned execution time (key - UTC ticks)
+    /// for each timeout that is not actively executing.
+    /// </summary>
     SortedList<long, Timeout> Timeouts { get; }
 
 
diff --git a/Terminal.Gui/Application/TimedEvents.cs b/Terminal.Gui/Application/TimedEvents.cs
index fec19e4ad0..cacc092e3b 100644
--- a/Terminal.Gui/Application/TimedEvents.cs
+++ b/Terminal.Gui/Application/TimedEvents.cs
@@ -3,6 +3,9 @@
 
 namespace Terminal.Gui;
 
+/// <summary>
+/// Handles timeouts and idles
+/// </summary>
 public class TimedEvents : ITimedEvents
 {
     internal List<Func<bool>> _idleHandlers = new ();
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
index 901cc5c4a1..ce0f0a6cc9 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
@@ -2,6 +2,10 @@
 
 namespace Terminal.Gui;
 
+/// <summary>
+/// Parses ansi escape sequence strings that describe keyboard activity e.g. cursor keys
+/// into <see cref="Key"/>.
+/// </summary>
 public class AnsiKeyboardParser
 {
     // Regex patterns for ANSI arrow keys (Up, Down, Left, Right)
@@ -36,5 +40,11 @@ public Key ProcessKeyboardInput (string input)
         return null;
     }
 
+    /// <summary>
+    /// Returns <see langword="true"/> if the given escape code
+    /// is a keyboard escape code (e.g. cursor key)
+    /// </summary>
+    /// <param name="cur">escape code</param>
+    /// <returns></returns>
     public bool IsKeyboard (string cur) { return _arrowKeyPattern.IsMatch (cur); }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
index 2a2cef7d5f..56604ac29a 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
@@ -15,12 +15,12 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
     /// <summary>
     ///     Event raised when mouse events are detected - requires setting <see cref="HandleMouse"/> to true
     /// </summary>
-    public event EventHandler<MouseEventArgs> Mouse;
+    public event EventHandler<MouseEventArgs>? Mouse;
 
     /// <summary>
     ///     Event raised when keyboard event is detected (e.g. cursors) - requires setting <see cref="HandleKeyboard"/>
     /// </summary>
-    public event Action<object, Key> Keyboard;
+    public event Action<object, Key>? Keyboard;
 
     /// <summary>
     ///     True to explicitly handle mouse escape sequences by passing them to <see cref="Mouse"/> event.
@@ -84,7 +84,6 @@ protected set
                                                                   'l', 'm', 'n',
                                                                   'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
                                                               ]);
-
     protected AnsiResponseParserBase (IHeld heldContent) { _heldContent = heldContent; }
 
     protected void ResetState ()
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/IAnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/IAnsiResponseParser.cs
index dbcd16b954..f0424711b2 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/IAnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/IAnsiResponseParser.cs
@@ -3,7 +3,7 @@ namespace Terminal.Gui;
 
 /// <summary>
 ///     When implemented in a derived class, allows watching an input stream of characters
-///     (i.e. console input) for ANSI response sequences.
+///     (i.e. console input) for ANSI response sequences (mouse input, cursor, query responses etc.).
 /// </summary>
 public interface IAnsiResponseParser
 {
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index af6020ea4c..7b7784ddeb 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -1,5 +1,6 @@
 #nullable enable
 using System.Collections.Concurrent;
+using System.Diagnostics;
 using Microsoft.Extensions.Logging;
 
 namespace Terminal.Gui;
@@ -14,10 +15,14 @@ public class ApplicationV2 : ApplicationImpl
     private readonly Func<IConsoleOutput> _netOutputFactory;
     private readonly Func<IWindowsInput> _winInputFactory;
     private readonly Func<IConsoleOutput> _winOutputFactory;
-    private IMainLoopCoordinator _coordinator;
+    private IMainLoopCoordinator? _coordinator;
     private string? _driverName;
     public ITimedEvents TimedEvents { get; } = new TimedEvents ();
 
+    /// <summary>
+    /// Creates anew instance of the Application backend. The provided
+    /// factory methods will be used on Init calls to get things booted.
+    /// </summary>
     public ApplicationV2 () : this (
                                     () => new NetInput (),
                                     () => new NetOutput (),
@@ -70,19 +75,19 @@ private void CreateDriver (string? driverName)
 
         if (definetlyWin)
         {
-            CreateWindowsSubcomponents ();
+            _coordinator = CreateWindowsSubcomponents ();
         }
         else if (definetlyNet)
         {
-            CreateNetSubcomponents ();
+            _coordinator = CreateNetSubcomponents ();
         }
         else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
         {
-            CreateWindowsSubcomponents ();
+            _coordinator = CreateWindowsSubcomponents();
         }
         else
         {
-            CreateNetSubcomponents ();
+            _coordinator = CreateNetSubcomponents ();
         }
 
         _coordinator.StartAsync ().Wait ();
@@ -93,12 +98,12 @@ private void CreateDriver (string? driverName)
         }
     }
 
-    private void CreateWindowsSubcomponents ()
+    private IMainLoopCoordinator CreateWindowsSubcomponents ()
     {
         ConcurrentQueue<WindowsConsole.InputRecord> inputBuffer = new ConcurrentQueue<WindowsConsole.InputRecord> ();
         MainLoop<WindowsConsole.InputRecord> loop = new MainLoop<WindowsConsole.InputRecord> ();
 
-        _coordinator = new MainLoopCoordinator<WindowsConsole.InputRecord> (
+        return new MainLoopCoordinator<WindowsConsole.InputRecord> (
                                                                             TimedEvents,
                                                                             _winInputFactory,
                                                                             inputBuffer,
@@ -107,12 +112,12 @@ private void CreateWindowsSubcomponents ()
                                                                             loop);
     }
 
-    private void CreateNetSubcomponents ()
+    private IMainLoopCoordinator CreateNetSubcomponents ()
     {
         ConcurrentQueue<ConsoleKeyInfo> inputBuffer = new ConcurrentQueue<ConsoleKeyInfo> ();
         MainLoop<ConsoleKeyInfo> loop = new MainLoop<ConsoleKeyInfo> ();
 
-        _coordinator = new MainLoopCoordinator<ConsoleKeyInfo> (
+        return new MainLoopCoordinator<ConsoleKeyInfo> (
                                                                 TimedEvents,
                                                                 _netInputFactory,
                                                                 inputBuffer,
@@ -149,6 +154,10 @@ public override void Run (Toplevel view, Func<Exception, bool>? errorHandler = n
         // TODO : how to know when we are done?
         while (Application.TopLevels.TryPeek (out Toplevel? found) && found == view)
         {
+            if (_coordinator is null)
+            {
+                throw new Exception ($"{nameof (IMainLoopCoordinator)}inexplicably became null during Run");
+            }
             _coordinator.RunIteration ();
         }
     }
@@ -156,13 +165,13 @@ public override void Run (Toplevel view, Func<Exception, bool>? errorHandler = n
     /// <inheritdoc/>
     public override void Shutdown ()
     {
-        _coordinator.Stop ();
+        _coordinator?.Stop ();
         base.Shutdown ();
         Application.Driver = null;
     }
 
     /// <inheritdoc/>
-    public override void RequestStop (Toplevel top)
+    public override void RequestStop (Toplevel? top)
     {
         Logging.Logger.LogInformation ($"RequestStop '{top}'");
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
index f8786fa47e..c1633f754e 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
@@ -4,6 +4,10 @@
 
 namespace Terminal.Gui;
 
+/// <summary>
+/// Base class for reading console input in perpetual loop
+/// </summary>
+/// <typeparam name="T"></typeparam>
 public abstract class ConsoleInput<T> : IConsoleInput<T>
 {
     private ConcurrentQueue<T>? _inputBuffer;
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs b/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs
index d1fed29e5f..ee80a8081f 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs
@@ -2,6 +2,13 @@
 
 namespace Terminal.Gui;
 
+/// <summary>
+/// Interface for reading console input indefinitely -
+/// i.e. in an infinite loop. The class is responsible only
+/// for reading and storing the input in a thread safe input buffer
+/// which is then processed downstream e.g. on main UI thread.
+/// </summary>
+/// <typeparam name="T"></typeparam>
 public interface IConsoleInput<T> : IDisposable
 {
     /// <summary>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
index e50094b468..2b16dd8b8c 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
@@ -1,10 +1,42 @@
 namespace Terminal.Gui;
 
+/// <summary>
+/// Interface for writing console output
+/// </summary>
 public interface IConsoleOutput : IDisposable
 {
+    /// <summary>
+    /// Writes the given text directly to the console. Use to send
+    /// ansi escape codes etc. Regular screen output should use the
+    /// <see cref="IOutputBuffer"/> overload.
+    /// </summary>
+    /// <param name="text"></param>
     void Write (string text);
+
+    /// <summary>
+    /// Write the contents of the <paramref name="buffer"/> to the console
+    /// </summary>
+    /// <param name="buffer"></param>
     void Write (IOutputBuffer buffer);
+
+    /// <summary>
+    /// Returns the current size of the console window in rows/columns (i.e.
+    /// of characters not pixels).
+    /// </summary>
+    /// <returns></returns>
     public Size GetWindowSize ();
+
+    /// <summary>
+    /// Updates the console cursor (the blinking underscore) to be hidden,
+    /// visible etc.
+    /// </summary>
+    /// <param name="visibility"></param>
     void SetCursorVisibility (CursorVisibility visibility);
+
+    /// <summary>
+    /// Moves the console cursor to the given location.
+    /// </summary>
+    /// <param name="col"></param>
+    /// <param name="row"></param>
     void SetCursorPosition (int col, int row);
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
index dfb6be7a99..64fdb0f396 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
@@ -1,6 +1,11 @@
 #nullable enable
 namespace Terminal.Gui;
 
+/// <summary>
+/// Interface for main loop class that will process the queued input buffer contents.
+/// Is responsible for <see cref="ProcessQueue"/> and translating into common Terminal.Gui
+/// events and data models.
+/// </summary>
 public interface IInputProcessor
 {
     /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="KeyUp"/>.</summary>
@@ -44,5 +49,9 @@ public interface IInputProcessor
     /// </summary>
     void ProcessQueue ();
 
+    /// <summary>
+    /// Gets the response parser currently configured on this input processor.
+    /// </summary>
+    /// <returns></returns>
     public IAnsiResponseParser GetParser ();
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
index 14ca18600d..5d5f9ea407 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
@@ -3,14 +3,37 @@
 
 namespace Terminal.Gui;
 
+/// <summary>
+/// Interface for main loop that runs the core Terminal.Gui UI loop.
+/// </summary>
+/// <typeparam name="T"></typeparam>
 public interface IMainLoop<T> : IDisposable
 {
+    /// <summary>
+    /// Gets the class responsible for servicing user timeouts and idles
+    /// </summary>
     public ITimedEvents TimedEvents { get; }
+
+    /// <summary>
+    /// Gets the class responsible for writing final rendered output to the console
+    /// </summary>
     public IOutputBuffer OutputBuffer { get; }
+
+    /// <summary>
+    /// Gets the class responsible for processing buffered console input and translating
+    /// it into events on the UI thread.
+    /// </summary>
     public IInputProcessor InputProcessor { get; }
 
+    /// <summary>
+    /// Gets the class responsible for sending ANSI escape requests which expect a response
+    /// from the remote terminal e.g. Device Attribute Request
+    /// </summary>
     public AnsiRequestScheduler AnsiRequestScheduler { get; }
 
+    /// <summary>
+    /// Gets the class responsible for determining the current console size
+    /// </summary>
     public IWindowSizeMonitor WindowSizeMonitor { get; }
 
     /// <summary>
@@ -23,15 +46,7 @@ public interface IMainLoop<T> : IDisposable
     void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput);
 
     /// <summary>
-    ///     Perform a single iteration of the main loop without blocking anywhere.
+    ///     Perform a single iteration of the main loop without blocking/sleeping anywhere.
     /// </summary>
     public void Iteration ();
-}
-
-public interface IWindowSizeMonitor
-{
-    /// <summary>Invoked when the terminal's size changed. The new size of the terminal is provided.</summary>
-    event EventHandler<SizeChangedEventArgs>? SizeChanging;
-
-    bool Poll ();
-}
+}
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
index 9cbca9e3e8..077387a8bf 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
@@ -1,10 +1,23 @@
 namespace Terminal.Gui;
 
+/// <summary>
+/// Interface for main Terminal.Gui loop manager in v2.
+/// </summary>
 public interface IMainLoopCoordinator
 {
+    /// <summary>
+    /// Create all required subcomponents and boot strap.
+    /// </summary>
+    /// <returns></returns>
     public Task StartAsync ();
 
+    /// <summary>
+    /// Stop and dispose all subcomponents
+    /// </summary>
     public void Stop ();
 
+    /// <summary>
+    /// Run a single iteration of the main UI loop
+    /// </summary>
     void RunIteration ();
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IWindowSizeMonitor.cs b/Terminal.Gui/ConsoleDrivers/V2/IWindowSizeMonitor.cs
new file mode 100644
index 0000000000..b7f29ea7ee
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/IWindowSizeMonitor.cs
@@ -0,0 +1,10 @@
+#nullable enable
+namespace Terminal.Gui;
+
+public interface IWindowSizeMonitor
+{
+    /// <summary>Invoked when the terminal's size changed. The new size of the terminal is provided.</summary>
+    event EventHandler<SizeChangedEventArgs>? SizeChanging;
+
+    bool Poll ();
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index 43e6014f9a..f2947c0b01 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -13,6 +13,7 @@ public abstract class InputProcessor<T> : IInputProcessor
     internal AnsiResponseParser<T> Parser { get; } = new ();
     public ConcurrentQueue<T> InputBuffer { get; }
 
+    /// <inheritdoc/>
     public IAnsiResponseParser GetParser () { return Parser; }
 
     private MouseInterpreter _mouseInterpreter { get; } = new ();

From 513ef2a84b8ce19a71ce4f2cbed9644e457904a2 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 29 Dec 2024 10:15:36 +0000
Subject: [PATCH 107/198] Tidy up

---
 Terminal.Gui/Application/TimedEvents.cs       |  1 +
 .../ConsoleDrivers/V2/IOutputBuffer.cs        |  5 +++
 .../ConsoleDrivers/V2/InputProcessor.cs       | 42 ++++++++++++++++---
 3 files changed, 42 insertions(+), 6 deletions(-)

diff --git a/Terminal.Gui/Application/TimedEvents.cs b/Terminal.Gui/Application/TimedEvents.cs
index cacc092e3b..8325e6ed6e 100644
--- a/Terminal.Gui/Application/TimedEvents.cs
+++ b/Terminal.Gui/Application/TimedEvents.cs
@@ -116,6 +116,7 @@ public void LockAndRunTimers ()
 
     }
 
+    /// <inheritdoc/>
     public void LockAndRunIdles ()
     {
         bool runIdle;
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
index 4f1d99f0d1..89aa6c155a 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
@@ -1,6 +1,11 @@
 #nullable enable
 namespace Terminal.Gui;
 
+/// <summary>
+/// Describes the screen state that you want the console to be in.
+/// Is designed to be drawn to repeatedly then manifest into the console
+/// once at the end of iteration after all drawing is finalized.
+/// </summary>
 public interface IOutputBuffer
 {
     /// <summary>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index f2947c0b01..499daf2a41 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -1,8 +1,14 @@
 #nullable enable
 using System.Collections.Concurrent;
+using Microsoft.Extensions.Logging;
 
 namespace Terminal.Gui;
 
+/// <summary>
+/// Processes the queued input buffer contents - which must be of Type <typeparamref name="T"/>.
+/// Is responsible for <see cref="ProcessQueue"/> and translating into common Terminal.Gui
+/// events and data models.
+/// </summary>
 public abstract class InputProcessor<T> : IInputProcessor
 {
     /// <summary>
@@ -11,12 +17,16 @@ public abstract class InputProcessor<T> : IInputProcessor
     private readonly TimeSpan _escTimeout = TimeSpan.FromMilliseconds (50);
 
     internal AnsiResponseParser<T> Parser { get; } = new ();
+
+    /// <summary>
+    /// Input buffer which will be drained from by this class.
+    /// </summary>
     public ConcurrentQueue<T> InputBuffer { get; }
 
     /// <inheritdoc/>
     public IAnsiResponseParser GetParser () { return Parser; }
 
-    private MouseInterpreter _mouseInterpreter { get; } = new ();
+    private readonly MouseInterpreter _mouseInterpreter = new ();
 
     /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="KeyUp"/>.</summary>
     public event EventHandler<Key>? KeyDown;
@@ -59,7 +69,13 @@ public void OnMouseEvent (MouseEventArgs a)
         MouseEvent?.Invoke (this, a);
     }
 
-    public InputProcessor (ConcurrentQueue<T> inputBuffer)
+    /// <summary>
+    /// Constructs base instance including wiring all relevant
+    /// parser events and setting <see cref="InputBuffer"/> to
+    /// the provided thread safe input collection.
+    /// </summary>
+    /// <param name="inputBuffer"></param>
+    protected InputProcessor (ConcurrentQueue<T> inputBuffer)
     {
         InputBuffer = inputBuffer;
         Parser.HandleMouse = true;
@@ -74,7 +90,11 @@ public InputProcessor (ConcurrentQueue<T> inputBuffer)
                            };
 
         // TODO: For now handle all other escape codes with ignore
-        Parser.UnexpectedResponseHandler = str => { return true; };
+        Parser.UnexpectedResponseHandler = str =>
+                                           {
+                                               Logging.Logger.LogInformation ($"{nameof(InputProcessor<T>)} ignored unrecognized response '{str}'");
+                                               return true;
+                                           };
     }
 
     /// <summary>
@@ -89,13 +109,13 @@ public void ProcessQueue ()
             Process (input);
         }
 
-        foreach (T input in ShouldReleaseParserHeldKeys ())
+        foreach (T input in ReleaseParserHeldKeysIfStale ())
         {
             ProcessAfterParsing (input);
         }
     }
 
-    public IEnumerable<T> ShouldReleaseParserHeldKeys ()
+    private IEnumerable<T> ReleaseParserHeldKeysIfStale ()
     {
         if (Parser.State == AnsiResponseParserState.ExpectingBracket && DateTime.Now - Parser.StateChangedAt > _escTimeout)
         {
@@ -105,7 +125,17 @@ public IEnumerable<T> ShouldReleaseParserHeldKeys ()
         return [];
     }
 
-    protected abstract void Process (T result);
+    /// <summary>
+    /// Process the provided single input element <paramref name="input"/>. This method
+    /// is called sequentially for each value read from <see cref="InputBuffer"/>.
+    /// </summary>
+    /// <param name="input"></param>
+    protected abstract void Process (T input);
 
+    /// <summary>
+    /// Process the provided single input element - short-circuiting the <see cref="Parser"/>
+    /// stage of the processing.
+    /// </summary>
+    /// <param name="input"></param>
     protected abstract void ProcessAfterParsing (T input);
 }

From cfc59b9f197782b687f34e7d46449577050909c8 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 29 Dec 2024 11:19:31 +0000
Subject: [PATCH 108/198] tidy up and nrt

---
 .../V2/AlreadyResolvedException.cs            |  7 ------
 .../ConsoleDrivers/V2/ApplicationV2.cs        | 15 +++++++------
 .../ConsoleDrivers/V2/IOutputBuffer.cs        | 22 +++++++++++++++++++
 Terminal.Gui/ConsoleDrivers/V2/IViewFinder.cs | 19 ----------------
 .../ConsoleDrivers/V2/IWindowSizeMonitor.cs   |  9 ++++++++
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    | 16 ++++++++++++--
 .../V2/NotInitializedException.cs             | 17 ++++++++++++++
 7 files changed, 70 insertions(+), 35 deletions(-)
 delete mode 100644 Terminal.Gui/ConsoleDrivers/V2/AlreadyResolvedException.cs
 delete mode 100644 Terminal.Gui/ConsoleDrivers/V2/IViewFinder.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/NotInitializedException.cs

diff --git a/Terminal.Gui/ConsoleDrivers/V2/AlreadyResolvedException.cs b/Terminal.Gui/ConsoleDrivers/V2/AlreadyResolvedException.cs
deleted file mode 100644
index 68292cb426..0000000000
--- a/Terminal.Gui/ConsoleDrivers/V2/AlreadyResolvedException.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-#nullable enable
-namespace Terminal.Gui;
-
-internal class AlreadyResolvedException : Exception
-{
-    public AlreadyResolvedException () : base ("MouseButtonSequence already resolved") { }
-}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index 7b7784ddeb..daa6cd8efe 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -17,7 +17,8 @@ public class ApplicationV2 : ApplicationImpl
     private readonly Func<IConsoleOutput> _winOutputFactory;
     private IMainLoopCoordinator? _coordinator;
     private string? _driverName;
-    public ITimedEvents TimedEvents { get; } = new TimedEvents ();
+
+    private readonly ITimedEvents _timedEvents = new TimedEvents ();
 
     /// <summary>
     /// Creates anew instance of the Application backend. The provided
@@ -104,7 +105,7 @@ private IMainLoopCoordinator CreateWindowsSubcomponents ()
         MainLoop<WindowsConsole.InputRecord> loop = new MainLoop<WindowsConsole.InputRecord> ();
 
         return new MainLoopCoordinator<WindowsConsole.InputRecord> (
-                                                                            TimedEvents,
+                                                                            _timedEvents,
                                                                             _winInputFactory,
                                                                             inputBuffer,
                                                                             new WindowsInputProcessor (inputBuffer),
@@ -118,7 +119,7 @@ private IMainLoopCoordinator CreateNetSubcomponents ()
         MainLoop<ConsoleKeyInfo> loop = new MainLoop<ConsoleKeyInfo> ();
 
         return new MainLoopCoordinator<ConsoleKeyInfo> (
-                                                                TimedEvents,
+                                                                _timedEvents,
                                                                 _netInputFactory,
                                                                 inputBuffer,
                                                                 new NetInputProcessor (inputBuffer),
@@ -191,7 +192,7 @@ public override void RequestStop (Toplevel? top)
     /// <inheritdoc/>
     public override void Invoke (Action action)
     {
-        TimedEvents.AddIdle (
+        _timedEvents.AddIdle (
                              () =>
                              {
                                  action ();
@@ -202,11 +203,11 @@ public override void Invoke (Action action)
     }
 
     /// <inheritdoc/>
-    public override void AddIdle (Func<bool> func) { TimedEvents.AddIdle (func); }
+    public override void AddIdle (Func<bool> func) { _timedEvents.AddIdle (func); }
 
     /// <inheritdoc/>
-    public override object AddTimeout (TimeSpan time, Func<bool> callback) { return TimedEvents.AddTimeout (time, callback); }
+    public override object AddTimeout (TimeSpan time, Func<bool> callback) { return _timedEvents.AddTimeout (time, callback); }
 
     /// <inheritdoc/>
-    public override bool RemoveTimeout (object token) { return TimedEvents.RemoveTimeout (token); }
+    public override bool RemoveTimeout (object token) { return _timedEvents.RemoveTimeout (token); }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
index 89aa6c155a..01de7958c7 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
@@ -49,7 +49,16 @@ public interface IOutputBuffer
     /// </summary>
     public int Col { get; }
 
+    /// <summary>
+    /// The first cell index on left of screen - basically always 0.
+    /// Changing this may have unexpected consequences.
+    /// </summary>
     int Left { get; set; }
+
+    /// <summary>
+    /// The first cell index on top of screen - basically always 0.
+    /// Changing this may have unexpected consequences.
+    /// </summary>
     int Top { get; set; }
 
     /// <summary>
@@ -95,6 +104,19 @@ public interface IOutputBuffer
     /// <param name="rows"></param>
     void SetWindowSize (int cols, int rows);
 
+    /// <summary>
+    /// Fills the given <paramref name="rect"/> with the given
+    /// symbol using the currently selected attribute.
+    /// </summary>
+    /// <param name="rect"></param>
+    /// <param name="rune"></param>
     void FillRect (Rectangle rect, Rune rune);
+
+    /// <summary>
+    /// Fills the given <paramref name="rect"/> with the given
+    /// symbol using the currently selected attribute.
+    /// </summary>
+    /// <param name="rect"></param>
+    /// <param name="rune"></param>
     void FillRect (Rectangle rect, char rune);
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IViewFinder.cs b/Terminal.Gui/ConsoleDrivers/V2/IViewFinder.cs
deleted file mode 100644
index a4e5ac8636..0000000000
--- a/Terminal.Gui/ConsoleDrivers/V2/IViewFinder.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-namespace Terminal.Gui;
-
-public interface IViewFinder
-{
-    View GetViewAt (Point screenPosition, out Point viewportPoint);
-}
-
-internal class StaticViewFinder : IViewFinder
-{
-    /// <inheritdoc/>
-    public View GetViewAt (Point screenPosition, out Point viewportPoint)
-    {
-        View hit = View.GetViewsUnderMouse (screenPosition).LastOrDefault ();
-
-        viewportPoint = hit?.ScreenToViewport (screenPosition) ?? Point.Empty;
-
-        return hit;
-    }
-}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IWindowSizeMonitor.cs b/Terminal.Gui/ConsoleDrivers/V2/IWindowSizeMonitor.cs
index b7f29ea7ee..ba961cf444 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IWindowSizeMonitor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IWindowSizeMonitor.cs
@@ -1,10 +1,19 @@
 #nullable enable
 namespace Terminal.Gui;
 
+/// <summary>
+/// Interface for classes responsible for reporting the current
+/// size of the terminal window.
+/// </summary>
 public interface IWindowSizeMonitor
 {
     /// <summary>Invoked when the terminal's size changed. The new size of the terminal is provided.</summary>
     event EventHandler<SizeChangedEventArgs>? SizeChanging;
 
+    /// <summary>
+    /// Examines the current size of the terminal and raises <see cref="SizeChanging"/> if it is different
+    /// from last inspection.
+    /// </summary>
+    /// <returns></returns>
     bool Poll ();
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 2bd580817a..1f8dd71300 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -1,4 +1,5 @@
-using System.Collections.Concurrent;
+#nullable enable
+using System.Collections.Concurrent;
 using System.Diagnostics;
 using System.Diagnostics.Metrics;
 
@@ -7,9 +8,20 @@ namespace Terminal.Gui;
 /// <inheritdoc/>
 public class MainLoop<T> : IMainLoop<T>
 {
+    private ITimedEvents? _timedEvents;
+
     /// <inheritdoc/>
-    public ITimedEvents TimedEvents { get; private set; }
+    public ITimedEvents TimedEvents
+    {
+        get => _timedEvents ?? throw new NotInitializedException(nameof(TimedEvents));
+        private set => _timedEvents = value;
+    }
 
+    /// <summary>
+    /// The input events thread-safe collection. This is populated on separate
+    /// thread by a <see cref="IConsoleInput{T}"/>. Is drained as part of each
+    /// <see cref="Iteration"/>
+    /// </summary>
     public ConcurrentQueue<T> InputBuffer { get; private set; } = new ();
 
     public IInputProcessor InputProcessor { get; private set; }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NotInitializedException.cs b/Terminal.Gui/ConsoleDrivers/V2/NotInitializedException.cs
new file mode 100644
index 0000000000..29651a9b6c
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/NotInitializedException.cs
@@ -0,0 +1,17 @@
+namespace Terminal.Gui;
+
+/// <summary>
+/// Thrown when user code attempts to access a property or perform a method
+/// that is only supported after Initialization e.g. of an <see cref="IMainLoop{T}"/>
+/// </summary>
+public class NotInitializedException : Exception
+{
+    /// <summary>
+    /// Creates a new instance of the exception indicating that the class
+    /// <paramref name="memberName"/> cannot be used until owner is initialized.
+    /// </summary>
+    /// <param name="memberName">Property or method name</param>
+    public NotInitializedException (string memberName):base($"{memberName} cannot be accessed before Initialization")
+    {
+    }
+}

From f97812b31a97753d858a8d5d37846eca333adf0d Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 29 Dec 2024 11:20:32 +0000
Subject: [PATCH 109/198] Add todo

---
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 1f8dd71300..8ea9c98548 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -17,12 +17,14 @@ public ITimedEvents TimedEvents
         private set => _timedEvents = value;
     }
 
+    // TODO: follow above pattern for others too
+
     /// <summary>
     /// The input events thread-safe collection. This is populated on separate
     /// thread by a <see cref="IConsoleInput{T}"/>. Is drained as part of each
     /// <see cref="Iteration"/>
     /// </summary>
-    public ConcurrentQueue<T> InputBuffer { get; private set; } = new ();
+    public ConcurrentQueue<T> InputBuffer { get; private set; }
 
     public IInputProcessor InputProcessor { get; private set; }
 

From c800fa64fbf6d6e74af33e865da3271e1dbfc65e Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 29 Dec 2024 11:30:54 +0000
Subject: [PATCH 110/198] Fix unit test

---
 Terminal.Gui/Application/ApplicationImpl.cs               | 4 ++--
 Terminal.Gui/ConsoleDrivers/V2/NotInitializedException.cs | 7 +++++++
 UnitTests/View/Layout/LayoutTests.cs                      | 1 +
 3 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/Terminal.Gui/Application/ApplicationImpl.cs b/Terminal.Gui/Application/ApplicationImpl.cs
index d3c693a062..b6b8f91c42 100644
--- a/Terminal.Gui/Application/ApplicationImpl.cs
+++ b/Terminal.Gui/Application/ApplicationImpl.cs
@@ -268,7 +268,7 @@ public virtual void AddIdle (Func<bool> func)
     {
         if(Application.MainLoop is null)
         {
-            throw new Exception ("Cannot add idle before main loop is initialized");
+            throw new NotInitializedException ("Cannot add idle before main loop is initialized");
         }
 
         // Yes in this case we cannot go direct via TimedEvents because legacy main loop
@@ -282,7 +282,7 @@ public virtual object AddTimeout (TimeSpan time, Func<bool> callback)
     {
         if (Application.MainLoop is null)
         {
-            throw new Exception ("Cannot add timeout before main loop is initialized");
+            throw new NotInitializedException ("Cannot add timeout before main loop is initialized", null);
         }
 
         return Application.MainLoop.TimedEvents.AddTimeout (time, callback);
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NotInitializedException.cs b/Terminal.Gui/ConsoleDrivers/V2/NotInitializedException.cs
index 29651a9b6c..9abc485211 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NotInitializedException.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NotInitializedException.cs
@@ -14,4 +14,11 @@ public class NotInitializedException : Exception
     public NotInitializedException (string memberName):base($"{memberName} cannot be accessed before Initialization")
     {
     }
+
+    /// <summary>
+    /// Creates a new instance of the exception with the full message/inner exception.
+    /// </summary>
+    /// <param name="msg"></param>
+    /// <param name="innerException"></param>
+    public NotInitializedException (string msg, Exception innerException) :base(msg,innerException){}
 }
diff --git a/UnitTests/View/Layout/LayoutTests.cs b/UnitTests/View/Layout/LayoutTests.cs
index d1cde695d7..2d5e1f65d3 100644
--- a/UnitTests/View/Layout/LayoutTests.cs
+++ b/UnitTests/View/Layout/LayoutTests.cs
@@ -5,6 +5,7 @@ namespace Terminal.Gui.LayoutTests;
 public class LayoutTests (ITestOutputHelper _output) : TestsAllViews
 {
     [Theory]
+    [AutoInitShutdown] // Required for spinner view that wants to register timeouts
     [MemberData (nameof (AllViewTypes))]
     public void AllViews_Layout_Does_Not_Draw (Type viewType)
     {

From 2a7b3913cdd18b7cef4634cde601c291ac90c840 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 29 Dec 2024 11:44:48 +0000
Subject: [PATCH 111/198] Fix for unit tests

---
 UnitTests/View/Draw/AllViewsDrawTests.cs      | 1 +
 UnitTests/View/Keyboard/KeyboardEventTests.cs | 2 ++
 UnitTests/View/Layout/LayoutTests.cs          | 2 +-
 UnitTests/View/Mouse/MouseTests.cs            | 2 ++
 UnitTests/Views/AllViewsTests.cs              | 5 +++++
 5 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/UnitTests/View/Draw/AllViewsDrawTests.cs b/UnitTests/View/Draw/AllViewsDrawTests.cs
index fa865068bf..77e02a86a1 100644
--- a/UnitTests/View/Draw/AllViewsDrawTests.cs
+++ b/UnitTests/View/Draw/AllViewsDrawTests.cs
@@ -5,6 +5,7 @@ namespace Terminal.Gui.LayoutTests;
 public class AllViewsDrawTests (ITestOutputHelper _output) : TestsAllViews
 {
     [Theory]
+    [SetupFakeDriver] // Required for spinner view that wants to register timeouts
     [MemberData (nameof (AllViewTypes))]
     public void AllViews_Draw_Does_Not_Layout (Type viewType)
     {
diff --git a/UnitTests/View/Keyboard/KeyboardEventTests.cs b/UnitTests/View/Keyboard/KeyboardEventTests.cs
index fa842d50fc..7cbbcaa8fa 100644
--- a/UnitTests/View/Keyboard/KeyboardEventTests.cs
+++ b/UnitTests/View/Keyboard/KeyboardEventTests.cs
@@ -11,6 +11,7 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews
     ///     events: KeyDown and KeyDownNotHandled. Note that KeyUp is independent.
     /// </summary>
     [Theory]
+    [SetupFakeDriver] // Required for spinner view that wants to register timeouts
     [MemberData (nameof (AllViewTypes))]
     public void AllViews_NewKeyDownEvent_All_EventsFire (Type viewType)
     {
@@ -53,6 +54,7 @@ public void AllViews_NewKeyDownEvent_All_EventsFire (Type viewType)
     ///     KeyUp
     /// </summary>
     [Theory]
+    [SetupFakeDriver] // Required for spinner view that wants to register timeouts
     [MemberData (nameof (AllViewTypes))]
     public void AllViews_NewKeyUpEvent_All_EventsFire (Type viewType)
     {
diff --git a/UnitTests/View/Layout/LayoutTests.cs b/UnitTests/View/Layout/LayoutTests.cs
index 2d5e1f65d3..57a7d90c59 100644
--- a/UnitTests/View/Layout/LayoutTests.cs
+++ b/UnitTests/View/Layout/LayoutTests.cs
@@ -5,7 +5,7 @@ namespace Terminal.Gui.LayoutTests;
 public class LayoutTests (ITestOutputHelper _output) : TestsAllViews
 {
     [Theory]
-    [AutoInitShutdown] // Required for spinner view that wants to register timeouts
+    [SetupFakeDriver] // Required for spinner view that wants to register timeouts
     [MemberData (nameof (AllViewTypes))]
     public void AllViews_Layout_Does_Not_Draw (Type viewType)
     {
diff --git a/UnitTests/View/Mouse/MouseTests.cs b/UnitTests/View/Mouse/MouseTests.cs
index ab23aead12..9c91735ac4 100644
--- a/UnitTests/View/Mouse/MouseTests.cs
+++ b/UnitTests/View/Mouse/MouseTests.cs
@@ -154,6 +154,7 @@ public void NewMouseEvent_Invokes_MouseEvent_Properly ()
     }
 
     [Theory]
+    [SetupFakeDriver] // Required for spinner view that wants to register timeouts
     [MemberData (nameof (AllViewTypes))]
     public void AllViews_NewMouseEvent_Enabled_False_Does_Not_Set_Handled (Type viewType)
     {
@@ -173,6 +174,7 @@ public void AllViews_NewMouseEvent_Enabled_False_Does_Not_Set_Handled (Type view
     }
 
     [Theory]
+    [SetupFakeDriver] // Required for spinner view that wants to register timeouts
     [MemberData (nameof (AllViewTypes))]
     public void AllViews_NewMouseEvent_Clicked_Enabled_False_Does_Not_Set_Handled (Type viewType)
     {
diff --git a/UnitTests/Views/AllViewsTests.cs b/UnitTests/Views/AllViewsTests.cs
index bc20443618..2a18b9638d 100644
--- a/UnitTests/Views/AllViewsTests.cs
+++ b/UnitTests/Views/AllViewsTests.cs
@@ -12,6 +12,7 @@ public class AllViewsTests (ITestOutputHelper output) : TestsAllViews
 
     [Theory]
     [MemberData (nameof (AllViewTypes))]
+    [SetupFakeDriver] // Required for spinner view that wants to register timeouts
     public void AllViews_Center_Properly (Type viewType)
     {
         var view = (View)CreateInstanceIfNotGeneric (viewType);
@@ -62,6 +63,7 @@ public void AllViews_Center_Properly (Type viewType)
 
     [Theory]
     [MemberData (nameof (AllViewTypes))]
+    [SetupFakeDriver] // Required for spinner view that wants to register timeouts
     public void AllViews_Tests_All_Constructors (Type viewType)
     {
         Assert.True (Test_All_Constructors_Of_Type (viewType));
@@ -97,6 +99,7 @@ public bool Test_All_Constructors_Of_Type (Type type)
 
     [Theory]
     [MemberData (nameof (AllViewTypes))]
+    [SetupFakeDriver] // Required for spinner view that wants to register timeouts
     public void AllViews_Command_Select_Raises_Selecting (Type viewType)
     {
         var view = (View)CreateInstanceIfNotGeneric (viewType);
@@ -131,6 +134,7 @@ public void AllViews_Command_Select_Raises_Selecting (Type viewType)
     }
 
     [Theory]
+    [SetupFakeDriver] // Required for spinner view that wants to register timeouts
     [MemberData (nameof (AllViewTypes))]
     public void AllViews_Command_Accept_Raises_Accepted (Type viewType)
     {
@@ -168,6 +172,7 @@ public void AllViews_Command_Accept_Raises_Accepted (Type viewType)
 
     [Theory]
     [MemberData (nameof (AllViewTypes))]
+    [SetupFakeDriver] // Required for spinner view that wants to register timeouts
     public void AllViews_Command_HotKey_Raises_HandlingHotKey (Type viewType)
     {
         var view = (View)CreateInstanceIfNotGeneric (viewType);

From d9a05c791dfd81eef71fb090313fc30a8ffd821f Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 29 Dec 2024 17:34:39 +0000
Subject: [PATCH 112/198] unit test fixes

---
 Terminal.Gui/Application/Application.Run.cs |  2 +-
 UnitTests/View/Draw/AllViewsDrawTests.cs    |  2 ++
 UnitTests/View/Layout/LayoutTests.cs        |  4 ++++
 UnitTests/Views/AllViewsTests.cs            | 12 ++++++++++++
 4 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs
index a70c372802..fc6819aed1 100644
--- a/Terminal.Gui/Application/Application.Run.cs
+++ b/Terminal.Gui/Application/Application.Run.cs
@@ -436,7 +436,7 @@ public static void LayoutAndDraw (bool forceDraw = false)
 
     /// <summary>The <see cref="MainLoop"/> driver for the application</summary>
     /// <value>The main loop.</value>
-    internal static MainLoop? MainLoop { get; private set; }
+    internal static MainLoop? MainLoop { get; set; }
 
     /// <summary>
     ///     Set to true to cause <see cref="End"/> to be called after the first iteration. Set to false (the default) to
diff --git a/UnitTests/View/Draw/AllViewsDrawTests.cs b/UnitTests/View/Draw/AllViewsDrawTests.cs
index 77e02a86a1..64d6c543eb 100644
--- a/UnitTests/View/Draw/AllViewsDrawTests.cs
+++ b/UnitTests/View/Draw/AllViewsDrawTests.cs
@@ -10,6 +10,8 @@ public class AllViewsDrawTests (ITestOutputHelper _output) : TestsAllViews
     public void AllViews_Draw_Does_Not_Layout (Type viewType)
     {
         Application.ResetState (true);
+        // Required for spinner view that wants to register timeouts
+        Application.MainLoop = new MainLoop (new FakeMainLoop (Application.Driver));
 
         var view = (View)CreateInstanceIfNotGeneric (viewType);
 
diff --git a/UnitTests/View/Layout/LayoutTests.cs b/UnitTests/View/Layout/LayoutTests.cs
index 57a7d90c59..1b41105532 100644
--- a/UnitTests/View/Layout/LayoutTests.cs
+++ b/UnitTests/View/Layout/LayoutTests.cs
@@ -9,6 +9,10 @@ public class LayoutTests (ITestOutputHelper _output) : TestsAllViews
     [MemberData (nameof (AllViewTypes))]
     public void AllViews_Layout_Does_Not_Draw (Type viewType)
     {
+
+        // Required for spinner view that wants to register timeouts
+        Application.MainLoop = new MainLoop (new FakeMainLoop (Application.Driver));
+
         var view = (View)CreateInstanceIfNotGeneric (viewType);
 
         if (view == null)
diff --git a/UnitTests/Views/AllViewsTests.cs b/UnitTests/Views/AllViewsTests.cs
index 2a18b9638d..456c2be0bd 100644
--- a/UnitTests/Views/AllViewsTests.cs
+++ b/UnitTests/Views/AllViewsTests.cs
@@ -15,6 +15,9 @@ public class AllViewsTests (ITestOutputHelper output) : TestsAllViews
     [SetupFakeDriver] // Required for spinner view that wants to register timeouts
     public void AllViews_Center_Properly (Type viewType)
     {
+        // Required for spinner view that wants to register timeouts
+        Application.MainLoop = new MainLoop (new FakeMainLoop (Application.Driver));
+
         var view = (View)CreateInstanceIfNotGeneric (viewType);
         // See https://github.com/gui-cs/Terminal.Gui/issues/3156
 
@@ -102,6 +105,9 @@ public bool Test_All_Constructors_Of_Type (Type type)
     [SetupFakeDriver] // Required for spinner view that wants to register timeouts
     public void AllViews_Command_Select_Raises_Selecting (Type viewType)
     {
+        // Required for spinner view that wants to register timeouts
+        Application.MainLoop = new MainLoop (new FakeMainLoop (Application.Driver));
+
         var view = (View)CreateInstanceIfNotGeneric (viewType);
 
         if (view == null)
@@ -138,6 +144,9 @@ public void AllViews_Command_Select_Raises_Selecting (Type viewType)
     [MemberData (nameof (AllViewTypes))]
     public void AllViews_Command_Accept_Raises_Accepted (Type viewType)
     {
+        // Required for spinner view that wants to register timeouts
+        Application.MainLoop = new MainLoop (new FakeMainLoop (Application.Driver));
+
         var view = (View)CreateInstanceIfNotGeneric (viewType);
 
         if (view == null)
@@ -175,6 +184,9 @@ public void AllViews_Command_Accept_Raises_Accepted (Type viewType)
     [SetupFakeDriver] // Required for spinner view that wants to register timeouts
     public void AllViews_Command_HotKey_Raises_HandlingHotKey (Type viewType)
     {
+        // Required for spinner view that wants to register timeouts
+        Application.MainLoop = new MainLoop (new FakeMainLoop (Application.Driver));
+
         var view = (View)CreateInstanceIfNotGeneric (viewType);
 
         if (view == null)

From 263e66c5739fde2140a7e97e5ae06f556fa0be02 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 29 Dec 2024 17:52:03 +0000
Subject: [PATCH 113/198] Warnings and nrt

---
 .../ConsoleDrivers/V2/ConsoleInput.cs         |  5 +-
 Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs   | 26 ++++++----
 Terminal.Gui/ConsoleDrivers/V2/Logging.cs     | 17 ++++++
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    | 52 ++++++++++++++-----
 4 files changed, 74 insertions(+), 26 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
index c1633f754e..3c93608fc4 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
@@ -18,8 +18,7 @@ public abstract class ConsoleInput<T> : IConsoleInput<T>
     /// </summary>
     public Func<DateTime> Now { get; set; } = () => DateTime.Now;
 
-    private readonly Histogram<int> drainInputStream = Logging.Meter.CreateHistogram<int> ("Drain Input (ms)");
-
+    
     /// <inheritdoc/>
     public virtual void Dispose () { }
 
@@ -51,7 +50,7 @@ public void Run (CancellationToken token)
                 TimeSpan took = Now () - dt;
                 TimeSpan sleepFor = TimeSpan.FromMilliseconds (20) - took;
 
-                drainInputStream.Record (took.Milliseconds);
+                Logging.DrainInputStream.Record (took.Milliseconds);
 
                 if (sleepFor.Milliseconds > 0)
                 {
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
index 5d5f9ea407..73493eb7ea 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoop.cs
@@ -4,35 +4,40 @@
 namespace Terminal.Gui;
 
 /// <summary>
-/// Interface for main loop that runs the core Terminal.Gui UI loop.
+///     Interface for main loop that runs the core Terminal.Gui UI loop.
 /// </summary>
 /// <typeparam name="T"></typeparam>
 public interface IMainLoop<T> : IDisposable
 {
     /// <summary>
-    /// Gets the class responsible for servicing user timeouts and idles
+    ///     Gets the class responsible for servicing user timeouts and idles
     /// </summary>
     public ITimedEvents TimedEvents { get; }
 
     /// <summary>
-    /// Gets the class responsible for writing final rendered output to the console
+    ///     Gets the class responsible for writing final rendered output to the console
     /// </summary>
     public IOutputBuffer OutputBuffer { get; }
 
     /// <summary>
-    /// Gets the class responsible for processing buffered console input and translating
-    /// it into events on the UI thread.
+    ///     Class for writing output to the console.
+    /// </summary>
+    public IConsoleOutput Out { get; }
+
+    /// <summary>
+    ///     Gets the class responsible for processing buffered console input and translating
+    ///     it into events on the UI thread.
     /// </summary>
     public IInputProcessor InputProcessor { get; }
 
     /// <summary>
-    /// Gets the class responsible for sending ANSI escape requests which expect a response
-    /// from the remote terminal e.g. Device Attribute Request
+    ///     Gets the class responsible for sending ANSI escape requests which expect a response
+    ///     from the remote terminal e.g. Device Attribute Request
     /// </summary>
     public AnsiRequestScheduler AnsiRequestScheduler { get; }
 
     /// <summary>
-    /// Gets the class responsible for determining the current console size
+    ///     Gets the class responsible for determining the current console size
     /// </summary>
     public IWindowSizeMonitor WindowSizeMonitor { get; }
 
@@ -46,7 +51,8 @@ public interface IMainLoop<T> : IDisposable
     void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput);
 
     /// <summary>
-    ///     Perform a single iteration of the main loop without blocking/sleeping anywhere.
+    ///     Perform a single iteration of the main loop then blocks for a fixed length
+    ///     of time, this method is designed to be run in a loop.
     /// </summary>
     public void Iteration ();
-}
\ No newline at end of file
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/Logging.cs b/Terminal.Gui/ConsoleDrivers/V2/Logging.cs
index 300dac1c5a..b8034ebbe4 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/Logging.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/Logging.cs
@@ -27,4 +27,21 @@ public static class Logging
     ///     create your own static instrument e.g. CreateCounter, CreateHistogram etc
     /// </summary>
     internal static readonly Meter Meter = new ("Terminal.Gui");
+
+    /// <summary>
+    /// Metric for how long it takes each full iteration of the main loop to occur
+    /// </summary>
+    public static readonly Histogram<int> TotalIterationMetric = Logging.Meter.CreateHistogram<int> ("Iteration (ms)");
+
+    /// <summary>
+    /// Metric for how long it took to do the 'timeouts and invokes' section of main loop.
+    /// </summary>
+    public static readonly Histogram<int> IterationInvokesAndTimeouts = Logging.Meter.CreateHistogram<int> ("Invokes & Timers (ms)");
+
+    /// <summary>
+    /// Metric for how long it takes to read all available input from the input stream - at which
+    /// point input loop will sleep.
+    /// </summary>
+    public static readonly Histogram<int> DrainInputStream = Logging.Meter.CreateHistogram<int> ("Drain Input (ms)");
+
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 8ea9c98548..1bed07e671 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -1,7 +1,6 @@
 #nullable enable
 using System.Collections.Concurrent;
 using System.Diagnostics;
-using System.Diagnostics.Metrics;
 
 namespace Terminal.Gui;
 
@@ -9,6 +8,11 @@ namespace Terminal.Gui;
 public class MainLoop<T> : IMainLoop<T>
 {
     private ITimedEvents? _timedEvents;
+    private ConcurrentQueue<T>? _inputBuffer;
+    private IInputProcessor? _inputProcessor;
+    private IConsoleOutput? _out;
+    private AnsiRequestScheduler? _ansiRequestScheduler;
+    private IWindowSizeMonitor? _windowSizeMonitor;
 
     /// <inheritdoc/>
     public ITimedEvents TimedEvents
@@ -24,16 +28,42 @@ public ITimedEvents TimedEvents
     /// thread by a <see cref="IConsoleInput{T}"/>. Is drained as part of each
     /// <see cref="Iteration"/>
     /// </summary>
-    public ConcurrentQueue<T> InputBuffer { get; private set; }
+    public ConcurrentQueue<T> InputBuffer
+    {
+        get => _inputBuffer ?? throw new NotInitializedException (nameof (InputBuffer));
+        private set => _inputBuffer = value;
+    }
 
-    public IInputProcessor InputProcessor { get; private set; }
+    /// <inheritdoc/>
+    public IInputProcessor InputProcessor
+    {
+        get => _inputProcessor ?? throw new NotInitializedException (nameof (InputProcessor));
+        private set => _inputProcessor = value;
+    }
 
+    /// <inheritdoc/>
     public IOutputBuffer OutputBuffer { get; } = new OutputBuffer ();
 
-    public IConsoleOutput Out { get; private set; }
-    public AnsiRequestScheduler AnsiRequestScheduler { get; private set; }
+    /// <inheritdoc/>
+    public IConsoleOutput Out
+    {
+        get => _out ?? throw new NotInitializedException (nameof (Out));
+        private set => _out = value;
+    }
+
+    /// <inheritdoc/>
+    public AnsiRequestScheduler AnsiRequestScheduler
+    {
+        get => _ansiRequestScheduler ?? throw new NotInitializedException (nameof (AnsiRequestScheduler));
+        private set => _ansiRequestScheduler = value;
+    }
 
-    public IWindowSizeMonitor WindowSizeMonitor { get; private set; }
+    /// <inheritdoc/>
+    public IWindowSizeMonitor WindowSizeMonitor
+    {
+        get => _windowSizeMonitor ?? throw new NotInitializedException (nameof (WindowSizeMonitor));
+        private set => _windowSizeMonitor = value;
+    }
 
     /// <summary>
     ///     Determines how to get the current system type, adjust
@@ -41,10 +71,6 @@ public ITimedEvents TimedEvents
     /// </summary>
     public Func<DateTime> Now { get; set; } = () => DateTime.Now;
 
-    private static readonly Histogram<int> totalIterationMetric = Logging.Meter.CreateHistogram<int> ("Iteration (ms)");
-
-    private static readonly Histogram<int> iterationInvokesAndTimeouts = Logging.Meter.CreateHistogram<int> ("Invokes & Timers (ms)");
-
     public void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput)
     {
         InputBuffer = inputBuffer;
@@ -67,7 +93,7 @@ public void Iteration ()
         TimeSpan took = Now () - dt;
         TimeSpan sleepFor = TimeSpan.FromMilliseconds (50) - took;
 
-        totalIterationMetric.Record (took.Milliseconds);
+        Logging.TotalIterationMetric.Record (took.Milliseconds);
 
         if (sleepFor.Milliseconds > 0)
         {
@@ -75,7 +101,7 @@ public void Iteration ()
         }
     }
 
-    public void IterationImpl ()
+    internal void IterationImpl ()
     {
         InputProcessor.ProcessQueue ();
 
@@ -100,7 +126,7 @@ public void IterationImpl ()
 
         TimedEvents.LockAndRunIdles ();
 
-        iterationInvokesAndTimeouts.Record (swCallbacks.Elapsed.Milliseconds);
+        Logging.IterationInvokesAndTimeouts.Record (swCallbacks.Elapsed.Milliseconds);
     }
 
     private bool AnySubviewsNeedDrawn (View v)

From 75f0f0aae1cccc14af823f7037fc2a829509b882 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 29 Dec 2024 19:37:42 +0000
Subject: [PATCH 114/198] warnings and xmldoc

---
 .../ConsoleDrivers/V2/ApplicationV2.cs        |  5 ++++
 .../ConsoleDrivers/V2/IMainLoopCoordinator.cs |  4 ++-
 .../ConsoleDrivers/V2/InputProcessor.cs       |  4 +--
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    |  7 +++++
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  | 23 ++++++++++-----
 Terminal.Gui/ConsoleDrivers/V2/NetInput.cs    |  8 +++++
 Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs   | 24 +++++++--------
 .../ConsoleDrivers/V2/OutputBuffer.cs         |  3 +-
 .../ConsoleDrivers/V2/WindowsOutput.cs        | 29 ++++++++++---------
 9 files changed, 68 insertions(+), 39 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index daa6cd8efe..fd824ddfe0 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -1,6 +1,7 @@
 #nullable enable
 using System.Collections.Concurrent;
 using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
 using Microsoft.Extensions.Logging;
 
 namespace Terminal.Gui;
@@ -47,6 +48,8 @@ Func<IConsoleOutput> winOutputFactory
     }
 
     /// <inheritdoc/>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
     public override void Init (IConsoleDriver? driver = null, string? driverName = null)
     {
         if (!string.IsNullOrWhiteSpace (driverName))
@@ -128,6 +131,8 @@ private IMainLoopCoordinator CreateNetSubcomponents ()
     }
 
     /// <inheritdoc/>
+    [RequiresUnreferencedCode ("AOT")]
+    [RequiresDynamicCode ("AOT")]
     public override T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
     {
         var top = new T ();
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
index 077387a8bf..dd3598dbcb 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
@@ -11,8 +11,10 @@ public interface IMainLoopCoordinator
     /// <returns></returns>
     public Task StartAsync ();
 
+
     /// <summary>
-    /// Stop and dispose all subcomponents
+    /// Stops the input thread, blocking till it exits.
+    /// Call this method only from the main UI loop.
     /// </summary>
     public void Stop ();
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index 499daf2a41..9efd27e600 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -102,9 +102,7 @@ protected InputProcessor (ConcurrentQueue<T> inputBuffer)
     /// </summary>
     public void ProcessQueue ()
     {
-        // TODO: Esc timeout etc
-
-        while (InputBuffer.TryDequeue (out T input))
+        while (InputBuffer.TryDequeue (out T? input))
         {
             Process (input);
         }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 1bed07e671..d9b8fa46a1 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -71,6 +71,13 @@ public IWindowSizeMonitor WindowSizeMonitor
     /// </summary>
     public Func<DateTime> Now { get; set; } = () => DateTime.Now;
 
+    /// <summary>
+    /// Initializes the class with the provided subcomponents
+    /// </summary>
+    /// <param name="timedEvents"></param>
+    /// <param name="inputBuffer"></param>
+    /// <param name="inputProcessor"></param>
+    /// <param name="consoleOutput"></param>
     public void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput)
     {
         InputBuffer = inputBuffer;
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index 6b6fb33186..de6e64d78d 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -3,17 +3,25 @@
 
 namespace Terminal.Gui;
 
-public class MainLoopCoordinator<T> : IMainLoopCoordinator
+/// <summary>
+///<para>
+/// Handles creating the input loop thread and bootstrapping the
+/// <see cref="MainLoop{T}"/> that handles layout/drawing/events etc.
+/// </para>
+/// <para>This class is designed to be managed by <see cref="ApplicationV2"/></para>
+/// </summary>
+/// <typeparam name="T"></typeparam>
+internal class MainLoopCoordinator<T> : IMainLoopCoordinator
 {
     private readonly Func<IConsoleInput<T>> _inputFactory;
     private readonly ConcurrentQueue<T> _inputBuffer;
     private readonly IInputProcessor _inputProcessor;
     private readonly IMainLoop<T> _loop;
-    private readonly CancellationTokenSource tokenSource = new ();
+    private readonly CancellationTokenSource _tokenSource = new ();
     private readonly Func<IConsoleOutput> _outputFactory;
     private IConsoleInput<T> _input;
     private IConsoleOutput _output;
-    private readonly object oLockInitialization = new ();
+    private readonly object _oLockInitialization = new ();
     private ConsoleDriverFacade<T> _facade;
     private Task _inputTask;
     private readonly ITimedEvents _timedEvents;
@@ -77,7 +85,7 @@ private void RunInput ()
     {
         try
         {
-            lock (oLockInitialization)
+            lock (_oLockInitialization)
             {
                 // Instance must be constructed on the thread in which it is used.
                 _input = _inputFactory.Invoke ();
@@ -88,7 +96,7 @@ private void RunInput ()
 
             try
             {
-                _input.Run (tokenSource.Token);
+                _input.Run (_tokenSource.Token);
             }
             catch (OperationCanceledException)
             { }
@@ -106,7 +114,7 @@ private void RunInput ()
 
     private void BootMainLoop ()
     {
-        lock (oLockInitialization)
+        lock (_oLockInitialization)
         {
             // Instance must be constructed on the thread in which it is used.
             _output = _outputFactory.Invoke ();
@@ -132,9 +140,10 @@ private void BuildFacadeIfPossible ()
         }
     }
 
+    /// <inheritdoc/>
     public void Stop ()
     {
-        tokenSource.Cancel ();
+        _tokenSource.Cancel ();
 
         // Wait for input infinite loop to exit
         Task.WhenAll (_inputTask).Wait ();
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
index 5183f0577f..0162e895d2 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
@@ -2,10 +2,18 @@
 
 namespace Terminal.Gui;
 
+/// <summary>
+/// Console input implementation that uses native dotnet methods e.g. <see cref="System.Console"/>.
+/// </summary>
 public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
 {
     private readonly NetWinVTConsole _adjustConsole;
 
+    /// <summary>
+    /// Creates a new instance of the class. Implicitly sends
+    /// console mode settings that enable virtual input (mouse
+    /// reporting etc).
+    /// </summary>
     public NetInput ()
     {
         Logging.Logger.LogInformation ($"Creating {nameof (NetInput)}");
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
index ece30a5602..c55fe938d9 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
@@ -2,12 +2,19 @@
 
 namespace Terminal.Gui;
 
+/// <summary>
+/// Implementation of <see cref="IConsoleOutput"/> that uses native dotnet
+/// methods e.g. <see cref="System.Console"/>
+/// </summary>
 public class NetOutput : IConsoleOutput
 {
-    public bool IsWinPlatform { get; }
+    private readonly bool _isWinPlatform;
 
     private CursorVisibility? _cachedCursorVisibility;
 
+    /// <summary>
+    /// Creates a new instance of the <see cref="NetOutput"/> class.
+    /// </summary>
     public NetOutput ()
     {
         Logging.Logger.LogInformation ($"Creating {nameof (NetOutput)}");
@@ -16,7 +23,7 @@ public NetOutput ()
 
         if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
         {
-            IsWinPlatform = true;
+            _isWinPlatform = true;
         }
     }
 
@@ -179,21 +186,13 @@ private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref
         outputWidth = 0;
     }
 
-    public bool SetCursorVisibility (CursorVisibility visibility)
-    {
-        _cachedCursorVisibility = visibility;
-
-        Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
-
-        return visibility == CursorVisibility.Default;
-    }
 
     /// <inheritdoc/>
     public void SetCursorPosition (int col, int row) { SetCursorPositionImpl (col, row); }
 
     private bool SetCursorPositionImpl (int col, int row)
     {
-        if (IsWinPlatform)
+        if (_isWinPlatform)
         {
             // Could happens that the windows is still resizing and the col is bigger than Console.WindowWidth.
             try
@@ -218,7 +217,8 @@ private bool SetCursorPositionImpl (int col, int row)
     /// <inheritdoc/>
     public void Dispose () { Console.Clear (); }
 
-    void IConsoleOutput.SetCursorVisibility (CursorVisibility visibility)
+    /// <inheritdoc/>
+    public void SetCursorVisibility (CursorVisibility visibility)
     {
         Console.Out.Write (visibility == CursorVisibility.Default ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
     }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
index dc920068f2..112c7f9cc2 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
@@ -82,8 +82,7 @@ public int Cols
     /// <summary>The topmost row in the terminal.</summary>
     public virtual int Top { get; set; } = 0;
 
-    // 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.
+    /// <inheritdoc/>
     public bool [] DirtyLines { get; set; } = [];
 
     // QUESTION: When non-full screen apps are supported, will this represent the app size, or will that be in Application?
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
index 82682ef4b5..441dbf931e 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
@@ -1,4 +1,5 @@
-using System.ComponentModel;
+#nullable enable
+using System.ComponentModel;
 using System.Runtime.InteropServices;
 using Microsoft.Extensions.Logging;
 using static Terminal.Gui.WindowsConsole;
@@ -11,7 +12,7 @@ internal class WindowsOutput : IConsoleOutput
     private static extern bool WriteConsole (
         nint hConsoleOutput,
         string lpbufer,
-        uint NumberOfCharsToWriten,
+        uint numberOfCharsToWriten,
         out uint lpNumberOfCharsWritten,
         nint lpReserved
     );
@@ -48,7 +49,7 @@ private enum DesiredAccess : uint
     internal static nint INVALID_HANDLE_VALUE = new (-1);
 
     [DllImport ("kernel32.dll", SetLastError = true)]
-    private static extern bool SetConsoleActiveScreenBuffer (nint Handle);
+    private static extern bool SetConsoleActiveScreenBuffer (nint handle);
 
     [DllImport ("kernel32.dll")]
     private static extern bool SetConsoleCursorPosition (nint hConsoleOutput, Coord dwCursorPosition);
@@ -182,7 +183,7 @@ public void Write (IOutputBuffer buffer)
 
     public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord bufferSize, SmallRect window, bool force16Colors)
     {
-        var _stringBuilder = new StringBuilder ();
+        var stringBuilder = new StringBuilder ();
 
         //Debug.WriteLine ("WriteToConsole");
 
@@ -212,10 +213,10 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord
         }
         else
         {
-            _stringBuilder.Clear ();
+            stringBuilder.Clear ();
 
-            _stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition);
-            _stringBuilder.Append (EscSeqUtils.CSI_SetCursorPosition (0, 0));
+            stringBuilder.Append (EscSeqUtils.CSI_SaveCursorPosition);
+            stringBuilder.Append (EscSeqUtils.CSI_SetCursorPosition (0, 0));
 
             Attribute? prev = null;
 
@@ -226,27 +227,27 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord
                 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));
+                    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);
+                        stringBuilder.Append (info.Char);
                     }
                 }
                 else
                 {
-                    _stringBuilder.Append (' ');
+                    stringBuilder.Append (' ');
                 }
             }
 
-            _stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition);
-            _stringBuilder.Append (EscSeqUtils.CSI_HideCursor);
+            stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition);
+            stringBuilder.Append (EscSeqUtils.CSI_HideCursor);
 
-            var s = _stringBuilder.ToString ();
+            var s = stringBuilder.ToString ();
 
             // TODO: requires extensive testing if we go down this route
             // If console output has changed

From e8efe804b117e8366d93493559c5c58caafdd18c Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 29 Dec 2024 20:42:49 +0000
Subject: [PATCH 115/198] Work on shift+ctrl+etc with arrow keys as escape
 sequences

---
 .../AnsiResponseParser/AnsiKeyboardParser.cs  | 32 +++++++++++++--
 .../ConsoleDrivers/V2/InputProcessor.cs       |  2 +-
 Terminal.Gui/ConsoleDrivers/V2/NetInput.cs    |  4 +-
 Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs   | 18 ++++++++-
 .../ConsoleDrivers/AnsiKeyboardParserTests.cs | 39 +++++++++++++++++++
 5 files changed, 88 insertions(+), 7 deletions(-)
 create mode 100644 UnitTests/ConsoleDrivers/AnsiKeyboardParserTests.cs

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
index ce0f0a6cc9..bb50afbb9e 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
@@ -9,7 +9,7 @@ namespace Terminal.Gui;
 public class AnsiKeyboardParser
 {
     // Regex patterns for ANSI arrow keys (Up, Down, Left, Right)
-    private readonly Regex _arrowKeyPattern = new (@"\u001b\[(A|B|C|D)", RegexOptions.Compiled);
+    private readonly Regex _arrowKeyPattern = new (@"^\u001b\[(1;(\d+))?([A-D])$", RegexOptions.Compiled);
 
     /// <summary>
     ///     Parses an ANSI escape sequence into a keyboard event. Returns null if input
@@ -24,16 +24,40 @@ public Key ProcessKeyboardInput (string input)
 
         if (match.Success)
         {
-            char direction = match.Groups [1].Value [0];
+            // Group 2 captures the modifier number, if present
+            string modifierGroup = match.Groups [2].Value;
+            char direction = match.Groups [3].Value [0];
 
-            return direction switch
+            Key key = direction switch
                    {
                        'A' => Key.CursorUp,
                        'B' => Key.CursorDown,
                        'C' => Key.CursorRight,
-                       'D' => Key.CursorDown,
+                       'D' => Key.CursorLeft,
                        _ => default (Key)
                    };
+
+            if(key == null)
+            {
+                return null;
+            }
+
+            // TODO: these are wrong I think
+            // Apply modifiers based on the modifier number
+            if (modifierGroup == "3") // Ctrl
+            {
+                key = key.WithCtrl;
+            }
+            else if (modifierGroup == "4") // Alt
+            {
+                key = key.WithAlt;
+            }
+            else if (modifierGroup == "5") // Shift
+            {
+                key = key.WithShift;
+            }
+
+            return key;
         }
 
         // It's an unrecognized keyboard event
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index 9efd27e600..d5b0b362e5 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -92,7 +92,7 @@ protected InputProcessor (ConcurrentQueue<T> inputBuffer)
         // TODO: For now handle all other escape codes with ignore
         Parser.UnexpectedResponseHandler = str =>
                                            {
-                                               Logging.Logger.LogInformation ($"{nameof(InputProcessor<T>)} ignored unrecognized response '{str}'");
+                                               Logging.Logger.LogInformation ($"{nameof(InputProcessor<T>)} ignored unrecognized response '{new string(str.Select (k=>k.Item1).ToArray ())}'");
                                                return true;
                                            };
     }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
index 0162e895d2..c113596bcd 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
@@ -34,8 +34,8 @@ public NetInput ()
             }
         }
 
-        // Doesn't seem to work
         Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents);
+        Console.TreatControlCAsInput = true;
     }
 
     /// <inheritdoc/>
@@ -55,5 +55,7 @@ public override void Dispose ()
     {
         base.Dispose ();
         _adjustConsole?.Cleanup ();
+
+        Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents);
     }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
index c55fe938d9..e350a03813 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
@@ -25,6 +25,12 @@ public NetOutput ()
         {
             _isWinPlatform = true;
         }
+
+        //Enable alternative screen buffer.
+        Console.Out.Write (EscSeqUtils.CSI_SaveCursorAndActivateAltBufferNoBackscroll);
+
+        //Set cursor key to application.
+        Console.Out.Write (EscSeqUtils.CSI_HideCursor);
     }
 
     /// <inheritdoc/>
@@ -215,7 +221,17 @@ private bool SetCursorPositionImpl (int col, int row)
     }
 
     /// <inheritdoc/>
-    public void Dispose () { Console.Clear (); }
+    public void Dispose ()
+    {
+        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 ();
+    }
 
     /// <inheritdoc/>
     public void SetCursorVisibility (CursorVisibility visibility)
diff --git a/UnitTests/ConsoleDrivers/AnsiKeyboardParserTests.cs b/UnitTests/ConsoleDrivers/AnsiKeyboardParserTests.cs
new file mode 100644
index 0000000000..28bd77723f
--- /dev/null
+++ b/UnitTests/ConsoleDrivers/AnsiKeyboardParserTests.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace UnitTests.ConsoleDrivers;
+public class AnsiKeyboardParserTests
+{
+    private readonly AnsiKeyboardParser _parser = new ();
+
+    public static IEnumerable<object []> GetKeyboardTestData ()
+    {
+        // Test data for various ANSI escape sequences and their expected Key values
+        yield return new object [] { "\u001b[A", Key.CursorUp };
+        yield return new object [] { "\u001b[B", Key.CursorDown };
+        yield return new object [] { "\u001b[C", Key.CursorRight };
+        yield return new object [] { "\u001b[D", Key.CursorLeft };
+
+        // Invalid inputs
+        yield return new object [] { "\u001b[Z", null };
+        yield return new object [] { "\u001b[invalid", null };
+        yield return new object [] { "\u001b[1", null };
+        yield return new object [] { "\u001b[AB", null };
+        yield return new object [] { "\u001b[;A", null };
+    }
+
+    // Consolidated test for all keyboard events (e.g., arrow keys)
+    [Theory]
+    [MemberData (nameof (GetKeyboardTestData))]
+    public void ProcessKeyboardInput_ReturnsCorrectKey (string input, Key? expectedKey)
+    {
+        // Act
+        Key? result = _parser.ProcessKeyboardInput (input);
+
+        // Assert
+        Assert.Equal (expectedKey, result); // Verify the returned key matches the expected one
+    }
+}

From 4cd54aca32ab617a0ec506205fefb5ab4d8cda96 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Mon, 30 Dec 2024 09:38:59 +0000
Subject: [PATCH 116/198] Fix keyboard parser to work correctly with shift/alt
 etc

---
 .../AnsiResponseParser/AnsiKeyboardParser.cs  | 32 ++++++++++-------
 .../ConsoleDrivers/AnsiKeyboardParserTests.cs | 34 +++++++++++++++++++
 2 files changed, 53 insertions(+), 13 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
index bb50afbb9e..8a6cedeb69 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
@@ -1,4 +1,5 @@
-using System.Text.RegularExpressions;
+using System.Numerics;
+using System.Text.RegularExpressions;
 
 namespace Terminal.Gui;
 
@@ -42,19 +43,24 @@ public Key ProcessKeyboardInput (string input)
                 return null;
             }
 
-            // TODO: these are wrong I think
-            // Apply modifiers based on the modifier number
-            if (modifierGroup == "3") // Ctrl
-            {
-                key = key.WithCtrl;
-            }
-            else if (modifierGroup == "4") // Alt
-            {
-                key = key.WithAlt;
-            }
-            else if (modifierGroup == "5") // Shift
+            // Examples:
+            // without modifiers:
+            //   \u001b\[B
+            // with modifiers:
+            //   \u001b\[1; 2B
+
+            if (!string.IsNullOrWhiteSpace (modifierGroup) && int.TryParse (modifierGroup, out var modifier))
             {
-                key = key.WithShift;
+                key = modifier switch
+                      {
+                          2=>key.WithShift,
+                          3=>key.WithAlt,
+                          4=>key.WithAlt.WithShift,
+                          5=>key.WithCtrl,
+                          6=>key.WithCtrl.WithShift,
+                          7=>key.WithCtrl.WithAlt,
+                          8=>key.WithCtrl.WithAlt.WithShift
+                      };
             }
 
             return key;
diff --git a/UnitTests/ConsoleDrivers/AnsiKeyboardParserTests.cs b/UnitTests/ConsoleDrivers/AnsiKeyboardParserTests.cs
index 28bd77723f..09e7cc25e6 100644
--- a/UnitTests/ConsoleDrivers/AnsiKeyboardParserTests.cs
+++ b/UnitTests/ConsoleDrivers/AnsiKeyboardParserTests.cs
@@ -17,6 +17,40 @@ public static IEnumerable<object []> GetKeyboardTestData ()
         yield return new object [] { "\u001b[C", Key.CursorRight };
         yield return new object [] { "\u001b[D", Key.CursorLeft };
 
+        // Valid inputs with modifiers
+        yield return new object [] { "\u001b[1;2A", Key.CursorUp.WithShift };
+        yield return new object [] { "\u001b[1;3A", Key.CursorUp.WithAlt };
+        yield return new object [] { "\u001b[1;4A", Key.CursorUp.WithAlt.WithShift };
+        yield return new object [] { "\u001b[1;5A", Key.CursorUp.WithCtrl };
+        yield return new object [] { "\u001b[1;6A", Key.CursorUp.WithCtrl.WithShift };
+        yield return new object [] { "\u001b[1;7A", Key.CursorUp.WithCtrl.WithAlt };
+        yield return new object [] { "\u001b[1;8A", Key.CursorUp.WithCtrl.WithAlt.WithShift };
+
+        yield return new object [] { "\u001b[1;2B", Key.CursorDown.WithShift };
+        yield return new object [] { "\u001b[1;3B", Key.CursorDown.WithAlt };
+        yield return new object [] { "\u001b[1;4B", Key.CursorDown.WithAlt.WithShift };
+        yield return new object [] { "\u001b[1;5B", Key.CursorDown.WithCtrl };
+        yield return new object [] { "\u001b[1;6B", Key.CursorDown.WithCtrl.WithShift };
+        yield return new object [] { "\u001b[1;7B", Key.CursorDown.WithCtrl.WithAlt };
+        yield return new object [] { "\u001b[1;8B", Key.CursorDown.WithCtrl.WithAlt.WithShift };
+
+        yield return new object [] { "\u001b[1;2C", Key.CursorRight.WithShift };
+        yield return new object [] { "\u001b[1;3C", Key.CursorRight.WithAlt };
+        yield return new object [] { "\u001b[1;4C", Key.CursorRight.WithAlt.WithShift };
+        yield return new object [] { "\u001b[1;5C", Key.CursorRight.WithCtrl };
+        yield return new object [] { "\u001b[1;6C", Key.CursorRight.WithCtrl.WithShift };
+        yield return new object [] { "\u001b[1;7C", Key.CursorRight.WithCtrl.WithAlt };
+        yield return new object [] { "\u001b[1;8C", Key.CursorRight.WithCtrl.WithAlt.WithShift };
+
+        yield return new object [] { "\u001b[1;2D", Key.CursorLeft.WithShift };
+        yield return new object [] { "\u001b[1;3D", Key.CursorLeft.WithAlt };
+        yield return new object [] { "\u001b[1;4D", Key.CursorLeft.WithAlt.WithShift };
+        yield return new object [] { "\u001b[1;5D", Key.CursorLeft.WithCtrl };
+        yield return new object [] { "\u001b[1;6D", Key.CursorLeft.WithCtrl.WithShift };
+        yield return new object [] { "\u001b[1;7D", Key.CursorLeft.WithCtrl.WithAlt };
+        yield return new object [] { "\u001b[1;8D", Key.CursorLeft.WithCtrl.WithAlt.WithShift };
+
+
         // Invalid inputs
         yield return new object [] { "\u001b[Z", null };
         yield return new object [] { "\u001b[invalid", null };

From 3c20bc57bde55adc4b50e19dd5e60e44a89e42f4 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Mon, 30 Dec 2024 17:06:16 +0000
Subject: [PATCH 117/198] Add test for keyboard event in AnsiResponseParser

---
 .../ConsoleDrivers/AnsiResponseParserTests.cs | 35 +++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs b/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs
index 32ae7dbff3..b851d47044 100644
--- a/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs
+++ b/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs
@@ -513,6 +513,41 @@ public void ParserDetectsMouse ()
         Assert.Equal (49, mouseEventArgs [1].Position.Y);
     }
 
+
+    [Fact]
+    public void ParserDetectsKeyboard ()
+    {
+
+        // ANSI escape sequence for cursor left
+        const string LEFT = "\u001b[D";
+
+        // ANSI escape sequence for Device Attribute Response (e.g., Terminal identifying itself)
+        const string DEVICE_ATTRIBUTE_RESPONSE = "\u001B[?1;2c";
+
+        // ANSI escape sequence for cursor up (while shift held down)
+        const string SHIFT_UP = "\u001b[1;2A";
+
+        var parser = new AnsiResponseParser ();
+
+        parser.HandleKeyboard = true;
+        string? foundDar = null;
+        List<Key> keys = new ();
+
+        parser.Keyboard += (s, e) => keys.Add (e);
+        parser.ExpectResponse ("c", (dar) => foundDar = dar, null, false);
+        var released = parser.ProcessInput ("a" + LEFT + "asdf" + DEVICE_ATTRIBUTE_RESPONSE + "bbcc" + SHIFT_UP + "sss");
+
+        Assert.Equal ("aasdfbbccsss", released);
+
+        Assert.Equal (2, keys.Count);
+
+        Assert.NotNull (foundDar);
+        Assert.Equal (DEVICE_ATTRIBUTE_RESPONSE, foundDar);
+
+        Assert.Equal (Key.CursorLeft,keys [0]);
+        Assert.Equal (Key.CursorUp.WithShift, keys [1]);
+    }
+
     private Tuple<char, int> [] StringToBatch (string batch)
     {
         return batch.Select ((k) => Tuple.Create (k, tIndex++)).ToArray ();

From 4b88f7122005602b3da728d400724a1e32916c31 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Mon, 30 Dec 2024 17:39:14 +0000
Subject: [PATCH 118/198] Restore Run/Shutdown test

---
 UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs b/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
index eeca507040..544289e82f 100644
--- a/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
+++ b/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
@@ -54,11 +54,14 @@ public void Test_NoInitThrowOnRun ()
         var ex = Assert.Throws<Exception> (() => app.Run (new Window ()));
         Assert.Equal ("App not Initialized",ex.Message);
     }
-    /* TODO : Infinite loops
+
     [Fact]
     public void Test_InitRunShutdown ()
     {
+        var orig = ApplicationImpl.Instance;
+
         var v2 = NewApplicationV2();
+        ApplicationImpl.ChangeInstance (v2);
 
         v2.Init ();
 
@@ -75,8 +78,14 @@ public void Test_InitRunShutdown ()
                        }
                        );
         Assert.Null (Application.Top);
+
+        // Blocks until the timeout call is hit
+
         v2.Run (new Window ());
+
         Assert.Null (Application.Top);
         v2.Shutdown ();
-    }*/
+
+        ApplicationImpl.ChangeInstance (orig);
+    }
 }

From 4fbd00e3638105585391cf4eccd4198933cdd2cf Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Mon, 30 Dec 2024 18:38:49 +0000
Subject: [PATCH 119/198] Harden ApplicationV2 boot process

---
 .../ConsoleDrivers/V2/ApplicationV2.cs        |  2 +-
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  | 21 ++++-
 .../ConsoleDrivers/V2/ApplicationV2Tests.cs   | 79 +++++++++++++++++--
 3 files changed, 93 insertions(+), 9 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index fd824ddfe0..2e3ce2db25 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -150,7 +150,7 @@ public override void Run (Toplevel view, Func<Exception, bool>? errorHandler = n
 
         if (!Application.Initialized)
         {
-            throw new ("App not Initialized");
+            throw new NotInitializedException (nameof(Run));
         }
 
         Application.Top = view;
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index de6e64d78d..02efdef2fa 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -69,14 +69,27 @@ public async Task StartAsync ()
     {
         Logging.Logger.LogInformation ("Main Loop Coordinator booting...");
 
-        // TODO: if crash on boot then semaphore never finishes
         _inputTask = Task.Run (RunInput);
 
         // Main loop is now booted on same thread as rest of users application
         BootMainLoop ();
 
-        // Use asynchronous semaphore waiting.
-        await _startupSemaphore.WaitAsync ().ConfigureAwait (false);
+        // Wait asynchronously for the semaphore or task failure.
+        var waitForSemaphore = _startupSemaphore.WaitAsync ();
+
+        // Wait for either the semaphore to be released or the input task to crash.
+        var completedTask = await Task.WhenAny (waitForSemaphore, _inputTask).ConfigureAwait (false);
+
+        // Check if the task was the input task and if it has failed.
+        if (completedTask == _inputTask)
+        {
+            if (_inputTask.IsFaulted)
+            {
+                throw _inputTask.Exception;
+            }
+
+            throw new Exception ("Input loop exited during startup instead of entering read loop properly (i.e. and blocking)");
+        }
 
         Logging.Logger.LogInformation ("Main Loop Coordinator booting complete");
     }
@@ -106,6 +119,8 @@ private void RunInput ()
         catch (Exception e)
         {
             Logging.Logger.LogCritical (e, "Input loop crashed");
+
+            throw;
         }
     }
 
diff --git a/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs b/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
index 544289e82f..e015847c50 100644
--- a/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
+++ b/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
@@ -1,4 +1,5 @@
-using Moq;
+using System.Collections.Concurrent;
+using Moq;
 
 namespace UnitTests.ConsoleDrivers.V2;
 public class ApplicationV2Tests
@@ -6,10 +7,15 @@ public class ApplicationV2Tests
 
     private ApplicationV2 NewApplicationV2 ()
     {
+        var netInput = new Mock<INetInput> ();
+        SetupRunInputMockMethodToBlock (netInput);
+        var winInput = new Mock<IWindowsInput> ();
+        SetupRunInputMockMethodToBlock (winInput);
+
         return new (
-                    Mock.Of<INetInput>,
+                    ()=>netInput.Object,
                     Mock.Of<IConsoleOutput>,
-                    Mock.Of<IWindowsInput>,
+                    () => winInput.Object,
                     Mock.Of<IConsoleOutput>);
     }
 
@@ -46,13 +52,76 @@ public void TestInit_DriverIsFacade ()
         Assert.Null (Application.Driver);
     }
 
+    [Fact]
+    public void TestInit_ExplicitlyRequestWin ()
+    {
+        var netInput = new Mock<INetInput> (MockBehavior.Strict);
+        var netOutput = new Mock<IConsoleOutput> (MockBehavior.Strict);
+        var winInput = new Mock<IWindowsInput> (MockBehavior.Strict);
+        var winOutput = new Mock<IConsoleOutput> (MockBehavior.Strict);
+
+        winInput.Setup (i => i.Initialize (It.IsAny<ConcurrentQueue<WindowsConsole.InputRecord>> ()))
+                .Verifiable(Times.Once);
+        SetupRunInputMockMethodToBlock (winInput);
+        winInput.Setup (i=>i.Dispose ())
+                .Verifiable(Times.Once);
+
+        var v2 = new ApplicationV2 (
+                                    ()=> netInput.Object,
+                                    () => netOutput.Object,
+                                    () => winInput.Object,
+                                    () => winOutput.Object);
+
+        Assert.Null (Application.Driver);
+        v2.Init (null,"v2win");
+        Assert.NotNull (Application.Driver);
+
+        var type = Application.Driver.GetType ();
+        Assert.True (type.IsGenericType);
+        Assert.True (type.GetGenericTypeDefinition () == typeof (ConsoleDriverFacade<>));
+        v2.Shutdown ();
+
+        Assert.Null (Application.Driver);
+
+        winInput.VerifyAll();
+    }
+
+    private void SetupRunInputMockMethodToBlock (Mock<IWindowsInput> winInput)
+    {
+        winInput.Setup (r => r.Run (It.IsAny<CancellationToken> ()))
+                .Callback<CancellationToken> (token =>
+                                              {
+                                                  // Simulate an infinite loop that checks for cancellation
+                                                  while (!token.IsCancellationRequested)
+                                                  {
+                                                      // Perform the action that should repeat in the loop
+                                                      // This could be some mock behavior or just an empty loop depending on the context
+                                                  }
+                                              })
+                .Verifiable (Times.Once);
+    }
+    private void SetupRunInputMockMethodToBlock (Mock<INetInput> netInput)
+    {
+        netInput.Setup (r => r.Run (It.IsAny<CancellationToken> ()))
+                .Callback<CancellationToken> (token =>
+                                              {
+                                                  // Simulate an infinite loop that checks for cancellation
+                                                  while (!token.IsCancellationRequested)
+                                                  {
+                                                      // Perform the action that should repeat in the loop
+                                                      // This could be some mock behavior or just an empty loop depending on the context
+                                                  }
+                                              })
+                .Verifiable (Times.Once);
+    }
+
     [Fact]
     public void Test_NoInitThrowOnRun ()
     {
         var app = NewApplicationV2();
 
-        var ex = Assert.Throws<Exception> (() => app.Run (new Window ()));
-        Assert.Equal ("App not Initialized",ex.Message);
+        var ex = Assert.Throws<NotInitializedException> (() => app.Run (new Window ()));
+        Assert.Equal ("Run cannot be accessed before Initialization", ex.Message);
     }
 
     [Fact]

From 4f80d7485d00f5cb0799c4d18effdf07ff4bd225 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Mon, 30 Dec 2024 18:58:19 +0000
Subject: [PATCH 120/198] Add test for explicitly requesting net

---
 .../ConsoleDrivers/V2/ApplicationV2Tests.cs   | 34 +++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs b/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
index e015847c50..901582b803 100644
--- a/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
+++ b/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
@@ -86,6 +86,40 @@ public void TestInit_ExplicitlyRequestWin ()
         winInput.VerifyAll();
     }
 
+    [Fact]
+    public void TestInit_ExplicitlyRequestNet ()
+    {
+        var netInput = new Mock<INetInput> (MockBehavior.Strict);
+        var netOutput = new Mock<IConsoleOutput> (MockBehavior.Strict);
+        var winInput = new Mock<IWindowsInput> (MockBehavior.Strict);
+        var winOutput = new Mock<IConsoleOutput> (MockBehavior.Strict);
+
+        netInput.Setup (i => i.Initialize (It.IsAny<ConcurrentQueue<ConsoleKeyInfo>> ()))
+                .Verifiable (Times.Once);
+        SetupRunInputMockMethodToBlock (netInput);
+        netInput.Setup (i => i.Dispose ())
+                .Verifiable (Times.Once);
+
+        var v2 = new ApplicationV2 (
+                                    () => netInput.Object,
+                                    () => netOutput.Object,
+                                    () => winInput.Object,
+                                    () => winOutput.Object);
+
+        Assert.Null (Application.Driver);
+        v2.Init (null, "v2net");
+        Assert.NotNull (Application.Driver);
+
+        var type = Application.Driver.GetType ();
+        Assert.True (type.IsGenericType);
+        Assert.True (type.GetGenericTypeDefinition () == typeof (ConsoleDriverFacade<>));
+        v2.Shutdown ();
+
+        Assert.Null (Application.Driver);
+
+        netInput.VerifyAll ();
+    }
+
     private void SetupRunInputMockMethodToBlock (Mock<IWindowsInput> winInput)
     {
         winInput.Setup (r => r.Run (It.IsAny<CancellationToken> ()))

From 445b3024d033b1069fcaca47d3047e8ce7e23ada Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Tue, 31 Dec 2024 14:32:15 +0000
Subject: [PATCH 121/198] Fix not disposing output

---
 Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs | 3 ++-
 Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs           | 1 +
 UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs     | 5 ++++-
 3 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index 02efdef2fa..220e4bfeb9 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -159,8 +159,9 @@ private void BuildFacadeIfPossible ()
     public void Stop ()
     {
         _tokenSource.Cancel ();
+        _output.Dispose ();
 
         // Wait for input infinite loop to exit
-        Task.WhenAll (_inputTask).Wait ();
+        _inputTask.Wait ();
     }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
index e350a03813..6d6e9baf86 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
@@ -230,6 +230,7 @@ public void Dispose ()
 
         //Set cursor key to cursor.
         Console.Out.Write (EscSeqUtils.CSI_ShowCursor);
+
         Console.Out.Close ();
     }
 
diff --git a/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs b/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
index 901582b803..9f11c4bdf3 100644
--- a/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
+++ b/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
@@ -65,6 +65,8 @@ public void TestInit_ExplicitlyRequestWin ()
         SetupRunInputMockMethodToBlock (winInput);
         winInput.Setup (i=>i.Dispose ())
                 .Verifiable(Times.Once);
+        winOutput.Setup (i => i.Dispose ())
+                 .Verifiable (Times.Once);
 
         var v2 = new ApplicationV2 (
                                     ()=> netInput.Object,
@@ -99,7 +101,8 @@ public void TestInit_ExplicitlyRequestNet ()
         SetupRunInputMockMethodToBlock (netInput);
         netInput.Setup (i => i.Dispose ())
                 .Verifiable (Times.Once);
-
+        netOutput.Setup (i => i.Dispose ())
+                 .Verifiable (Times.Once);
         var v2 = new ApplicationV2 (
                                     () => netInput.Object,
                                     () => netOutput.Object,

From e8815dcbec1858568ed436cf88674d78fd4a828e Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Tue, 31 Dec 2024 15:25:30 +0000
Subject: [PATCH 122/198] Fix GetSupportedCultures not called

---
 Terminal.Gui/Application/Application.Initialization.cs | 1 -
 Terminal.Gui/Application/Application.cs                | 4 +---
 2 files changed, 1 insertion(+), 4 deletions(-)

diff --git a/Terminal.Gui/Application/Application.Initialization.cs b/Terminal.Gui/Application/Application.Initialization.cs
index e142790747..87a61c28ff 100644
--- a/Terminal.Gui/Application/Application.Initialization.cs
+++ b/Terminal.Gui/Application/Application.Initialization.cs
@@ -174,7 +174,6 @@ internal static void InternalInit (
 
         SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ());
 
-        SupportedCultures = GetSupportedCultures ();
         MainThreadId = Thread.CurrentThread.ManagedThreadId;
         bool init = Initialized = true;
         InitializedChanged?.Invoke (null, new (init));
diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs
index cc0f84e2eb..a493f30569 100644
--- a/Terminal.Gui/Application/Application.cs
+++ b/Terminal.Gui/Application/Application.cs
@@ -23,10 +23,8 @@ namespace Terminal.Gui;
 /// <remarks></remarks>
 public static partial class Application
 {
-    internal static bool IsLegacy => ApplicationImpl.Instance.IsLegacy;
-
     /// <summary>Gets all cultures supported by the application without the invariant language.</summary>
-    public static List<CultureInfo>? SupportedCultures { get; private set; }
+    public static List<CultureInfo>? SupportedCultures { get; private set; } = GetSupportedCultures ();
 
     /// <summary>
     ///     Gets a string representation of the Application as rendered by <see cref="Driver"/>.

From 4d2d5f10306125d23449209abc95f30a14187526 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Tue, 31 Dec 2024 16:03:32 +0000
Subject: [PATCH 123/198] Emit click separate and after release events

---
 .../ConsoleDrivers/V2/InputProcessor.cs       |  9 +++++----
 .../ConsoleDrivers/V2/MouseInterpreter.cs     | 19 +++++++++++++------
 2 files changed, 18 insertions(+), 10 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index d5b0b362e5..528eaeccc3 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -63,10 +63,11 @@ public void OnMouseEvent (MouseEventArgs a)
         // Ensure ScreenPosition is set
         a.ScreenPosition = a.Position;
 
-        _mouseInterpreter.Process (a);
-
-        // Pass on
-        MouseEvent?.Invoke (this, a);
+        foreach (var e in _mouseInterpreter.Process (a))
+        {
+            // Pass on
+            MouseEvent?.Invoke (this, e);
+        }
     }
 
     /// <summary>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
index 224150a4d4..5a05d62a8b 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
@@ -35,8 +35,10 @@ public MouseInterpreter (
         };
     }
 
-    public MouseEventArgs Process (MouseEventArgs e)
+    public IEnumerable<MouseEventArgs> Process (MouseEventArgs e)
     {
+        yield return e;
+
         // For each mouse button
         for (var i = 0; i < 4; i++)
         {
@@ -44,18 +46,23 @@ public MouseEventArgs Process (MouseEventArgs e)
 
             if (numClicks.HasValue)
             {
-                return RaiseClick (i, numClicks.Value, e);
+                yield return RaiseClick (i, numClicks.Value, e);
             }
         }
-
-        return e;
     }
 
     private MouseEventArgs RaiseClick (int button, int numberOfClicks, MouseEventArgs mouseEventArgs)
     {
-        mouseEventArgs.Flags |= ToClicks (button, numberOfClicks);
+        var newClick = new MouseEventArgs
+        {
+            Handled = false,
+            Flags = ToClicks (button, numberOfClicks),
+            ScreenPosition = mouseEventArgs.ScreenPosition,
+            View = mouseEventArgs.View,
+            Position = mouseEventArgs.Position
+        };
 
-        return mouseEventArgs;
+        return newClick;
     }
 
     private MouseFlags ToClicks (int buttonIdx, int numberOfClicks)

From 2a8dc6e30012467a711b1729dd4710a925f9fc74 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Tue, 31 Dec 2024 16:05:55 +0000
Subject: [PATCH 124/198] fix test

---
 UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs b/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
index 7d32e2916d..99f05fb881 100644
--- a/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
+++ b/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
@@ -13,13 +13,13 @@ public void TestMouseEventSequences_InterpretedOnlyAsFlag (List<MouseEventArgs>
         // Act and Assert: Process all but the last event and ensure they yield no results
         for (int i = 0; i < events.Count - 1; i++)
         {
-            var intermediateResult = interpreter.Process (events [i]);
+            var intermediateResult = interpreter.Process (events [i]).Single();
             Assert.Equal (events [i].Flags,intermediateResult.Flags);
         }
 
         // Process the final event and verify the expected result
-        var finalResult = interpreter.Process (events [^1]); // ^1 is the last item in the list
-        Assert.Equal (expected, finalResult.Flags);
+        var finalResult = interpreter.Process (events [^1]).ToArray (); // ^1 is the last item in the list
+        Assert.Equal (expected, finalResult [1].Flags);
     }
 
 

From 99bdd84a1542ad297233bca2094b50244c06cee0 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Wed, 1 Jan 2025 19:25:04 +0000
Subject: [PATCH 125/198] Add trace logging when we create an 'extra' event -
 for mouse clicks

---
 Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
index 5a05d62a8b..a9b2f198e6 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
@@ -1,5 +1,7 @@
 #nullable enable
 
+using Microsoft.Extensions.Logging;
+
 namespace Terminal.Gui;
 
 internal class MouseInterpreter
@@ -61,7 +63,7 @@ private MouseEventArgs RaiseClick (int button, int numberOfClicks, MouseEventArg
             View = mouseEventArgs.View,
             Position = mouseEventArgs.Position
         };
-
+        Logging.Logger.LogTrace ($"Raising click event:{newClick.Flags}");
         return newClick;
     }
 

From 7f83873fc58372f57e182433ffd916a0bcda302e Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Wed, 1 Jan 2025 19:53:59 +0000
Subject: [PATCH 126/198] Brute force fix for  #3881

---
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index d9b8fa46a1..8a63a9e4e1 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -124,6 +124,10 @@ internal void IterationImpl ()
                 Application.LayoutAndDraw (true);
 
                 Out.Write (OutputBuffer);
+
+                Application.Top.MostFocused?.PositionCursor ();
+                Out.SetCursorPosition (OutputBuffer.Col, OutputBuffer.Row);
+                Out.SetCursorVisibility (CursorVisibility.Default);
             }
         }
 

From aa8bdd72499f7a95e75485acefe07eef0a3c6642 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 2 Jan 2025 07:17:03 +0000
Subject: [PATCH 127/198] Use helper function MapConsoleKeyInfo to support
 backspace etc

---
 Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
index 71d94ae2e3..4774914fb5 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
@@ -22,7 +22,8 @@ protected override void Process (ConsoleKeyInfo consoleKeyInfo)
     /// <inheritdoc/>
     protected override void ProcessAfterParsing (ConsoleKeyInfo input)
     {
-        KeyCode key = EscSeqUtils.MapKey (input);
+        ConsoleKeyInfo adjustedInput = EscSeqUtils.MapConsoleKeyInfo (input);
+        KeyCode key = EscSeqUtils.MapKey (adjustedInput);
         OnKeyDown (key);
         OnKeyUp (key);
     }

From 6c55e117e015c15614b5fa1e902968a7182b3800 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 2 Jan 2025 07:35:52 +0000
Subject: [PATCH 128/198] Fix windows right click etc mouse buttons

---
 .../ConsoleDrivers/V2/WindowsInputProcessor.cs       | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
index ce507794cf..eff51619c9 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
@@ -122,8 +122,16 @@ private MouseEventArgs ToDriverMouse (MouseEventRecord e)
         {
             Position = new (e.MousePosition.X, e.MousePosition.Y),
 
-            //Wrong but for POC ok
-            Flags = e.ButtonState.HasFlag (ButtonState.Button1Pressed) ? MouseFlags.Button1Pressed : MouseFlags.None
+            Flags = e.ButtonState switch
+                    {
+                        ButtonState.NoButtonPressed => MouseFlags.None,
+                        ButtonState.Button1Pressed => MouseFlags.Button1Pressed,
+                        ButtonState.Button2Pressed => MouseFlags.Button2Pressed,
+                        ButtonState.Button3Pressed => MouseFlags.Button3Pressed,
+                        ButtonState.Button4Pressed => MouseFlags.Button4Pressed,
+                        ButtonState.RightmostButtonPressed => MouseFlags.Button3Pressed,
+
+                    } 
         };
 
         // TODO: Return keys too

From 7fd6268d80ab319593b505ee5c81308ed0615a2b Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 5 Jan 2025 18:26:03 +0000
Subject: [PATCH 129/198] Add default case

---
 Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
index eff51619c9..6192e3bbda 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
@@ -130,8 +130,8 @@ private MouseEventArgs ToDriverMouse (MouseEventRecord e)
                         ButtonState.Button3Pressed => MouseFlags.Button3Pressed,
                         ButtonState.Button4Pressed => MouseFlags.Button4Pressed,
                         ButtonState.RightmostButtonPressed => MouseFlags.Button3Pressed,
-
-                    } 
+                        _=> MouseFlags.None
+                    }
         };
 
         // TODO: Return keys too

From 39a4748cb8823f2db5e855d51dcae9dac58b9eeb Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 11 Jan 2025 15:41:35 +0000
Subject: [PATCH 130/198] Log the view type that triggers redraw

---
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs | 19 +++++++++++++++++--
 1 file changed, 17 insertions(+), 2 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 8a63a9e4e1..6d6fdf9cd9 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -1,6 +1,7 @@
 #nullable enable
 using System.Collections.Concurrent;
 using System.Diagnostics;
+using Microsoft.Extensions.Logging;
 
 namespace Terminal.Gui;
 
@@ -124,9 +125,8 @@ internal void IterationImpl ()
                 Application.LayoutAndDraw (true);
 
                 Out.Write (OutputBuffer);
+                this.SetCursor ();
 
-                Application.Top.MostFocused?.PositionCursor ();
-                Out.SetCursorPosition (OutputBuffer.Col, OutputBuffer.Row);
                 Out.SetCursorVisibility (CursorVisibility.Default);
             }
         }
@@ -140,10 +140,25 @@ internal void IterationImpl ()
         Logging.IterationInvokesAndTimeouts.Record (swCallbacks.Elapsed.Milliseconds);
     }
 
+    private void SetCursor ()
+    {
+        var mostFocused = Application.Top.MostFocused;
+
+        if (mostFocused == null)
+        {
+            return;
+        }
+
+        mostFocused.PositionCursor ();
+        Out.SetCursorPosition (OutputBuffer.Col, OutputBuffer.Row);
+        Out.SetCursorVisibility (mostFocused.CursorVisibility);
+    }
+
     private bool AnySubviewsNeedDrawn (View v)
     {
         if (v.NeedsDraw || v.NeedsLayout)
         {
+            Logging.Logger.LogTrace ( $"{v.GetType ().Name} triggered redraw (NeedsDraw={v.NeedsDraw} NeedsLayout={v.NeedsLayout}) ");
             return true;
         }
 

From 57ef88d2030c55776ab053d53e9578c41cfaf777 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 11 Jan 2025 15:53:33 +0000
Subject: [PATCH 131/198] Log redraws as a Counter metric and log Trace of who
 is causing redraws.

---
 Terminal.Gui/ConsoleDrivers/V2/Logging.cs  | 5 +++++
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs | 1 +
 2 files changed, 6 insertions(+)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/Logging.cs b/Terminal.Gui/ConsoleDrivers/V2/Logging.cs
index b8034ebbe4..68bada3fe3 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/Logging.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/Logging.cs
@@ -38,6 +38,11 @@ public static class Logging
     /// </summary>
     public static readonly Histogram<int> IterationInvokesAndTimeouts = Logging.Meter.CreateHistogram<int> ("Invokes & Timers (ms)");
 
+    /// <summary>
+    /// Counter for when we redraw, helps detect situations e.g. where we are repainting entire UI every loop
+    /// </summary>
+    public static readonly Counter<int> Redraws = Logging.Meter.CreateCounter<int> ("Redraws");
+
     /// <summary>
     /// Metric for how long it takes to read all available input from the input stream - at which
     /// point input loop will sleep.
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 6d6fdf9cd9..5a13f43f18 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -121,6 +121,7 @@ internal void IterationImpl ()
 
             if (needsDrawOrLayout || sizeChanged)
             {
+                Logging.Redraws.Add (1);
                 // TODO: Test only
                 Application.LayoutAndDraw (true);
 

From 664dfeb45655aebcc2e087071bf767377b672ee7 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 11 Jan 2025 19:11:30 +0000
Subject: [PATCH 132/198] Fix cursor not showing in NetOutput and
 NetInputProcessor dealing with { for example

---
 .../ConsoleDrivers/V2/NetInputProcessor.cs         | 14 +++++++++++++-
 Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs        |  3 +--
 2 files changed, 14 insertions(+), 3 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
index 4774914fb5..8cf74f264c 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
@@ -23,7 +23,19 @@ protected override void Process (ConsoleKeyInfo consoleKeyInfo)
     protected override void ProcessAfterParsing (ConsoleKeyInfo input)
     {
         ConsoleKeyInfo adjustedInput = EscSeqUtils.MapConsoleKeyInfo (input);
-        KeyCode key = EscSeqUtils.MapKey (adjustedInput);
+        
+        KeyCode key;
+
+        // TODO : EscSeqUtils.MapConsoleKeyInfo is wrong for e.g. '{' - it winds up clearing the Key
+        //        So if the method nuked it then we should just work with the original.
+        if (adjustedInput.Key == ConsoleKey.None && input.Key != ConsoleKey.None)
+        {
+            key = EscSeqUtils.MapKey (input);
+        }
+        else
+        {
+            key = EscSeqUtils.MapKey (adjustedInput);
+        }
         OnKeyDown (key);
         OnKeyUp (key);
     }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
index 6d6e9baf86..3ed4a501e9 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
@@ -175,8 +175,7 @@ public void Write (IOutputBuffer buffer)
             }
         }
 
-        SetCursorPositionImpl (0, 0);
-
+        SetCursorVisibility (savedVisibility ?? CursorVisibility.Default);
         _cachedCursorVisibility = savedVisibility;
     }
 

From 80270783b7bc4d7376828600cbe0a5fc26efdbd7 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Tue, 14 Jan 2025 19:02:14 +0000
Subject: [PATCH 133/198] Fix cursor logic and win close/dispose

---
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    |  3 +-
 Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs   |  8 +++++
 .../ConsoleDrivers/V2/WindowsOutput.cs        | 34 +++++++++++++++++--
 3 files changed, 42 insertions(+), 3 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 5a13f43f18..df0f3644bb 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -126,10 +126,11 @@ internal void IterationImpl ()
                 Application.LayoutAndDraw (true);
 
                 Out.Write (OutputBuffer);
-                this.SetCursor ();
 
                 Out.SetCursorVisibility (CursorVisibility.Default);
             }
+
+            this.SetCursor ();
         }
 
         var swCallbacks = Stopwatch.StartNew ();
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
index 3ed4a501e9..c36f5b0e87 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
@@ -195,8 +195,16 @@ private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref
     /// <inheritdoc/>
     public void SetCursorPosition (int col, int row) { SetCursorPositionImpl (col, row); }
 
+    private Point _lastCursorPosition = new Point ();
     private bool SetCursorPositionImpl (int col, int row)
     {
+        if (_lastCursorPosition.X == col && _lastCursorPosition.Y == row)
+        {
+            return true;
+        }
+
+        _lastCursorPosition = new Point (col, row);
+
         if (_isWinPlatform)
         {
             // Could happens that the windows is still resizing and the col is bigger than Console.WindowWidth.
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
index 441dbf931e..e56c6ffb1c 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
@@ -302,15 +302,45 @@ public void SetCursorVisibility (CursorVisibility visibility)
         Write (sb.ToString ());
     }
 
+    private Point _lastCursorPosition = new Point ();
+
     /// <inheritdoc/>
-    public void SetCursorPosition (int col, int row) { SetConsoleCursorPosition (_screenBuffer, new ((short)col, (short)row)); }
+    public void SetCursorPosition (int col, int row)
+    {
+
+        if (_lastCursorPosition.X == col && _lastCursorPosition.Y == row)
+        {
+            return;
+        }
+
+        _lastCursorPosition = new Point (col, row);
+
+        SetConsoleCursorPosition (_screenBuffer, new ((short)col, (short)row));
+    }
+
+    private bool _isDisposed = false;
 
     /// <inheritdoc/>
     public void Dispose ()
     {
+        //TODO: find why double disposed
+
+        if (_isDisposed)
+        {
+            return;
+        }
+
         if (_screenBuffer != nint.Zero)
         {
-            CloseHandle (_screenBuffer);
+            try
+            {
+                CloseHandle (_screenBuffer);
+            }
+            catch (Exception e)
+            {
+                Logging.Logger.LogError (e,"Error trying to close screen buffer handle in WindowsOutput via interop method");
+            }
         }
+        _isDisposed=true;
     }
 }

From bc27f5c241466153ad3518e5b7f5e48592685a25 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Tue, 14 Jan 2025 19:42:15 +0000
Subject: [PATCH 134/198] Ignore repeated calls to Stop on the same
 MainLoopCoordinator

---
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  |  9 +++++++
 .../ConsoleDrivers/V2/WindowsOutput.cs        |  2 --
 .../ConsoleDrivers/V2/ApplicationV2Tests.cs   | 24 +++++++++++++++++++
 3 files changed, 33 insertions(+), 2 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index 220e4bfeb9..c9997d1e0c 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -155,9 +155,18 @@ private void BuildFacadeIfPossible ()
         }
     }
 
+    private bool _stopCalled = false;
+
     /// <inheritdoc/>
     public void Stop ()
     {
+        // Ignore repeated calls to Stop - happens if user spams Application.Shutdown().
+        if (_stopCalled)
+        {
+            return;
+        }
+        _stopCalled = true;
+
         _tokenSource.Cancel ();
         _output.Dispose ();
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
index e56c6ffb1c..7f8279f876 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
@@ -323,8 +323,6 @@ public void SetCursorPosition (int col, int row)
     /// <inheritdoc/>
     public void Dispose ()
     {
-        //TODO: find why double disposed
-
         if (_isDisposed)
         {
             return;
diff --git a/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs b/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
index 9f11c4bdf3..31ab7603af 100644
--- a/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
+++ b/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
@@ -194,4 +194,28 @@ public void Test_InitRunShutdown ()
 
         ApplicationImpl.ChangeInstance (orig);
     }
+
+
+    [Fact]
+    public void TestRepeatedShutdownCalls_DoNotDuplicateDisposeOutput ()
+    {
+        var netInput = new Mock<INetInput> ();
+        SetupRunInputMockMethodToBlock (netInput);
+        Mock<IConsoleOutput>? outputMock = null;
+
+
+        var v2 = new ApplicationV2(
+                                   () => netInput.Object,
+                                   ()=> (outputMock = new Mock<IConsoleOutput>()).Object,
+                                   Mock.Of<IWindowsInput>,
+                                   Mock.Of<IConsoleOutput>);
+
+        v2.Init (null,"v2net");
+
+
+        v2.Shutdown ();
+        v2.Shutdown ();
+        outputMock.Verify(o=>o.Dispose (),Times.Once);
+    }
+
 }

From d8bd12c37197ede8f0ab1b4434423e7559e79629 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 18 Jan 2025 08:44:24 +0000
Subject: [PATCH 135/198] Use mostFocused.PositionCursor return value for post
 drawing cursor positioning

---
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs | 19 +++++++++++++++----
 1 file changed, 15 insertions(+), 4 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index df0f3644bb..9a812b89e2 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -144,16 +144,27 @@ internal void IterationImpl ()
 
     private void SetCursor ()
     {
-        var mostFocused = Application.Top.MostFocused;
+        View? mostFocused = Application.Top.MostFocused;
 
         if (mostFocused == null)
         {
             return;
         }
 
-        mostFocused.PositionCursor ();
-        Out.SetCursorPosition (OutputBuffer.Col, OutputBuffer.Row);
-        Out.SetCursorVisibility (mostFocused.CursorVisibility);
+        Point? to = mostFocused.PositionCursor ();
+
+        if (to.HasValue)
+        {
+            // Translate to screen coordinates
+            to = mostFocused.ViewportToScreen (to.Value);
+
+            Out.SetCursorPosition (to.Value.X, to.Value.Y);
+            Out.SetCursorVisibility (mostFocused.CursorVisibility);
+        }
+        else
+        {
+            Out.SetCursorVisibility (CursorVisibility.Invisible);
+        }
     }
 
     private bool AnySubviewsNeedDrawn (View v)

From c4aa1bb9cb7e9729baeb771467c4b22312943076 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 18 Jan 2025 08:50:04 +0000
Subject: [PATCH 136/198] Make ConsoleKeyInfo to Key public static for testing

---
 .../ConsoleDrivers/V2/NetInputProcessor.cs    | 25 ++++++++++++-------
 1 file changed, 16 insertions(+), 9 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
index 8cf74f264c..72e4a08c37 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
@@ -21,22 +21,29 @@ protected override void Process (ConsoleKeyInfo consoleKeyInfo)
 
     /// <inheritdoc/>
     protected override void ProcessAfterParsing (ConsoleKeyInfo input)
+    {
+        Key key = ConsoleKeyInfoToKey (input);
+        OnKeyDown (key);
+        OnKeyUp (key);
+    }
+
+    /// <summary>
+    /// Converts terminal raw input class <see cref="ConsoleKeyInfo"/> into
+    /// common Terminal.Gui event model for keypresses (<see cref="Key"/>)
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    public static Key ConsoleKeyInfoToKey (ConsoleKeyInfo input)
     {
         ConsoleKeyInfo adjustedInput = EscSeqUtils.MapConsoleKeyInfo (input);
-        
-        KeyCode key;
 
         // TODO : EscSeqUtils.MapConsoleKeyInfo is wrong for e.g. '{' - it winds up clearing the Key
         //        So if the method nuked it then we should just work with the original.
         if (adjustedInput.Key == ConsoleKey.None && input.Key != ConsoleKey.None)
         {
-            key = EscSeqUtils.MapKey (input);
+            return EscSeqUtils.MapKey (input);
         }
-        else
-        {
-            key = EscSeqUtils.MapKey (adjustedInput);
-        }
-        OnKeyDown (key);
-        OnKeyUp (key);
+
+        return EscSeqUtils.MapKey (adjustedInput);
     }
 }

From 715754c09aae29870f3fcdfba30c87167de8b3e6 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 18 Jan 2025 09:14:45 +0000
Subject: [PATCH 137/198] Add ConsoleKeyInfoToKey test cases

---
 .../ConsoleDrivers/V2/NetInputProcessor.cs    | 16 ++++
 .../V2/NetInputProcessorTests.cs              | 88 +++++++++++++++++++
 2 files changed, 104 insertions(+)
 create mode 100644 UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs

diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
index 72e4a08c37..0c83f4f8aa 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
@@ -1,4 +1,5 @@
 using System.Collections.Concurrent;
+using Microsoft.Extensions.Logging;
 
 namespace Terminal.Gui;
 
@@ -22,6 +23,9 @@ protected override void Process (ConsoleKeyInfo consoleKeyInfo)
     /// <inheritdoc/>
     protected override void ProcessAfterParsing (ConsoleKeyInfo input)
     {
+        // For building test cases
+        //Logging.Logger.LogTrace (FormatConsoleKeyInfoForTestCase (input));
+
         Key key = ConsoleKeyInfoToKey (input);
         OnKeyDown (key);
         OnKeyUp (key);
@@ -46,4 +50,16 @@ public static Key ConsoleKeyInfoToKey (ConsoleKeyInfo input)
 
         return EscSeqUtils.MapKey (adjustedInput);
     }
+
+    /* For building test cases
+    public static string FormatConsoleKeyInfoForTestCase (ConsoleKeyInfo input)
+    {
+        string charLiteral = input.KeyChar == '\0' ? @"'\0'" : $"'{input.KeyChar}'";
+        string expectedLiteral = $"new Rune('todo')";
+
+        return $"yield return new object[] {{ new ConsoleKeyInfo({charLiteral}, ConsoleKey.{input.Key}, " +
+               $"{input.Modifiers.HasFlag (ConsoleModifiers.Shift).ToString ().ToLower ()}, " +
+               $"{input.Modifiers.HasFlag (ConsoleModifiers.Alt).ToString ().ToLower ()}, " +
+               $"{input.Modifiers.HasFlag (ConsoleModifiers.Control).ToString ().ToLower ()}), {expectedLiteral} }};";
+    }*/
 }
diff --git a/UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs b/UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs
new file mode 100644
index 0000000000..ac5af431e6
--- /dev/null
+++ b/UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs
@@ -0,0 +1,88 @@
+using System.Text;
+
+namespace UnitTests.ConsoleDrivers.V2;
+public class NetInputProcessorTests
+{
+    public static IEnumerable<object []> GetConsoleKeyInfoToKeyTestCases_Rune ()
+    {
+        yield return new object [] { new ConsoleKeyInfo ('C', ConsoleKey.None, false, false, false), new Rune('C') };
+        yield return new object [] { new ConsoleKeyInfo ('\\', ConsoleKey.Oem5, false, false, false), new Rune ('\\') };
+        yield return new object [] { new ConsoleKeyInfo ('+', ConsoleKey.OemPlus, true, false, false), new Rune ('+') };
+        yield return new object [] { new ConsoleKeyInfo ('=', ConsoleKey.OemPlus, false, false, false), new Rune ('=') };
+        yield return new object [] { new ConsoleKeyInfo ('_', ConsoleKey.OemMinus, true, false, false), new Rune ('_') };
+        yield return new object [] { new ConsoleKeyInfo ('-', ConsoleKey.OemMinus, false, false, false), new Rune ('-') };
+        yield return new object [] { new ConsoleKeyInfo (')', ConsoleKey.None, false, false, false), new Rune (')') };
+        yield return new object [] { new ConsoleKeyInfo ('0', ConsoleKey.None, false, false, false), new Rune ('0') };
+        yield return new object [] { new ConsoleKeyInfo ('(', ConsoleKey.None, false, false, false), new Rune ('(') };
+        yield return new object [] { new ConsoleKeyInfo ('9', ConsoleKey.None, false, false, false), new Rune ('9') };
+        yield return new object [] { new ConsoleKeyInfo ('*', ConsoleKey.None, false, false, false), new Rune ('*') };
+        yield return new object [] { new ConsoleKeyInfo ('8', ConsoleKey.None, false, false, false), new Rune ('8') };
+        yield return new object [] { new ConsoleKeyInfo ('&', ConsoleKey.None, false, false, false), new Rune ('&') };
+        yield return new object [] { new ConsoleKeyInfo ('7', ConsoleKey.None, false, false, false), new Rune ('7') };
+        yield return new object [] { new ConsoleKeyInfo ('^', ConsoleKey.None, false, false, false), new Rune ('^') };
+        yield return new object [] { new ConsoleKeyInfo ('6', ConsoleKey.None, false, false, false), new Rune ('6') };
+        yield return new object [] { new ConsoleKeyInfo ('%', ConsoleKey.None, false, false, false), new Rune ('%') };
+        yield return new object [] { new ConsoleKeyInfo ('5', ConsoleKey.None, false, false, false), new Rune ('5') };
+        yield return new object [] { new ConsoleKeyInfo ('$', ConsoleKey.None, false, false, false), new Rune ('$') };
+        yield return new object [] { new ConsoleKeyInfo ('4', ConsoleKey.None, false, false, false), new Rune ('4') };
+        yield return new object [] { new ConsoleKeyInfo ('#', ConsoleKey.None, false, false, false), new Rune ('#') };
+        yield return new object [] { new ConsoleKeyInfo ('@', ConsoleKey.None, false, false, false), new Rune ('@') };
+        yield return new object [] { new ConsoleKeyInfo ('2', ConsoleKey.None, false, false, false), new Rune ('2') };
+        yield return new object [] { new ConsoleKeyInfo ('!', ConsoleKey.None, false, false, false), new Rune ('!') };
+        yield return new object [] { new ConsoleKeyInfo ('1', ConsoleKey.None, false, false, false), new Rune ('1') };
+        yield return new object [] { new ConsoleKeyInfo ('\t', ConsoleKey.None, false, false, false), new Rune ('\t') };
+        yield return new object [] { new ConsoleKeyInfo ('}', ConsoleKey.Oem6, true, false, false), new Rune ('}') };
+        yield return new object [] { new ConsoleKeyInfo (']', ConsoleKey.Oem6, false, false, false), new Rune (']') };
+        yield return new object [] { new ConsoleKeyInfo ('{', ConsoleKey.Oem4, true, false, false), new Rune ('{') };
+        yield return new object [] { new ConsoleKeyInfo ('[', ConsoleKey.Oem4, false, false, false), new Rune ('[') };
+        yield return new object [] { new ConsoleKeyInfo ('\"', ConsoleKey.Oem7, true, false, false), new Rune ('\"') };
+        yield return new object [] { new ConsoleKeyInfo ('\'', ConsoleKey.Oem7, false, false, false), new Rune ('\'') };
+        yield return new object [] { new ConsoleKeyInfo (':', ConsoleKey.Oem1, true, false, false), new Rune (':') };
+        yield return new object [] { new ConsoleKeyInfo (';', ConsoleKey.Oem1, false, false, false), new Rune (';') };
+        yield return new object [] { new ConsoleKeyInfo ('?', ConsoleKey.Oem2, true, false, false), new Rune ('?') };
+        yield return new object [] { new ConsoleKeyInfo ('/', ConsoleKey.Oem2, false, false, false), new Rune ('/') };
+        yield return new object [] { new ConsoleKeyInfo ('>', ConsoleKey.OemPeriod, true, false, false), new Rune ('>') };
+        yield return new object [] { new ConsoleKeyInfo ('.', ConsoleKey.OemPeriod, false, false, false), new Rune ('.') };
+        yield return new object [] { new ConsoleKeyInfo ('<', ConsoleKey.OemComma, true, false, false), new Rune ('<') };
+        yield return new object [] { new ConsoleKeyInfo (',', ConsoleKey.OemComma, false, false, false), new Rune (',') };
+        yield return new object [] { new ConsoleKeyInfo ('w', ConsoleKey.None, false, false, false), new Rune ('w') };
+        yield return new object [] { new ConsoleKeyInfo ('e', ConsoleKey.None, false, false, false), new Rune ('e') };
+        yield return new object [] { new ConsoleKeyInfo ('a', ConsoleKey.None, false, false, false), new Rune ('a') };
+        yield return new object [] { new ConsoleKeyInfo ('s', ConsoleKey.None, false, false, false), new Rune ('s') };
+    }
+
+    [Theory]
+    [MemberData (nameof (GetConsoleKeyInfoToKeyTestCases_Rune))]
+    public void ConsoleKeyInfoToKey_ValidInput_AsRune (ConsoleKeyInfo input, Rune expected)
+    {
+        // Act
+        var result = NetInputProcessor.ConsoleKeyInfoToKey (input);
+
+        // Assert
+        Assert.Equal (expected, result.AsRune);
+    }
+
+    public static IEnumerable<object []> GetConsoleKeyInfoToKeyTestCases_Key ()
+    {
+        yield return new object [] { new ConsoleKeyInfo ('\t', ConsoleKey.None, false, false, false), Key.Tab};
+
+        // TODO: Terminal.Gui does not have a Key for this mapped
+        // TODO: null and default(Key) are both not same as Null.  Why user has to do (Key)0 to get a null key?!
+        yield return new object [] { new ConsoleKeyInfo ('\0', ConsoleKey.LeftWindows, false, false, false), (Key)0 };
+
+
+    }
+
+
+
+    [Theory]
+    [MemberData (nameof (GetConsoleKeyInfoToKeyTestCases_Key))]
+    public void ConsoleKeyInfoToKey_ValidInput_AsKey (ConsoleKeyInfo input, Key expected)
+    {
+        // Act
+        var result = NetInputProcessor.ConsoleKeyInfoToKey (input);
+
+        // Assert
+        Assert.Equal (expected, result);
+    }
+}

From f2fcd6d9460abb06eb1c4bfa72a4a3cfc6024f43 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 18 Jan 2025 09:26:45 +0000
Subject: [PATCH 138/198] Add escape and backspace tests

---
 .../ConsoleDrivers/V2/NetInputProcessor.cs    | 21 +++++++++++++++----
 .../V2/NetInputProcessorTests.cs              |  3 ++-
 2 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
index 0c83f4f8aa..36fe76f855 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
@@ -8,6 +8,16 @@ namespace Terminal.Gui;
 /// </summary>
 public class NetInputProcessor : InputProcessor<ConsoleKeyInfo>
 {
+    #pragma warning disable CA2211
+    /// <summary>
+    /// Set to true to generate code in <see cref="Logging"/> (verbose only) for test cases in NetInputProcessorTests.
+    /// <remarks>This makes the task of capturing user/language/terminal specific keyboard issues easier to
+    /// diagnose. By turning this on and searching logs user can send us exactly the input codes that are released
+    /// to input stream.</remarks>
+    /// </summary>
+    public static bool GenerateTestCasesForKeyPresses = false;
+    #pragma warning enable CA2211
+
     /// <inheritdoc/>
     public NetInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer) : base (inputBuffer) { }
 
@@ -24,7 +34,10 @@ protected override void Process (ConsoleKeyInfo consoleKeyInfo)
     protected override void ProcessAfterParsing (ConsoleKeyInfo input)
     {
         // For building test cases
-        //Logging.Logger.LogTrace (FormatConsoleKeyInfoForTestCase (input));
+        if (GenerateTestCasesForKeyPresses)
+        {
+            Logging.Logger.LogTrace (FormatConsoleKeyInfoForTestCase (input));
+        }
 
         Key key = ConsoleKeyInfoToKey (input);
         OnKeyDown (key);
@@ -51,8 +64,8 @@ public static Key ConsoleKeyInfoToKey (ConsoleKeyInfo input)
         return EscSeqUtils.MapKey (adjustedInput);
     }
 
-    /* For building test cases
-    public static string FormatConsoleKeyInfoForTestCase (ConsoleKeyInfo input)
+    /* For building test cases */
+    private static string FormatConsoleKeyInfoForTestCase (ConsoleKeyInfo input)
     {
         string charLiteral = input.KeyChar == '\0' ? @"'\0'" : $"'{input.KeyChar}'";
         string expectedLiteral = $"new Rune('todo')";
@@ -61,5 +74,5 @@ public static string FormatConsoleKeyInfoForTestCase (ConsoleKeyInfo input)
                $"{input.Modifiers.HasFlag (ConsoleModifiers.Shift).ToString ().ToLower ()}, " +
                $"{input.Modifiers.HasFlag (ConsoleModifiers.Alt).ToString ().ToLower ()}, " +
                $"{input.Modifiers.HasFlag (ConsoleModifiers.Control).ToString ().ToLower ()}), {expectedLiteral} }};";
-    }*/
+    }
 }
diff --git a/UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs b/UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs
index ac5af431e6..22fcf7eec0 100644
--- a/UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs
+++ b/UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs
@@ -65,12 +65,13 @@ public void ConsoleKeyInfoToKey_ValidInput_AsRune (ConsoleKeyInfo input, Rune ex
     public static IEnumerable<object []> GetConsoleKeyInfoToKeyTestCases_Key ()
     {
         yield return new object [] { new ConsoleKeyInfo ('\t', ConsoleKey.None, false, false, false), Key.Tab};
+        yield return new object [] { new ConsoleKeyInfo ('\u005C', ConsoleKey.None, false, false, false), Key.Esc };
+        yield return new object [] { new ConsoleKeyInfo ('\u007f', ConsoleKey.None, false, false, false), Key.Backspace };
 
         // TODO: Terminal.Gui does not have a Key for this mapped
         // TODO: null and default(Key) are both not same as Null.  Why user has to do (Key)0 to get a null key?!
         yield return new object [] { new ConsoleKeyInfo ('\0', ConsoleKey.LeftWindows, false, false, false), (Key)0 };
 
-
     }
 
 

From 10604894a766232907f647bca4bb3b9f7eb53837 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 18 Jan 2025 09:32:11 +0000
Subject: [PATCH 139/198] Fix esc definition

---
 UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs b/UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs
index 22fcf7eec0..8a15c76caa 100644
--- a/UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs
+++ b/UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs
@@ -65,7 +65,7 @@ public void ConsoleKeyInfoToKey_ValidInput_AsRune (ConsoleKeyInfo input, Rune ex
     public static IEnumerable<object []> GetConsoleKeyInfoToKeyTestCases_Key ()
     {
         yield return new object [] { new ConsoleKeyInfo ('\t', ConsoleKey.None, false, false, false), Key.Tab};
-        yield return new object [] { new ConsoleKeyInfo ('\u005C', ConsoleKey.None, false, false, false), Key.Esc };
+        yield return new object [] { new ConsoleKeyInfo ('\u001B', ConsoleKey.None, false, false, false), Key.Esc };
         yield return new object [] { new ConsoleKeyInfo ('\u007f', ConsoleKey.None, false, false, false), Key.Backspace };
 
         // TODO: Terminal.Gui does not have a Key for this mapped

From a521a2a586028865e6a4ca15c551152a231b1506 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 18 Jan 2025 16:53:02 +0000
Subject: [PATCH 140/198] Update GetVersionInfo to show which version of
 ConsoleDriverFacade is running (net or win)

---
 .../ConsoleDrivers/V2/ConsoleDriverFacade.cs  | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
index caa9e0b033..25ee57552d 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
@@ -223,9 +223,22 @@ public void ClearContents ()
     /// <param name="c"></param>
     public void FillRect (Rectangle rect, char c) { _outputBuffer.FillRect (rect, c); }
 
-    /// <summary>Returns the name of the driver and relevant library version information.</summary>
-    /// <returns></returns>
-    public string GetVersionInfo () { return GetType ().Name; }
+    /// <inheritdoc/>
+    public virtual string GetVersionInfo ()
+    {
+        string type = "";
+
+        if (_inputProcessor is WindowsInputProcessor)
+        {
+            type = "(win)";
+        }
+        else if (_inputProcessor is NetInputProcessor)
+        {
+            type = "(net)";
+        }
+
+        return GetType().Name.TrimEnd('`','1') + type;
+    }
 
     /// <summary>Tests if the specified rune is supported by the driver.</summary>
     /// <param name="rune"></param>

From 511140b4ca53de6197bc58fc5b0cd641b8e4f1fc Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 19 Jan 2025 12:57:31 +0000
Subject: [PATCH 141/198] Updated class diagram

---
 Terminal.Gui/ConsoleDrivers/V2/V2.cd | 131 ++++++++++++++-------------
 1 file changed, 69 insertions(+), 62 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index f566fc3c92..bcfb6076f1 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -10,10 +10,10 @@
     <Position X="6.5" Y="1.25" Height="0.291" Width="2.929" />
   </Comment>
   <Comment CommentText="Allows Views to work with new architecture without having to be rewritten.">
-    <Position X="8.604" Y="6.771" Height="0.75" Width="1.7" />
+    <Position X="4.666" Y="7.834" Height="0.75" Width="1.7" />
   </Comment>
   <Comment CommentText="Ansi Escape Sequence - Request / Response">
-    <Position X="19.458" Y="3.562" Height="0.396" Width="2.825" />
+    <Position X="19.208" Y="3.562" Height="0.396" Width="2.825" />
   </Comment>
   <Comment CommentText="Mouse interpretation subsystem">
     <Position X="13.271" Y="9.896" Height="0.396" Width="2.075" />
@@ -46,21 +46,41 @@
   <Class Name="Terminal.Gui.ConsoleInput&lt;T&gt;" Collapsed="true">
     <Position X="12.5" Y="2" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAACAEAQAAAAAAAQAgACAAAAAAAAAAAAAAAAo=</HashCode>
+      <HashCode>AAAAAAAAACAEAQAAAAAAAAAgACAAAAAAAAAAAAAAAAo=</HashCode>
       <FileName>ConsoleDrivers\V2\ConsoleInput.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.MainLoop&lt;T&gt;" Collapsed="true" BaseTypeListCollapsed="true">
     <Position X="11" Y="4.75" Width="1.5" />
-    <AssociationLine Name="Out" Type="Terminal.Gui.IConsoleOutput" ManuallyRouted="true">
+    <AssociationLine Name="TimedEvents" Type="Terminal.Gui.ITimedEvents" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
       <Path>
-        <Point X="11.852" Y="5.312" />
-        <Point X="11.852" Y="7.146" />
-        <Point X="12.75" Y="7.146" />
-        <Point X="12.75" Y="7.596" />
-        <Point X="12.761" Y="7.596" Type="JumpStart" />
-        <Point X="12.927" Y="7.596" Type="JumpEnd" />
+        <Point X="11.312" Y="5.312" />
+        <Point X="11.312" Y="6.292" />
+        <Point X="10" Y="6.292" />
+        <Point X="10" Y="7.25" />
+      </Path>
+      <MemberNameLabel ManuallyPlaced="true">
+        <Position X="-1.015" Y="1.019" />
+      </MemberNameLabel>
+    </AssociationLine>
+    <AssociationLine Name="OutputBuffer" Type="Terminal.Gui.IOutputBuffer" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
+      <Path>
+        <Point X="11.875" Y="5.312" />
+        <Point X="11.875" Y="5.687" />
+        <Point X="11.812" Y="5.687" />
+        <Point X="11.812" Y="7.25" />
+      </Path>
+      <MemberNameLabel ManuallyPlaced="true">
+        <Position X="0.027" Y="0.102" />
+      </MemberNameLabel>
+    </AssociationLine>
+    <AssociationLine Name="Out" Type="Terminal.Gui.IConsoleOutput" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
+      <Path>
+        <Point X="12.5" Y="5.125" />
+        <Point X="12.5" Y="5.792" />
+        <Point X="13.031" Y="5.792" />
+        <Point X="13.031" Y="7.596" />
         <Point X="14" Y="7.596" />
       </Path>
     </AssociationLine>
@@ -68,12 +88,8 @@
       <Path>
         <Point X="11.75" Y="4.75" />
         <Point X="11.75" Y="4.39" />
-        <Point X="13.667" Y="4.39" Type="JumpStart" />
-        <Point X="13.833" Y="4.39" Type="JumpEnd" />
-        <Point X="15.698" Y="4.39" Type="JumpStart" />
-        <Point X="15.865" Y="4.39" Type="JumpEnd" />
-        <Point X="20.625" Y="4.39" />
-        <Point X="20.625" Y="4.5" />
+        <Point X="20.375" Y="4.39" />
+        <Point X="20.375" Y="4.5" />
       </Path>
       <MemberNameLabel ManuallyPlaced="true">
         <Position X="0.11" Y="0.143" />
@@ -84,36 +100,31 @@
         <Point X="12.125" Y="5.312" />
         <Point X="12.125" Y="7" />
         <Point X="12.844" Y="7" />
-        <Point X="12.844" Y="14.531" />
-        <Point X="13.25" Y="14.531" />
+        <Point X="12.844" Y="13.281" />
+        <Point X="13.25" Y="13.281" />
       </Path>
       <MemberNameLabel ManuallyPlaced="true">
         <Position X="0.047" Y="-0.336" />
       </MemberNameLabel>
     </AssociationLine>
-    <AssociationLine Name="TimedEvents" Type="Terminal.Gui.ITimedEvents">
-      <MemberNameLabel ManuallyPlaced="true">
-        <Position X="-1.14" Y="0.123" />
-      </MemberNameLabel>
-    </AssociationLine>
     <TypeIdentifier>
-      <HashCode>QQQQAAAQACABIQQAAAAAAAAAACAAAAACAAAAAACAEAA=</HashCode>
+      <HashCode>QQQAAAAQACABJQQAABAAAAAAACAAAAACAAEAAACAEgg=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoop.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
+      <Property Name="TimedEvents" />
       <Property Name="InputProcessor" />
       <Property Name="OutputBuffer" />
       <Property Name="Out" />
       <Property Name="AnsiRequestScheduler" />
       <Property Name="WindowSizeMonitor" />
-      <Property Name="TimedEvents" />
     </ShowAsAssociation>
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.MainLoopCoordinator&lt;T&gt;">
     <Position X="6.5" Y="2" Width="2" />
     <TypeIdentifier>
-      <HashCode>AAAAIAEgCAIABAAAABQAAAAAABAQAAQAIQIABAAACgw=</HashCode>
+      <HashCode>IAAAIAEiCAIABAAAABQAAAAAABAAAQQAIQIABAAACgg=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoopCoordinator.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -122,7 +133,7 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.AnsiResponseParser&lt;T&gt;" Collapsed="true">
-    <Position X="19.75" Y="10" Width="2" />
+    <Position X="19.5" Y="10" Width="2" />
     <TypeIdentifier>
       <HashCode>AAQAAAAAAAAACIAAAAAAAAAAAAAgAABAAAAACBAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
@@ -143,7 +154,7 @@
   <Class Name="Terminal.Gui.NetOutput" Collapsed="true">
     <Position X="14.75" Y="8.25" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AEAAAAAAACEAAAAAAAAAAAAAAQAAQAAAMACAAAEAAAE=</HashCode>
+      <HashCode>AEAAAAAAACAAAAAAAAAAAAAAAAAAQAAAMACAAAEAgAk=</HashCode>
       <FileName>ConsoleDrivers\V2\NetOutput.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
@@ -151,48 +162,48 @@
   <Class Name="Terminal.Gui.WindowsOutput" Collapsed="true">
     <Position X="13.25" Y="8.25" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AEAAABACACAAhAAAAAAAACAAAAgAQAAIMAAAAAEAAAQ=</HashCode>
+      <HashCode>AEAAABACACAAhAAAAAAAACCAAAgAQAAIMAAAAAEAgAQ=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsOutput.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.InputProcessor&lt;T&gt;" Collapsed="true">
-    <Position X="16.75" Y="4.75" Width="2" />
+    <Position X="16.5" Y="4.75" Width="2" />
     <AssociationLine Name="_mouseInterpreter" Type="Terminal.Gui.MouseInterpreter" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
       <Path>
-        <Point X="18" Y="5.312" />
-        <Point X="18" Y="10.031" />
+        <Point X="17.75" Y="5.312" />
+        <Point X="17.75" Y="10.031" />
         <Point X="15.99" Y="10.031" />
         <Point X="15.99" Y="10.605" />
         <Point X="15" Y="10.605" />
       </Path>
     </AssociationLine>
     <TypeIdentifier>
-      <HashCode>AQCkEAAAAASAiAAAAgggAAAABAIAAAAAAAAAAAAAAAA=</HashCode>
+      <HashCode>AQAkEAAAAASAiAAAAgwgAAAABAIAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\InputProcessor.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
+      <Field Name="_mouseInterpreter" />
       <Property Name="Parser" />
-      <Property Name="_mouseInterpreter" />
     </ShowAsAssociation>
     <Lollipop Position="0.1" />
   </Class>
   <Class Name="Terminal.Gui.NetInputProcessor" Collapsed="true">
-    <Position X="18" Y="5.75" Width="2" />
+    <Position X="17.75" Y="5.75" Width="2" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAAAACAAAAgAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <HashCode>AAEAAAAAAAAACBAAAgAAAEAAAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\NetInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.WindowsInputProcessor" Collapsed="true">
-    <Position X="16" Y="5.75" Width="2" />
+    <Position X="15.75" Y="5.75" Width="2" />
     <TypeIdentifier>
       <HashCode>AQAAAAAAAAAACAAAAgAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.AnsiMouseParser">
-    <Position X="24.25" Y="9.5" Width="1.75" />
+    <Position X="24" Y="9.5" Width="1.75" />
     <TypeIdentifier>
       <HashCode>BAAAAAAAAAgAAAAAAAAAAAAAIAAAAAAAQAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiMouseParser.cs</FileName>
@@ -211,7 +222,7 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.AnsiRequestScheduler" Collapsed="true">
-    <Position X="19.75" Y="4.5" Width="2" />
+    <Position X="19.5" Y="4.5" Width="2" />
     <TypeIdentifier>
       <HashCode>AAQAACAAIAAAIAACAESQAAQAACGAAAAAAAAAAAAAQQA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiRequestScheduler.cs</FileName>
@@ -221,7 +232,7 @@
     </ShowAsCollectionAssociation>
   </Class>
   <Class Name="Terminal.Gui.AnsiResponseParserBase" Collapsed="true">
-    <Position X="20.5" Y="9" Width="2" />
+    <Position X="20.25" Y="9" Width="2" />
     <TypeIdentifier>
       <HashCode>UAiASAAAEICQALAAQAAAKAAAoAIAAABAAQIAJgAQASU=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
@@ -243,14 +254,14 @@
     </ShowAsCollectionAssociation>
   </Class>
   <Class Name="Terminal.Gui.MouseButtonStateEx">
-    <Position X="16.5" Y="11.75" Width="2" />
+    <Position X="16.5" Y="10.25" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAEwAIAAAAAAAAAAAABCAAAAAAAAABAAEAAg=</HashCode>
       <FileName>ConsoleDrivers\V2\MouseButtonState.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.StringHeld" Collapsed="true">
-    <Position X="22" Y="11" Width="1.5" />
+    <Position X="21.75" Y="11" Width="1.75" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAIAACAAAAAAAIAAAAAAAACAAAAAAAgAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\StringHeld.cs</FileName>
@@ -258,7 +269,7 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.GenericHeld&lt;T&gt;" Collapsed="true">
-    <Position X="20.25" Y="11" Width="1.5" />
+    <Position X="20" Y="11" Width="1.75" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAgAIAACAAAAAAAIAAAAAAAACAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\GenericHeld.cs</FileName>
@@ -266,21 +277,21 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.AnsiEscapeSequenceRequest">
-    <Position X="23.25" Y="4.5" Width="2.5" />
+    <Position X="23" Y="4.5" Width="2.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAEAAAAAAAEAAAAACAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiEscapeSequenceRequest.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.AnsiEscapeSequence" Collapsed="true">
-    <Position X="23.25" Y="3.75" Width="2.5" />
+    <Position X="23" Y="3.75" Width="2.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAgAAEAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiEscapeSequence.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.AnsiResponseParser" Collapsed="true">
-    <Position X="21.75" Y="10" Width="1.75" />
+    <Position X="21.5" Y="10" Width="1.75" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAgACBAAAAACBAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
@@ -307,7 +318,7 @@
   <Class Name="Terminal.Gui.ApplicationV2" Collapsed="true">
     <Position X="4.75" Y="4.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>QAAAAAgABAEIAgAQAAAAAQAAAAAAgAEAAAAKgIAAEgI=</HashCode>
+      <HashCode>QAAAAAgABAEIBgAQAAAAAQAAAAAAgAEAAAAKgIAAAgI=</HashCode>
       <FileName>ConsoleDrivers\V2\ApplicationV2.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -332,7 +343,7 @@
   <Interface Name="Terminal.Gui.IMainLoop&lt;T&gt;" Collapsed="true">
     <Position X="9.25" Y="4.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAQAAAAAAAABIQQAAAAAAAAAAAAAAAACAAAAAAAAEAA=</HashCode>
+      <HashCode>QAQAAAAAAAABIQQAAAAAAAAAAAAAAAACAAAAAAAAEAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IMainLoop.cs</FileName>
     </TypeIdentifier>
   </Interface>
@@ -357,22 +368,15 @@
       <FileName>ConsoleDrivers\V2\IInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.IViewFinder">
-    <Position X="16.75" Y="10.25" Width="1.5" />
-    <TypeIdentifier>
-      <HashCode>AAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\IViewFinder.cs</FileName>
-    </TypeIdentifier>
-  </Interface>
   <Interface Name="Terminal.Gui.IHeld">
-    <Position X="24" Y="6.5" Width="1.5" />
+    <Position X="23.75" Y="6.5" Width="1.75" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAIAACAAAAAAAIAAAAAAAACAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\IHeld.cs</FileName>
     </TypeIdentifier>
   </Interface>
   <Interface Name="Terminal.Gui.IAnsiResponseParser">
-    <Position X="20.5" Y="5.25" Width="2" />
+    <Position X="20.25" Y="5.25" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAQAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAJAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\IAnsiResponseParser.cs</FileName>
@@ -396,21 +400,24 @@
     </TypeIdentifier>
   </Interface>
   <Interface Name="Terminal.Gui.IWindowSizeMonitor" Collapsed="true">
-    <Position X="13.25" Y="14.25" Width="1.75" />
+    <Position X="13.25" Y="13" Width="1.75" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAEAAAAAAAAAAAACAAAAAAAAAAAAAA=</HashCode>
-      <FileName>ConsoleDrivers\V2\IMainLoop.cs</FileName>
+      <FileName>ConsoleDrivers\V2\IWindowSizeMonitor.cs</FileName>
     </TypeIdentifier>
   </Interface>
-  <Interface Name="Terminal.Gui.ITimedEvents" Collapsed="true">
-    <Position X="14.5" Y="4.75" Width="1.5" />
+  <Interface Name="Terminal.Gui.ITimedEvents">
+    <Position X="9.25" Y="7.25" Width="1.5" />
+    <Compartments>
+      <Compartment Name="Methods" Collapsed="true" />
+    </Compartments>
     <TypeIdentifier>
       <HashCode>BAAAIAAAAQAAAAAQACAAAIBAAQAAAAAAAAAIgAAAAAA=</HashCode>
       <FileName>Application\ITimedEvents.cs</FileName>
     </TypeIdentifier>
   </Interface>
   <Enum Name="Terminal.Gui.AnsiResponseParserState">
-    <Position X="20.5" Y="7.25" Width="2" />
+    <Position X="20.25" Y="7.25" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAEAAAAAAAAAAAAAAAAAAAAAIAAIAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParserState.cs</FileName>

From 8a62266755e674af16acf8e5f88d67d6f3a6cb49 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 19 Jan 2025 13:09:55 +0000
Subject: [PATCH 142/198] Update MouseInterpreterTests to cover double click

---
 .../V2/MouseInterpreterTests.cs               | 58 +++++++++++++++----
 1 file changed, 48 insertions(+), 10 deletions(-)

diff --git a/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs b/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
index 99f05fb881..60df3fd45e 100644
--- a/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
+++ b/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
@@ -5,21 +5,29 @@ public class MouseInterpreterTests
 {
     [Theory]
     [MemberData (nameof (SequenceTests))]
-    public void TestMouseEventSequences_InterpretedOnlyAsFlag (List<MouseEventArgs> events, MouseFlags expected)
+    public void TestMouseEventSequences_InterpretedOnlyAsFlag (List<MouseEventArgs> events, params MouseFlags?[] expected)
     {
         // Arrange: Mock dependencies and set up the interpreter
         var interpreter = new MouseInterpreter (null);
 
-        // Act and Assert: Process all but the last event and ensure they yield no results
-        for (int i = 0; i < events.Count - 1; i++)
+        // Act and Assert
+        for (int i = 0; i < events.Count; i++)
         {
-            var intermediateResult = interpreter.Process (events [i]).Single();
-            Assert.Equal (events [i].Flags,intermediateResult.Flags);
-        }
+            var results = interpreter.Process (events [i]).ToArray();
+
+            // Raw input event should be there
+            Assert.Equal (events [i].Flags, results [0].Flags);
 
-        // Process the final event and verify the expected result
-        var finalResult = interpreter.Process (events [^1]).ToArray (); // ^1 is the last item in the list
-        Assert.Equal (expected, finalResult [1].Flags);
+            // also any expected should be there
+            if (expected [i] != null)
+            {
+                Assert.Equal (expected [i], results [1].Flags);
+            }
+            else
+            {
+                Assert.Single (results);
+            }
+        }
     }
 
 
@@ -38,9 +46,39 @@ public static IEnumerable<object []> SequenceTests ()
                 // Then it wasn't
                 new()
             },
-            // Means click
+            // No extra then click
+            null,
             MouseFlags.Button1Clicked
         };
+
+        yield return new object []
+        {
+            new List<MouseEventArgs> ()
+            {
+                // Mouse was down
+                new ()
+                {
+                    Flags = MouseFlags.Button1Pressed
+                },
+
+                // Then it wasn't
+                new(),
+
+                // Then it was again
+                new ()
+                {
+                    Flags = MouseFlags.Button1Pressed
+                },
+
+                // Then it wasn't
+                new()
+            },
+            // No extra then click, then into none/double click
+            null,
+            MouseFlags.Button1Clicked,
+            null,
+            MouseFlags.Button1DoubleClicked
+        };
     }
 
 }

From e0f81e807ea51cb10e21113fa1032798e10c2a3b Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 19 Jan 2025 13:28:31 +0000
Subject: [PATCH 143/198] Only raise double click in same pos

---
 .../ConsoleDrivers/V2/MouseButtonState.cs     |  5 +-
 .../V2/MouseInterpreterTests.cs               | 49 +++++++++----------
 2 files changed, 27 insertions(+), 27 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseButtonState.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseButtonState.cs
index 0a64108f98..78557d2dae 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MouseButtonState.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseButtonState.cs
@@ -10,6 +10,7 @@ internal class MouseButtonStateEx
     private readonly TimeSpan _repeatClickThreshold;
     private readonly int _buttonIdx;
     private int _consecutiveClicks;
+    private Point _lastPosition = new Point ();
 
     /// <summary>
     ///     When the button entered its current state.
@@ -31,10 +32,11 @@ public MouseButtonStateEx (Func<DateTime> now, TimeSpan repeatClickThreshold, in
     public void UpdateState (MouseEventArgs e, out int? numClicks)
     {
         bool isPressedNow = IsPressed (_buttonIdx, e.Flags);
+        bool isSamePosition = _lastPosition == e.Position;
 
         TimeSpan elapsed = _now () - At;
 
-        if (elapsed > _repeatClickThreshold)
+        if (elapsed > _repeatClickThreshold || !isSamePosition)
         {
             // Expired
             OverwriteState (e);
@@ -70,6 +72,7 @@ private void OverwriteState (MouseEventArgs e)
     {
         Pressed = IsPressed (_buttonIdx, e.Flags);
         At = _now ();
+        _lastPosition = e.Position;
     }
 
     private bool IsPressed (int btn, MouseFlags eFlags)
diff --git a/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs b/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
index 60df3fd45e..5a31a6f4df 100644
--- a/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
+++ b/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
@@ -35,50 +35,47 @@ public static IEnumerable<object []> SequenceTests ()
     {
         yield return new object []
         {
-            new List<MouseEventArgs> ()
+            new List<MouseEventArgs>
             {
-                // Mouse was down
-                new ()
-                {
-                    Flags = MouseFlags.Button1Pressed
-                },
-
-                // Then it wasn't
+                new() { Flags = MouseFlags.Button1Pressed },
                 new()
             },
-            // No extra then click
             null,
             MouseFlags.Button1Clicked
         };
 
         yield return new object []
         {
-            new List<MouseEventArgs> ()
+            new List<MouseEventArgs>
             {
-                // Mouse was down
-                new ()
-                {
-                    Flags = MouseFlags.Button1Pressed
-                },
-
-                // Then it wasn't
+                new() { Flags = MouseFlags.Button1Pressed },
                 new(),
-
-                // Then it was again
-                new ()
-                {
-                    Flags = MouseFlags.Button1Pressed
-                },
-
-                // Then it wasn't
+                new() { Flags = MouseFlags.Button1Pressed },
                 new()
             },
-            // No extra then click, then into none/double click
             null,
             MouseFlags.Button1Clicked,
             null,
             MouseFlags.Button1DoubleClicked
         };
+
+
+        yield return new object []
+        {
+            new List<MouseEventArgs>
+            {
+                new() { Flags = MouseFlags.Button1Pressed ,Position = new Point (10,11)},
+                new(){Position = new Point (10,11)},
+
+                // Clicking the line below means no double click because it's a different location
+                new() { Flags = MouseFlags.Button1Pressed,Position = new Point (10,12) },
+                new(){Position = new Point (10,12)}
+            },
+            null,
+            MouseFlags.Button1Clicked,
+            null,
+            MouseFlags.Button1Clicked //release is click because new position
+        };
     }
 
 }

From 5bbf6ec90520960020d785489e4a36332169027d Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Tue, 21 Jan 2025 18:54:02 +0000
Subject: [PATCH 144/198] Function key support

---
 .../AnsiResponseParser/AnsiKeyboardParser.cs  | 86 +++++++++++++++----
 1 file changed, 68 insertions(+), 18 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
index 8a6cedeb69..73f7a7c6a9 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
@@ -1,4 +1,4 @@
-using System.Numerics;
+#nullable enable
 using System.Text.RegularExpressions;
 
 namespace Terminal.Gui;
@@ -9,6 +9,11 @@ namespace Terminal.Gui;
 /// </summary>
 public class AnsiKeyboardParser
 {
+    /*
+     * 11 to 24 are 
+     */
+    private readonly Regex _functionKey = new (@"^\u001b\[(\d+)~$");
+
     // Regex patterns for ANSI arrow keys (Up, Down, Left, Right)
     private readonly Regex _arrowKeyPattern = new (@"^\u001b\[(1;(\d+))?([A-D])$", RegexOptions.Compiled);
 
@@ -20,6 +25,15 @@ public class AnsiKeyboardParser
     /// <returns></returns>
     public Key ProcessKeyboardInput (string input)
     {
+        return
+            MapAsFunctionKey (input) ?? MapAsArrowKey (input);
+            
+    }
+
+    private Key? MapAsArrowKey (string input)
+    {
+
+
         // Match arrow key events
         Match match = _arrowKeyPattern.Match (input);
 
@@ -29,16 +43,16 @@ public Key ProcessKeyboardInput (string input)
             string modifierGroup = match.Groups [2].Value;
             char direction = match.Groups [3].Value [0];
 
-            Key key = direction switch
-                   {
-                       'A' => Key.CursorUp,
-                       'B' => Key.CursorDown,
-                       'C' => Key.CursorRight,
-                       'D' => Key.CursorLeft,
-                       _ => default (Key)
-                   };
+            Key? key = direction switch
+                      {
+                          'A' => Key.CursorUp,
+                          'B' => Key.CursorDown,
+                          'C' => Key.CursorRight,
+                          'D' => Key.CursorLeft,
+                          _ => null
+                      };
 
-            if(key == null)
+            if (key is null)
             {
                 return null;
             }
@@ -53,13 +67,13 @@ public Key ProcessKeyboardInput (string input)
             {
                 key = modifier switch
                       {
-                          2=>key.WithShift,
-                          3=>key.WithAlt,
-                          4=>key.WithAlt.WithShift,
-                          5=>key.WithCtrl,
-                          6=>key.WithCtrl.WithShift,
-                          7=>key.WithCtrl.WithAlt,
-                          8=>key.WithCtrl.WithAlt.WithShift
+                          2 => key.WithShift,
+                          3 => key.WithAlt,
+                          4 => key.WithAlt.WithShift,
+                          5 => key.WithCtrl,
+                          6 => key.WithCtrl.WithShift,
+                          7 => key.WithCtrl.WithAlt,
+                          8 => key.WithCtrl.WithAlt.WithShift
                       };
             }
 
@@ -68,6 +82,40 @@ public Key ProcessKeyboardInput (string input)
 
         // It's an unrecognized keyboard event
         return null;
+
+    }
+
+    private Key? MapAsFunctionKey (string input)
+    {
+        // Match arrow key events
+        Match match = _functionKey.Match (input);
+
+        if (match.Success)
+        {
+            string functionDigit = match.Groups [1].Value;
+
+            int digit = int.Parse (functionDigit);
+
+            return digit switch
+                   {
+                       24 => Key.F12,
+                       23 => Key.F11,
+                       21 => Key.F10,
+                       20 => Key.F9,
+                       29 => Key.F8,
+                       18 => Key.F7,
+                       17 => Key.F6,
+                       15 => Key.F5,
+                       14 => Key.F4,
+                       13 => Key.F3,
+                       12 => Key.F2,
+                       11 => Key.F1,
+                       _ => null,
+                   };
+
+        }
+
+        return null;
     }
 
     /// <summary>
@@ -76,5 +124,7 @@ public Key ProcessKeyboardInput (string input)
     /// </summary>
     /// <param name="cur">escape code</param>
     /// <returns></returns>
-    public bool IsKeyboard (string cur) { return _arrowKeyPattern.IsMatch (cur); }
+    public bool IsKeyboard (string cur) {
+        return _functionKey.IsMatch (cur) || _arrowKeyPattern.IsMatch (cur);
+    }
 }

From d6c5bf63a06320db103469b514463c9c3f0f151a Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Tue, 21 Jan 2025 19:26:00 +0000
Subject: [PATCH 145/198] Function key support including SS3 format (F1-F4 on
 some terminals).

---
 .../AnsiResponseParser/AnsiKeyboardParser.cs  |  56 ++++++--
 .../AnsiResponseParser/AnsiResponseParser.cs  |  22 ++-
 .../AnsiResponseParserState.cs                |   5 +-
 .../ConsoleDrivers/NetDriver/NetEvents.cs     |   2 +-
 .../ConsoleDrivers/V2/InputProcessor.cs       |   2 +-
 .../WindowsDriver/WindowsDriver.cs            |   2 +-
 .../ConsoleDrivers/AnsiResponseParserTests.cs | 127 ++++++++++++++++--
 7 files changed, 188 insertions(+), 28 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
index 73f7a7c6a9..e3de89d62f 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
@@ -10,7 +10,20 @@ namespace Terminal.Gui;
 public class AnsiKeyboardParser
 {
     /*
-     * 11 to 24 are 
+     *F1	\u001bOP
+       F2	\u001bOQ
+       F3	\u001bOR
+       F4	\u001bOS
+       F5 (sometimes)	\u001bOt
+       Left Arrow	\u001bOD
+       Right Arrow	\u001bOC
+       Up Arrow	\u001bOA
+       Down Arrow	\u001bOB
+     */
+    private readonly Regex _ss3Pattern = new (@"^\u001bO([PQRStDCAB])$");
+
+    /*
+     * F1 - F12
      */
     private readonly Regex _functionKey = new (@"^\u001b\[(\d+)~$");
 
@@ -23,17 +36,43 @@ public class AnsiKeyboardParser
     /// </summary>
     /// <param name="input"></param>
     /// <returns></returns>
-    public Key ProcessKeyboardInput (string input)
+    public Key? ProcessKeyboardInput (string input)
     {
-        return
-            MapAsFunctionKey (input) ?? MapAsArrowKey (input);
-            
+        return MapAsSs3Key(input) ??
+            MapAsFunctionKey (input) ??
+            MapAsArrowKey (input);
     }
 
-    private Key? MapAsArrowKey (string input)
+    private Key? MapAsSs3Key (string input)
     {
+        // Match arrow key events
+        Match match = _ss3Pattern.Match (input);
 
+        if (match.Success)
+        {
+            char finalLetter = match.Groups [1].Value.Single();
 
+            return finalLetter switch
+                   {
+                       'P' => Key.F1,
+                       'Q' => Key.F2,
+                       'R' => Key.F3,
+                       'S' => Key.F4,
+                       't' => Key.F5,
+                       'D' => Key.CursorLeft,
+                       'C' => Key.CursorRight,
+                       'A' => Key.CursorUp,
+                       'B' => Key.CursorDown,
+
+                       _ => null
+                   };
+        }
+
+        return null;
+    }
+
+    private Key? MapAsArrowKey (string input)
+    {
         // Match arrow key events
         Match match = _arrowKeyPattern.Match (input);
 
@@ -102,7 +141,7 @@ public Key ProcessKeyboardInput (string input)
                        23 => Key.F11,
                        21 => Key.F10,
                        20 => Key.F9,
-                       29 => Key.F8,
+                       19 => Key.F8,
                        18 => Key.F7,
                        17 => Key.F6,
                        15 => Key.F5,
@@ -112,7 +151,6 @@ public Key ProcessKeyboardInput (string input)
                        11 => Key.F1,
                        _ => null,
                    };
-
         }
 
         return null;
@@ -125,6 +163,6 @@ public Key ProcessKeyboardInput (string input)
     /// <param name="cur">escape code</param>
     /// <returns></returns>
     public bool IsKeyboard (string cur) {
-        return _functionKey.IsMatch (cur) || _arrowKeyPattern.IsMatch (cur);
+        return _ss3Pattern.IsMatch(cur) || _functionKey.IsMatch (cur) || _arrowKeyPattern.IsMatch (cur);
     }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
index 56604ac29a..b3c0f97f11 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
@@ -143,7 +143,7 @@ int inputLength
                     if (isEscape)
                     {
                         // Escape character detected, move to ExpectingBracket state
-                        State = AnsiResponseParserState.ExpectingBracket;
+                        State = AnsiResponseParserState.ExpectingEscapeSequence;
                         _heldContent.AddToHeld (currentObj); // Hold the escape character
                     }
                     else
@@ -154,16 +154,18 @@ int inputLength
 
                     break;
 
-                case AnsiResponseParserState.ExpectingBracket:
+                case AnsiResponseParserState.ExpectingEscapeSequence:
                     if (isEscape)
                     {
                         // Second escape so we must release first
-                        ReleaseHeld (appendOutput, AnsiResponseParserState.ExpectingBracket);
+                        ReleaseHeld (appendOutput, AnsiResponseParserState.ExpectingEscapeSequence);
                         _heldContent.AddToHeld (currentObj); // Hold the new escape
                     }
-                    else if (currentChar == '[')
+                    else if (currentChar == '[' || currentChar == 'O')
                     {
-                        // Detected '[', transition to InResponse state
+                        //We need O for SS3 mode F1-F4 e.g. "<esc>OP" => F1
+
+                        // Detected '[' or 'O', transition to InResponse state
                         State = AnsiResponseParserState.InResponse;
                         _heldContent.AddToHeld (currentObj); // Hold the '['
                     }
@@ -306,7 +308,15 @@ private void RaiseMouseEvent (string cur)
     private void RaiseKeyboardEvent (string cur)
     {
         Key? k = _keyboardParser.ProcessKeyboardInput (cur);
-        Keyboard?.Invoke (this, k);
+
+        if (k is null)
+        {
+            Logging.Logger.LogError ($"Failed to determine a Key for given Keyboard escape sequence '{cur}'");
+        }
+        else
+        {
+            Keyboard?.Invoke (this, k);
+        }
     }
 
     private bool IsKeyboard (string cur) { return _keyboardParser.IsKeyboard (cur); }
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParserState.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParserState.cs
index 934b6eb3eb..a28050f643 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParserState.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParserState.cs
@@ -12,9 +12,10 @@ public enum AnsiResponseParserState
 
     /// <summary>
     ///     Parser has encountered an Esc and is waiting to see if next
-    ///     key(s) continue to form an Ansi escape sequence
+    ///     key(s) continue to form an Ansi escape sequence (typically '[' but
+    ///     also other characters e.g. O for SS3).
     /// </summary>
-    ExpectingBracket,
+    ExpectingEscapeSequence,
 
     /// <summary>
     ///     Parser has encountered Esc[ and considers that it is in the process
diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs
index 30cf4a41a1..a83b84921c 100644
--- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs
+++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetEvents.cs
@@ -96,7 +96,7 @@ private ConsoleKeyInfo ReadConsoleKeyInfo (bool intercept = true)
 
     public IEnumerable<ConsoleKeyInfo> ShouldReleaseParserHeldKeys ()
     {
-        if (Parser.State == AnsiResponseParserState.ExpectingBracket &&
+        if (Parser.State == AnsiResponseParserState.ExpectingEscapeSequence &&
             DateTime.Now - Parser.StateChangedAt > ((NetDriver)_consoleDriver).EscTimeout)
         {
             return Parser.Release ().Select (o => o.Item2);
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index 528eaeccc3..348198dcb7 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -116,7 +116,7 @@ public void ProcessQueue ()
 
     private IEnumerable<T> ReleaseParserHeldKeysIfStale ()
     {
-        if (Parser.State == AnsiResponseParserState.ExpectingBracket && DateTime.Now - Parser.StateChangedAt > _escTimeout)
+        if (Parser.State == AnsiResponseParserState.ExpectingEscapeSequence && DateTime.Now - Parser.StateChangedAt > _escTimeout)
         {
             return Parser.Release ().Select (o => o.Item2);
         }
diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs
index 02d8a03ff7..5714fce951 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs
@@ -575,7 +575,7 @@ internal void ProcessInputAfterParsing (WindowsConsole.InputRecord inputEvent)
 
     public IEnumerable<WindowsConsole.InputRecord> ShouldReleaseParserHeldKeys ()
     {
-        if (_parser.State == AnsiResponseParserState.ExpectingBracket &&
+        if (_parser.State == AnsiResponseParserState.ExpectingEscapeSequence &&
             DateTime.Now - _parser.StateChangedAt > EscTimeout)
         {
             return _parser.Release ().Select (o => o.Item2);
diff --git a/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs b/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs
index b851d47044..46516b5f7a 100644
--- a/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs
+++ b/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs
@@ -153,7 +153,7 @@ public static IEnumerable<object []> TestInputSequencesExact_Cases ()
             null,
             new []
             {
-                new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingBracket,string.Empty)
+                new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingEscapeSequence,string.Empty)
             }
         ];
 
@@ -163,13 +163,13 @@ public static IEnumerable<object []> TestInputSequencesExact_Cases ()
             'c',
             new []
             {
-                new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingBracket,string.Empty),
+                new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingEscapeSequence,string.Empty),
                 new StepExpectation ('H',AnsiResponseParserState.Normal,"\u001bH"), // H is known terminator and not expected one so here we release both chars
-                new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingBracket,string.Empty),
+                new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingEscapeSequence,string.Empty),
                 new StepExpectation ('[',AnsiResponseParserState.InResponse,string.Empty),
                 new StepExpectation ('0',AnsiResponseParserState.InResponse,string.Empty),
                 new StepExpectation ('c',AnsiResponseParserState.Normal,string.Empty,"\u001b[0c"), // c is expected terminator so here we swallow input and populate expected response
-                new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingBracket,string.Empty),
+                new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingEscapeSequence,string.Empty),
             }
         ];
     }
@@ -260,8 +260,8 @@ public void ReleasesEscapeAfterTimeout ()
         AssertConsumed (input,ref i);
 
         // We should know when the state changed
-        Assert.Equal (AnsiResponseParserState.ExpectingBracket, _parser1.State);
-        Assert.Equal (AnsiResponseParserState.ExpectingBracket, _parser2.State);
+        Assert.Equal (AnsiResponseParserState.ExpectingEscapeSequence, _parser1.State);
+        Assert.Equal (AnsiResponseParserState.ExpectingEscapeSequence, _parser2.State);
 
         Assert.Equal (DateTime.Now.Date, _parser1.StateChangedAt.Date);
         Assert.Equal (DateTime.Now.Date, _parser2.StateChangedAt.Date);
@@ -299,8 +299,8 @@ public void TwoExcapesInARowWithTextBetween ()
 
         // First Esc gets grabbed
         AssertConsumed (input, ref i); // Esc
-        Assert.Equal (AnsiResponseParserState.ExpectingBracket,_parser1.State);
-        Assert.Equal (AnsiResponseParserState.ExpectingBracket, _parser2.State);
+        Assert.Equal (AnsiResponseParserState.ExpectingEscapeSequence,_parser1.State);
+        Assert.Equal (AnsiResponseParserState.ExpectingEscapeSequence, _parser2.State);
 
         // Because next char is 'f' we do not see a bracket so release both
         AssertReleased (input, ref i, "\u001bf", 0,1); // f
@@ -548,6 +548,117 @@ public void ParserDetectsKeyboard ()
         Assert.Equal (Key.CursorUp.WithShift, keys [1]);
     }
 
+    public static IEnumerable<object []> ParserDetects_FunctionKeys_Cases ()
+    {
+        // These are VT100 escape codes for F1-4
+        yield return
+        [
+            "\u001bOP",
+            Key.F1
+        ];
+
+        yield return
+        [
+            "\u001bOQ",
+            Key.F2
+        ];
+
+        yield return
+        [
+            "\u001bOR",
+            Key.F3
+        ];
+
+        yield return
+        [
+            "\u001bOS",
+            Key.F4
+        ];
+
+
+        // These are also F keys
+        yield return [
+                         "\u001b[11~",
+                         Key.F1
+                     ];
+
+        yield return [
+                         "\u001b[12~",
+                         Key.F2
+                     ];
+
+        yield return [
+                         "\u001b[13~",
+                         Key.F3
+                     ];
+
+        yield return [
+                         "\u001b[14~",
+                         Key.F4
+                     ];
+
+        yield return [
+                         "\u001b[15~",
+                         Key.F5
+                     ];
+
+        yield return [
+                         "\u001b[17~",
+                         Key.F6
+                     ];
+
+        yield return [
+                         "\u001b[18~",
+                         Key.F7
+                     ];
+
+        yield return [
+                         "\u001b[19~",
+                         Key.F8
+                     ];
+
+        yield return [
+                         "\u001b[20~",
+                         Key.F9
+                     ];
+
+        yield return [
+                         "\u001b[21~",
+                         Key.F10
+                     ];
+
+        yield return [
+                         "\u001b[23~",
+                         Key.F11
+                     ];
+
+        yield return [
+                         "\u001b[24~",
+                         Key.F12
+                     ];
+    }
+
+    [MemberData (nameof (ParserDetects_FunctionKeys_Cases))]
+
+    [Theory]
+    public void ParserDetects_FunctionKeys (string input, Key expectedKey)
+    {
+        var parser = new AnsiResponseParser ();
+
+        parser.HandleKeyboard = true;
+        List<Key> keys = new ();
+
+        parser.Keyboard += (s, e) => keys.Add (e);
+
+        foreach (var ch in input.ToCharArray ())
+        {
+            parser.ProcessInput (new (ch,1));
+        }
+        var k = Assert.Single (keys);
+
+        Assert.Equal (k,expectedKey);
+    }
+
     private Tuple<char, int> [] StringToBatch (string batch)
     {
         return batch.Select ((k) => Tuple.Create (k, tIndex++)).ToArray ();

From 3d99627bfdeb096acb288a1bc822d673722553e4 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 30 Jan 2025 08:03:41 +0000
Subject: [PATCH 146/198] Add mouse wheel to windriver

---
 .../ConsoleDrivers/V2/WindowsInputProcessor.cs   | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
index 6192e3bbda..f505406473 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
@@ -134,6 +134,22 @@ private MouseEventArgs ToDriverMouse (MouseEventRecord e)
                     }
         };
 
+        if (e.EventFlags == WindowsConsole.EventFlags.MouseWheeled)
+        {
+            switch ((int)e.ButtonState)
+            {
+                case int v when v > 0:
+                    result.Flags = MouseFlags.WheeledUp;
+
+                    break;
+
+                case int v when v < 0:
+                    result.Flags = MouseFlags.WheeledDown;
+
+                    break;
+            }
+        }
+
         // TODO: Return keys too
 
         return result;

From b18ef29633030498c577cbd841fb86dc98c865cd Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Fri, 31 Jan 2025 07:58:53 +0000
Subject: [PATCH 147/198] Fix AllViewsTester - Ignore double init - Raise init
 changed event

---
 Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index 2e3ce2db25..75d1001a24 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -52,6 +52,13 @@ Func<IConsoleOutput> winOutputFactory
     [RequiresDynamicCode ("AOT")]
     public override void Init (IConsoleDriver? driver = null, string? driverName = null)
     {
+
+        if (Application.Initialized)
+        {
+            Logging.Logger.LogError ("Init called multiple times without shutdown, ignoring.");
+            return;
+        }
+
         if (!string.IsNullOrWhiteSpace (driverName))
         {
             _driverName = driverName;
@@ -67,6 +74,7 @@ public override void Init (IConsoleDriver? driver = null, string? driverName = n
 
         Application.Initialized = true;
 
+        Application.OnInitializedChanged (this, new (true));
         Application.SubscribeDriverEvents ();
     }
 

From 98a870d85828096532ecaf6ae61e79d8b0b3d0f4 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Fri, 31 Jan 2025 08:31:26 +0000
Subject: [PATCH 148/198] Fix not loading config manager in v2

---
 .../Application/Application.Initialization.cs | 31 +++++++++++--------
 .../ConsoleDrivers/V2/ApplicationV2.cs        |  3 ++
 2 files changed, 21 insertions(+), 13 deletions(-)

diff --git a/Terminal.Gui/Application/Application.Initialization.cs b/Terminal.Gui/Application/Application.Initialization.cs
index 87a61c28ff..a74f1545d1 100644
--- a/Terminal.Gui/Application/Application.Initialization.cs
+++ b/Terminal.Gui/Application/Application.Initialization.cs
@@ -102,19 +102,7 @@ internal static void InternalInit (
 
         AddKeyBindings ();
 
-        // Start the process of configuration management.
-        // Note that we end up calling LoadConfigurationFromAllSources
-        // multiple times. We need to do this because some settings are only
-        // valid after a Driver is loaded. In this case we need just
-        // `Settings` so we can determine which driver to use.
-        // Don't reset, so we can inherit the theme from the previous run.
-        string previousTheme = Themes?.Theme ?? string.Empty;
-        Load ();
-        if (Themes is { } && !string.IsNullOrEmpty (previousTheme) && previousTheme != "Default")
-        {
-            ThemeManager.SelectedTheme = previousTheme;
-        }
-        Apply ();
+        InitializeConfigurationManagement ();
 
         // Ignore Configuration for ForceDriver if driverName is specified
         if (!string.IsNullOrEmpty (driverName))
@@ -179,6 +167,23 @@ internal static void InternalInit (
         InitializedChanged?.Invoke (null, new (init));
     }
 
+    internal static void InitializeConfigurationManagement ()
+    {
+        // Start the process of configuration management.
+        // Note that we end up calling LoadConfigurationFromAllSources
+        // multiple times. We need to do this because some settings are only
+        // valid after a Driver is loaded. In this case we need just
+        // `Settings` so we can determine which driver to use.
+        // Don't reset, so we can inherit the theme from the previous run.
+        string previousTheme = Themes?.Theme ?? string.Empty;
+        Load ();
+        if (Themes is { } && !string.IsNullOrEmpty (previousTheme) && previousTheme != "Default")
+        {
+            ThemeManager.SelectedTheme = previousTheme;
+        }
+        Apply ();
+    }
+
     internal static void SubscribeDriverEvents ()
     {
         ArgumentNullException.ThrowIfNull (Driver);
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index 75d1001a24..76c30f0a0d 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -72,12 +72,15 @@ public override void Init (IConsoleDriver? driver = null, string? driverName = n
         // making it use custom driver in future shutdown/init calls where no driver is specified
         CreateDriver (driverName ?? _driverName);
 
+        Application.InitializeConfigurationManagement ();
+
         Application.Initialized = true;
 
         Application.OnInitializedChanged (this, new (true));
         Application.SubscribeDriverEvents ();
     }
 
+
     private void CreateDriver (string? driverName)
     {
         PlatformID p = Environment.OSVersion.Platform;

From 4ad95eead5ab74a80fe800c75c158325f4c320f2 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 2 Feb 2025 15:42:54 +0000
Subject: [PATCH 149/198] Hack for stale layout/draw when a child view is
 closed (e.g. modal About dialog)

---
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 9a812b89e2..de814437dc 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -113,6 +113,8 @@ internal void IterationImpl ()
     {
         InputProcessor.ProcessQueue ();
 
+        HackLayoutDrawIfTopChanged ();
+
         if (Application.Top != null)
         {
             bool needsDrawOrLayout = AnySubviewsNeedDrawn (Application.Top);
@@ -142,6 +144,21 @@ internal void IterationImpl ()
         Logging.IterationInvokesAndTimeouts.Record (swCallbacks.Elapsed.Milliseconds);
     }
 
+    private View? _lastTop;
+    private void HackLayoutDrawIfTopChanged ()
+    {
+        // TODO: This fixes closing a modal not making its host (below) refresh
+        //       until you click.  This should not be the job of the main loop!
+        var newTop = Application.Top;
+        if (_lastTop != null && _lastTop != newTop && newTop != null)
+        {
+            newTop.SetNeedsDraw();
+            newTop.SetNeedsLayout ();
+        }
+        
+        _lastTop = Application.Top;
+    }
+
     private void SetCursor ()
     {
         View? mostFocused = Application.Top.MostFocused;

From 8689fe944c71c5e62e3568fdc98e6631f9330f7e Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 2 Feb 2025 17:33:12 +0000
Subject: [PATCH 150/198] Fix WindowsInputProcessor to use the full key mapping
 code from WindowsDriver

---
 .../V2/WindowsInputProcessor.cs               | 69 ++++++++-----------
 .../WindowsDriver/WindowsDriver.cs            |  6 +-
 2 files changed, 31 insertions(+), 44 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
index f505406473..071d7fffdc 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
@@ -1,4 +1,5 @@
-using System.Collections.Concurrent;
+#nullable enable
+using System.Collections.Concurrent;
 using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
 using static Terminal.Gui.WindowsConsole;
 
@@ -68,52 +69,38 @@ protected override void Process (InputRecord inputEvent)
     /// <inheritdoc/>
     protected override void ProcessAfterParsing (InputRecord input)
     {
-        Key key;
+        // TODO: This should be in a shared helper method not calling statics in WindowsDriver
+        if(InputRecordToKey (input, out var key))
+        {
+            OnKeyDown (key!);
+            OnKeyUp (key!);
+        }
+    }
 
-        if (input.KeyEvent.UnicodeChar == '\0')
+    private bool InputRecordToKey (InputRecord inputEvent, out Key? key)
+    {
+        if (inputEvent.KeyEvent.wVirtualKeyCode == (VK)ConsoleKey.Packet)
         {
-            key = input.KeyEvent.wVirtualKeyCode switch
-                  {
-                      VK.DOWN => Key.CursorDown,
-                      VK.UP => Key.CursorUp,
-                      VK.LEFT => Key.CursorLeft,
-                      VK.RIGHT => Key.CursorRight,
-                      VK.BACK => Key.Backspace,
-                      VK.TAB => Key.Tab,
-                      VK.F1 => Key.F1,
-                      VK.F2 => Key.F2,
-                      VK.F3 => Key.F3,
-                      VK.F4 => Key.F4,
-                      VK.F5 => Key.F5,
-                      VK.F6 => Key.F6,
-                      VK.F7 => Key.F7,
-                      VK.F8 => Key.F8,
-                      VK.F9 => Key.F9,
-                      VK.F10 => Key.F10,
-                      VK.F11 => Key.F11,
-                      VK.F12 => Key.F12,
-                      VK.F13 => Key.F13,
-                      VK.F14 => Key.F14,
-                      VK.F15 => Key.F15,
-                      VK.F16 => Key.F16,
-                      VK.F17 => Key.F17,
-                      VK.F18 => Key.F18,
-                      VK.F19 => Key.F19,
-                      VK.F20 => Key.F20,
-                      VK.F21 => Key.F21,
-                      VK.F22 => Key.F22,
-                      VK.F23 => Key.F23,
-                      VK.F24 => Key.F24,
-                      _ => '\0'
-                  };
+            // Used to pass Unicode characters as if they were keystrokes.
+            // The VK_PACKET key is the low word of a 32-bit
+            // Virtual Key value used for non-keyboard input methods.
+            inputEvent.KeyEvent = WindowsDriver.FromVKPacketToKeyEventRecord (inputEvent.KeyEvent);
         }
-        else
+
+        WindowsConsole.ConsoleKeyInfoEx keyInfo = WindowsDriver.ToConsoleKeyInfoEx (inputEvent.KeyEvent);
+
+        //Debug.WriteLine ($"event: KBD: {GetKeyboardLayoutName()} {inputEvent.ToString ()} {keyInfo.ToString (keyInfo)}");
+
+        KeyCode map = WindowsDriver.MapKey (keyInfo);
+
+        if (map == KeyCode.Null)
         {
-            key = input.KeyEvent.UnicodeChar;
+            key = null;
+            return false;
         }
 
-        OnKeyDown (key);
-        OnKeyUp (key);
+        key = new Key (map);
+        return true;
     }
 
     private MouseEventArgs ToDriverMouse (MouseEventRecord e)
diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs
index 5714fce951..34da9f24c1 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs
@@ -70,7 +70,7 @@ public WindowsDriver ()
 
     public WindowsConsole? WinConsole { get; private set; }
 
-    public WindowsConsole.KeyEventRecord FromVKPacketToKeyEventRecord (WindowsConsole.KeyEventRecord keyEvent)
+    public static WindowsConsole.KeyEventRecord FromVKPacketToKeyEventRecord (WindowsConsole.KeyEventRecord keyEvent)
     {
         if (keyEvent.wVirtualKeyCode != (VK)ConsoleKey.Packet)
         {
@@ -203,7 +203,7 @@ public override void WriteRaw (string str)
 
     #endregion
 
-    public WindowsConsole.ConsoleKeyInfoEx ToConsoleKeyInfoEx (WindowsConsole.KeyEventRecord keyEvent)
+    public static WindowsConsole.ConsoleKeyInfoEx ToConsoleKeyInfoEx (WindowsConsole.KeyEventRecord keyEvent)
     {
         WindowsConsole.ControlKeyState state = keyEvent.dwControlKeyState;
 
@@ -620,7 +620,7 @@ private void ChangeWin (object s, SizeChangedEventArgs e)
     }
 #endif
 
-    private KeyCode MapKey (WindowsConsole.ConsoleKeyInfoEx keyInfoEx)
+    public static KeyCode MapKey (WindowsConsole.ConsoleKeyInfoEx keyInfoEx)
     {
         ConsoleKeyInfo keyInfo = keyInfoEx.ConsoleKeyInfo;
 

From a7f13d3d49600d6dfa24804f607888eaec8a760a Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Wed, 5 Feb 2025 07:27:29 +0000
Subject: [PATCH 151/198] Move the 'convert to Key' bit of InputProcessor to
 seperate class for easier testing.

---
 .../ConsoleDrivers/V2/IKeyConverter.cs        | 18 ++++++++++
 .../ConsoleDrivers/V2/InputProcessor.cs       |  7 ++--
 .../ConsoleDrivers/V2/NetInputProcessor.cs    | 25 ++-----------
 .../ConsoleDrivers/V2/NetKeyConverter.cs      | 19 ++++++++++
 .../V2/WindowsInputProcessor.cs               | 36 +++----------------
 .../ConsoleDrivers/V2/WindowsKeyConverter.cs  | 32 +++++++++++++++++
 Terminal.Gui/Input/Keyboard/Key.cs            |  2 +-
 .../V2/NetInputProcessorTests.cs              |  7 ++--
 8 files changed, 88 insertions(+), 58 deletions(-)
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/IKeyConverter.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/NetKeyConverter.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/WindowsKeyConverter.cs

diff --git a/Terminal.Gui/ConsoleDrivers/V2/IKeyConverter.cs b/Terminal.Gui/ConsoleDrivers/V2/IKeyConverter.cs
new file mode 100644
index 0000000000..f53695a397
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/IKeyConverter.cs
@@ -0,0 +1,18 @@
+namespace Terminal.Gui;
+
+/// <summary>
+/// Interface for subcomponent of a <see cref="InputProcessor{T}"/> which
+/// can translate the raw console input type T (which typically varies by
+/// driver) to the shared Terminal.Gui <see cref="Key"/> class.
+/// </summary>
+/// <typeparam name="T"></typeparam>
+public interface IKeyConverter<in T>
+{
+    /// <summary>
+    /// Converts the native keyboard class read from console into
+    /// the shared <see cref="Key"/> class used by Terminal.Gui views.
+    /// </summary>
+    /// <param name="value"></param>
+    /// <returns></returns>
+    Key ToKey(T value);
+}
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index 348198dcb7..bd2f30bc29 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -18,6 +18,8 @@ public abstract class InputProcessor<T> : IInputProcessor
 
     internal AnsiResponseParser<T> Parser { get; } = new ();
 
+    public IKeyConverter<T> KeyConverter { get; }
+
     /// <summary>
     /// Input buffer which will be drained from by this class.
     /// </summary>
@@ -76,7 +78,7 @@ public void OnMouseEvent (MouseEventArgs a)
     /// the provided thread safe input collection.
     /// </summary>
     /// <param name="inputBuffer"></param>
-    protected InputProcessor (ConcurrentQueue<T> inputBuffer)
+    protected InputProcessor (ConcurrentQueue<T> inputBuffer, IKeyConverter<T> keyConverter)
     {
         InputBuffer = inputBuffer;
         Parser.HandleMouse = true;
@@ -93,9 +95,10 @@ protected InputProcessor (ConcurrentQueue<T> inputBuffer)
         // TODO: For now handle all other escape codes with ignore
         Parser.UnexpectedResponseHandler = str =>
                                            {
-                                               Logging.Logger.LogInformation ($"{nameof(InputProcessor<T>)} ignored unrecognized response '{new string(str.Select (k=>k.Item1).ToArray ())}'");
+                                               Logging.Logger.LogInformation ($"{nameof (InputProcessor<T>)} ignored unrecognized response '{new string (str.Select (k => k.Item1).ToArray ())}'");
                                                return true;
                                            };
+        KeyConverter = keyConverter;
     }
 
     /// <summary>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
index 36fe76f855..ae93c5d67b 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
@@ -19,7 +19,7 @@ public class NetInputProcessor : InputProcessor<ConsoleKeyInfo>
     #pragma warning enable CA2211
 
     /// <inheritdoc/>
-    public NetInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer) : base (inputBuffer) { }
+    public NetInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer) : base (inputBuffer, new NetKeyConverter()) { }
 
     /// <inheritdoc/>
     protected override void Process (ConsoleKeyInfo consoleKeyInfo)
@@ -39,30 +39,11 @@ protected override void ProcessAfterParsing (ConsoleKeyInfo input)
             Logging.Logger.LogTrace (FormatConsoleKeyInfoForTestCase (input));
         }
 
-        Key key = ConsoleKeyInfoToKey (input);
+        Key key = KeyConverter.ToKey (input);
         OnKeyDown (key);
         OnKeyUp (key);
     }
 
-    /// <summary>
-    /// Converts terminal raw input class <see cref="ConsoleKeyInfo"/> into
-    /// common Terminal.Gui event model for keypresses (<see cref="Key"/>)
-    /// </summary>
-    /// <param name="input"></param>
-    /// <returns></returns>
-    public static Key ConsoleKeyInfoToKey (ConsoleKeyInfo input)
-    {
-        ConsoleKeyInfo adjustedInput = EscSeqUtils.MapConsoleKeyInfo (input);
-
-        // TODO : EscSeqUtils.MapConsoleKeyInfo is wrong for e.g. '{' - it winds up clearing the Key
-        //        So if the method nuked it then we should just work with the original.
-        if (adjustedInput.Key == ConsoleKey.None && input.Key != ConsoleKey.None)
-        {
-            return EscSeqUtils.MapKey (input);
-        }
-
-        return EscSeqUtils.MapKey (adjustedInput);
-    }
 
     /* For building test cases */
     private static string FormatConsoleKeyInfoForTestCase (ConsoleKeyInfo input)
@@ -75,4 +56,4 @@ private static string FormatConsoleKeyInfoForTestCase (ConsoleKeyInfo input)
                $"{input.Modifiers.HasFlag (ConsoleModifiers.Alt).ToString ().ToLower ()}, " +
                $"{input.Modifiers.HasFlag (ConsoleModifiers.Control).ToString ().ToLower ()}), {expectedLiteral} }};";
     }
-}
+}
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetKeyConverter.cs b/Terminal.Gui/ConsoleDrivers/V2/NetKeyConverter.cs
new file mode 100644
index 0000000000..2df67e7410
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetKeyConverter.cs
@@ -0,0 +1,19 @@
+namespace Terminal.Gui;
+
+public class NetKeyConverter : IKeyConverter<ConsoleKeyInfo>
+{
+    /// <inheritdoc />
+    public Key ToKey (ConsoleKeyInfo input)
+    {
+        ConsoleKeyInfo adjustedInput = EscSeqUtils.MapConsoleKeyInfo (input);
+
+        // TODO : EscSeqUtils.MapConsoleKeyInfo is wrong for e.g. '{' - it winds up clearing the Key
+        //        So if the method nuked it then we should just work with the original.
+        if (adjustedInput.Key == ConsoleKey.None && input.Key != ConsoleKey.None)
+        {
+            return EscSeqUtils.MapKey (input);
+        }
+
+        return EscSeqUtils.MapKey (adjustedInput);
+    }
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
index 071d7fffdc..b7c1a997be 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
@@ -1,6 +1,5 @@
 #nullable enable
 using System.Collections.Concurrent;
-using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
 using static Terminal.Gui.WindowsConsole;
 
 namespace Terminal.Gui;
@@ -13,7 +12,7 @@ namespace Terminal.Gui;
 internal class WindowsInputProcessor : InputProcessor<InputRecord>
 {
     /// <inheritdoc/>
-    public WindowsInputProcessor (ConcurrentQueue<InputRecord> inputBuffer) : base (inputBuffer) { }
+    public WindowsInputProcessor (ConcurrentQueue<InputRecord> inputBuffer) : base (inputBuffer, new WindowsKeyConverter()) { }
 
     /// <inheritdoc/>
     protected override void Process (InputRecord inputEvent)
@@ -69,40 +68,15 @@ protected override void Process (InputRecord inputEvent)
     /// <inheritdoc/>
     protected override void ProcessAfterParsing (InputRecord input)
     {
-        // TODO: This should be in a shared helper method not calling statics in WindowsDriver
-        if(InputRecordToKey (input, out var key))
+        var key = KeyConverter.ToKey (input);
+
+        if(key != (Key)0)
         {
             OnKeyDown (key!);
             OnKeyUp (key!);
         }
     }
 
-    private bool InputRecordToKey (InputRecord inputEvent, out Key? key)
-    {
-        if (inputEvent.KeyEvent.wVirtualKeyCode == (VK)ConsoleKey.Packet)
-        {
-            // Used to pass Unicode characters as if they were keystrokes.
-            // The VK_PACKET key is the low word of a 32-bit
-            // Virtual Key value used for non-keyboard input methods.
-            inputEvent.KeyEvent = WindowsDriver.FromVKPacketToKeyEventRecord (inputEvent.KeyEvent);
-        }
-
-        WindowsConsole.ConsoleKeyInfoEx keyInfo = WindowsDriver.ToConsoleKeyInfoEx (inputEvent.KeyEvent);
-
-        //Debug.WriteLine ($"event: KBD: {GetKeyboardLayoutName()} {inputEvent.ToString ()} {keyInfo.ToString (keyInfo)}");
-
-        KeyCode map = WindowsDriver.MapKey (keyInfo);
-
-        if (map == KeyCode.Null)
-        {
-            key = null;
-            return false;
-        }
-
-        key = new Key (map);
-        return true;
-    }
-
     private MouseEventArgs ToDriverMouse (MouseEventRecord e)
     {
         var result = new MouseEventArgs
@@ -141,4 +115,4 @@ private MouseEventArgs ToDriverMouse (MouseEventRecord e)
 
         return result;
     }
-}
+}
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsKeyConverter.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsKeyConverter.cs
new file mode 100644
index 0000000000..0edf381a9c
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsKeyConverter.cs
@@ -0,0 +1,32 @@
+#nullable enable
+using Terminal.Gui.ConsoleDrivers;
+
+namespace Terminal.Gui;
+
+internal class WindowsKeyConverter : IKeyConverter<WindowsConsole.InputRecord>
+{
+    /// <inheritdoc />
+    public Key ToKey (WindowsConsole.InputRecord inputEvent)
+    {
+        if (inputEvent.KeyEvent.wVirtualKeyCode == (ConsoleKeyMapping.VK)ConsoleKey.Packet)
+        {
+            // Used to pass Unicode characters as if they were keystrokes.
+            // The VK_PACKET key is the low word of a 32-bit
+            // Virtual Key value used for non-keyboard input methods.
+            inputEvent.KeyEvent = WindowsDriver.FromVKPacketToKeyEventRecord (inputEvent.KeyEvent);
+        }
+
+        WindowsConsole.ConsoleKeyInfoEx keyInfo = WindowsDriver.ToConsoleKeyInfoEx (inputEvent.KeyEvent);
+
+        //Debug.WriteLine ($"event: KBD: {GetKeyboardLayoutName()} {inputEvent.ToString ()} {keyInfo.ToString (keyInfo)}");
+
+        KeyCode map = WindowsDriver.MapKey (keyInfo);
+
+        if (map == KeyCode.Null)
+        {
+            return (Key)0;
+        }
+
+        return new Key (map);
+    }
+}
diff --git a/Terminal.Gui/Input/Keyboard/Key.cs b/Terminal.Gui/Input/Keyboard/Key.cs
index cf524b6e5b..53d6292756 100644
--- a/Terminal.Gui/Input/Keyboard/Key.cs
+++ b/Terminal.Gui/Input/Keyboard/Key.cs
@@ -418,7 +418,7 @@ public override bool Equals (object? obj)
     /// <param name="a"></param>
     /// <param name="b"></param>
     /// <returns></returns>
-    public static bool operator != (Key a, Key b) { return !a!.Equals (b); }
+    public static bool operator != (Key a, Key? b) { return !a!.Equals (b); }
 
     /// <summary>Compares two <see cref="Key"/>s for less-than.</summary>
     /// <param name="a"></param>
diff --git a/UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs b/UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs
index 8a15c76caa..6b32f13903 100644
--- a/UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs
+++ b/UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs
@@ -55,8 +55,10 @@ public static IEnumerable<object []> GetConsoleKeyInfoToKeyTestCases_Rune ()
     [MemberData (nameof (GetConsoleKeyInfoToKeyTestCases_Rune))]
     public void ConsoleKeyInfoToKey_ValidInput_AsRune (ConsoleKeyInfo input, Rune expected)
     {
+        var converter = new NetKeyConverter ();
+
         // Act
-        var result = NetInputProcessor.ConsoleKeyInfoToKey (input);
+        var result = converter.ToKey (input);
 
         // Assert
         Assert.Equal (expected, result.AsRune);
@@ -80,8 +82,9 @@ public static IEnumerable<object []> GetConsoleKeyInfoToKeyTestCases_Key ()
     [MemberData (nameof (GetConsoleKeyInfoToKeyTestCases_Key))]
     public void ConsoleKeyInfoToKey_ValidInput_AsKey (ConsoleKeyInfo input, Key expected)
     {
+        var converter = new NetKeyConverter ();
         // Act
-        var result = NetInputProcessor.ConsoleKeyInfoToKey (input);
+        var result = converter.ToKey (input);
 
         // Assert
         Assert.Equal (expected, result);

From 5a7488765560a67a08deb08bb6219a673e89c56b Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Wed, 5 Feb 2025 07:31:26 +0000
Subject: [PATCH 152/198] Update class diagram

---
 Terminal.Gui/ConsoleDrivers/V2/V2.cd | 52 ++++++++++++++++++++++------
 1 file changed, 42 insertions(+), 10 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index bcfb6076f1..53ffa8fa60 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -108,7 +108,7 @@
       </MemberNameLabel>
     </AssociationLine>
     <TypeIdentifier>
-      <HashCode>QQQAAAAQACABJQQAABAAAAAAACAAAAACAAEAAACAEgg=</HashCode>
+      <HashCode>QQQAAAAQACABJQQAABAAAAAAACIAAAACIAEAAACAEgg=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoop.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -169,7 +169,7 @@
   </Class>
   <Class Name="Terminal.Gui.InputProcessor&lt;T&gt;" Collapsed="true">
     <Position X="16.5" Y="4.75" Width="2" />
-    <AssociationLine Name="_mouseInterpreter" Type="Terminal.Gui.MouseInterpreter" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
+    <AssociationLine Name="_mouseInterpreter" Type="Terminal.Gui.MouseInterpreter" ManuallyRouted="true">
       <Path>
         <Point X="17.75" Y="5.312" />
         <Point X="17.75" Y="10.031" />
@@ -179,19 +179,20 @@
       </Path>
     </AssociationLine>
     <TypeIdentifier>
-      <HashCode>AQAkEAAAAASAiAAAAgwgAAAABAIAAAAAAAAAAAAAAAA=</HashCode>
+      <HashCode>AQAkEAAAAASAiAAEAgwgAAAABAIAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\InputProcessor.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
       <Field Name="_mouseInterpreter" />
       <Property Name="Parser" />
+      <Property Name="KeyConverter" />
     </ShowAsAssociation>
     <Lollipop Position="0.1" />
   </Class>
   <Class Name="Terminal.Gui.NetInputProcessor" Collapsed="true">
     <Position X="17.75" Y="5.75" Width="2" />
     <TypeIdentifier>
-      <HashCode>AAEAAAAAAAAACBAAAgAAAEAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <HashCode>AAAAAAAAAAAACBAAAgAAAEAAAAAAAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\NetInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Class>
@@ -203,7 +204,7 @@
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.AnsiMouseParser">
-    <Position X="24" Y="9.5" Width="1.75" />
+    <Position X="23.5" Y="9.5" Width="1.75" />
     <TypeIdentifier>
       <HashCode>BAAAAAAAAAgAAAAAAAAAAAAAIAAAAAAAQAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiMouseParser.cs</FileName>
@@ -240,6 +241,7 @@
     <ShowAsAssociation>
       <Field Name="_mouseParser" />
       <Field Name="_heldContent" />
+      <Field Name="_keyboardParser" />
     </ShowAsAssociation>
     <Lollipop Position="0.2" />
   </Class>
@@ -256,12 +258,12 @@
   <Class Name="Terminal.Gui.MouseButtonStateEx">
     <Position X="16.5" Y="10.25" Width="2" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAEwAIAAAAAAAAAAAABCAAAAAAAAABAAEAAg=</HashCode>
+      <HashCode>AAAAAAAAAMwAIAAAAAAAAAAAABCAAAAAAAAABAAEAAg=</HashCode>
       <FileName>ConsoleDrivers\V2\MouseButtonState.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.StringHeld" Collapsed="true">
-    <Position X="21.75" Y="11" Width="1.75" />
+    <Position X="21.5" Y="11" Width="1.75" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAIAACAAAAAAAIAAAAAAAACAAAAAAAgAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\StringHeld.cs</FileName>
@@ -269,7 +271,7 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.GenericHeld&lt;T&gt;" Collapsed="true">
-    <Position X="20" Y="11" Width="1.75" />
+    <Position X="19.75" Y="11" Width="1.75" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAgAIAACAAAAAAAIAAAAAAAACAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\GenericHeld.cs</FileName>
@@ -300,7 +302,7 @@
   <Class Name="Terminal.Gui.Application" Collapsed="true">
     <Position X="0.5" Y="0.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>hEK4FAgAqARIspQeBwoUgTGgACNL0AIAESJKoggBSw8=</HashCode>
+      <HashCode>hEK4FAgAqARIspQeBwoUgTGgACNL0AIAESLKoggBSw8=</HashCode>
       <FileName>Application\Application.cs</FileName>
     </TypeIdentifier>
   </Class>
@@ -333,6 +335,29 @@
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
+  <Class Name="Terminal.Gui.WindowsKeyConverter" Collapsed="true">
+    <Position X="16" Y="7.5" Width="1.75" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\WindowsKeyConverter.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Class Name="Terminal.Gui.NetKeyConverter" Collapsed="true">
+    <Position X="17.75" Y="7.5" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\NetKeyConverter.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Class Name="Terminal.Gui.AnsiKeyboardParser">
+    <Position X="25.5" Y="9.5" Width="1.75" />
+    <TypeIdentifier>
+      <HashCode>AAAAAQEAAAAAAAAAAAAAAAAAAAQgAAAAAAABCAAAAAE=</HashCode>
+      <FileName>ConsoleDrivers\AnsiResponseParser\AnsiKeyboardParser.cs</FileName>
+    </TypeIdentifier>
+  </Class>
   <Interface Name="Terminal.Gui.IConsoleInput&lt;T&gt;" Collapsed="true">
     <Position X="12.5" Y="1" Width="1.5" />
     <TypeIdentifier>
@@ -416,10 +441,17 @@
       <FileName>Application\ITimedEvents.cs</FileName>
     </TypeIdentifier>
   </Interface>
+  <Interface Name="Terminal.Gui.IKeyConverter&lt;T&gt;" Collapsed="true">
+    <Position X="17" Y="6.5" Width="1.75" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\IKeyConverter.cs</FileName>
+    </TypeIdentifier>
+  </Interface>
   <Enum Name="Terminal.Gui.AnsiResponseParserState">
     <Position X="20.25" Y="7.25" Width="2" />
     <TypeIdentifier>
-      <HashCode>AAAAAEAAAAAAAAAAAAAAAAAAAAAIAAIAAAAAAAAAAAA=</HashCode>
+      <HashCode>AAAAAAAAAAAAAAAAAAAACAAAAAAIAAIAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParserState.cs</FileName>
     </TypeIdentifier>
   </Enum>

From a46d56ef9905051221e3ddce90918be6082c58a8 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Wed, 5 Feb 2025 19:18:33 +0000
Subject: [PATCH 153/198] Do not call for layout just because Top changed

---
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index de814437dc..0aaeca2409 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -153,7 +153,6 @@ private void HackLayoutDrawIfTopChanged ()
         if (_lastTop != null && _lastTop != newTop && newTop != null)
         {
             newTop.SetNeedsDraw();
-            newTop.SetNeedsLayout ();
         }
         
         _lastTop = Application.Top;

From d711131c5a2565c0c2ce03670bea9bd0a6731873 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Wed, 5 Feb 2025 19:41:04 +0000
Subject: [PATCH 154/198] Log more info about click events being raised

---
 Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
index a9b2f198e6..5f896a729e 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
@@ -63,7 +63,7 @@ private MouseEventArgs RaiseClick (int button, int numberOfClicks, MouseEventArg
             View = mouseEventArgs.View,
             Position = mouseEventArgs.Position
         };
-        Logging.Logger.LogTrace ($"Raising click event:{newClick.Flags}");
+        Logging.Logger.LogTrace ($"Raising click event:{newClick.Flags} at screen {newClick.ScreenPosition}");
         return newClick;
     }
 

From f45ecd022720090e59406b509406c7a682f5e8a3 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 8 Feb 2025 17:46:26 +0000
Subject: [PATCH 155/198] Add report mouse position to all win mouse events

- fixes click drag v2 win
---
 Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs     |  1 +
 .../ConsoleDrivers/V2/WindowsInputProcessor.cs       | 12 ++++++------
 2 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index bd2f30bc29..4ce2b40a18 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -67,6 +67,7 @@ public void OnMouseEvent (MouseEventArgs a)
 
         foreach (var e in _mouseInterpreter.Process (a))
         {
+            Logging.Logger.LogTrace ($"Mouse Interpreter raising {e.Flags}");
             // Pass on
             MouseEvent?.Invoke (this, e);
         }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
index b7c1a997be..af3b976f16 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
@@ -85,12 +85,12 @@ private MouseEventArgs ToDriverMouse (MouseEventRecord e)
 
             Flags = e.ButtonState switch
                     {
-                        ButtonState.NoButtonPressed => MouseFlags.None,
-                        ButtonState.Button1Pressed => MouseFlags.Button1Pressed,
-                        ButtonState.Button2Pressed => MouseFlags.Button2Pressed,
-                        ButtonState.Button3Pressed => MouseFlags.Button3Pressed,
-                        ButtonState.Button4Pressed => MouseFlags.Button4Pressed,
-                        ButtonState.RightmostButtonPressed => MouseFlags.Button3Pressed,
+                        ButtonState.NoButtonPressed => MouseFlags.ReportMousePosition,
+                        ButtonState.Button1Pressed => MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition,
+                        ButtonState.Button2Pressed => MouseFlags.Button2Pressed | MouseFlags.ReportMousePosition,
+                        ButtonState.Button3Pressed => MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition,
+                        ButtonState.Button4Pressed => MouseFlags.Button4Pressed | MouseFlags.ReportMousePosition,
+                        ButtonState.RightmostButtonPressed => MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition,
                         _=> MouseFlags.None
                     }
         };

From 181b0f75778f4137e65a569eeb9fd327bb1ff69e Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 9 Feb 2025 12:14:28 +0000
Subject: [PATCH 156/198] More tests for ApplicationV2

---
 .../ConsoleDrivers/V2/ApplicationV2Tests.cs   | 65 +++++++++++++++++++
 1 file changed, 65 insertions(+)

diff --git a/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs b/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
index 31ab7603af..1a696641b3 100644
--- a/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
+++ b/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
@@ -1,4 +1,5 @@
 using System.Collections.Concurrent;
+using Microsoft.Extensions.Logging;
 using Moq;
 
 namespace UnitTests.ConsoleDrivers.V2;
@@ -196,6 +197,40 @@ public void Test_InitRunShutdown ()
     }
 
 
+    [Fact]
+    public void Test_InitRunShutdown_Generic ()
+    {
+        var orig = ApplicationImpl.Instance;
+
+        var v2 = NewApplicationV2 ();
+        ApplicationImpl.ChangeInstance (v2);
+
+        v2.Init ();
+
+        v2.AddTimeout (TimeSpan.FromMilliseconds (150),
+                       () =>
+                       {
+                           if (Application.Top != null)
+                           {
+                               Application.RequestStop ();
+                               return true;
+                           }
+
+                           return true;
+                       }
+                      );
+        Assert.Null (Application.Top);
+
+        // Blocks until the timeout call is hit
+
+        v2.Run<Window> ();
+
+        Assert.Null (Application.Top);
+        v2.Shutdown ();
+
+        ApplicationImpl.ChangeInstance (orig);
+    }
+
     [Fact]
     public void TestRepeatedShutdownCalls_DoNotDuplicateDisposeOutput ()
     {
@@ -217,5 +252,35 @@ public void TestRepeatedShutdownCalls_DoNotDuplicateDisposeOutput ()
         v2.Shutdown ();
         outputMock.Verify(o=>o.Dispose (),Times.Once);
     }
+    [Fact]
+    public void TestRepeatedInitCalls_WarnsAndIgnores ()
+    {
+        var v2 = NewApplicationV2 ();
+
+        Assert.Null (Application.Driver);
+        v2.Init ();
+        Assert.NotNull (Application.Driver);
+
+        var mockLogger = new Mock<ILogger> ();
+
+        var beforeLogger = Logging.Logger;
+        Logging.Logger = mockLogger.Object;
+
+        v2.Init ();
+        v2.Init ();
+
+        mockLogger.Verify(
+                          l=>l.Log (LogLevel.Error,
+                                    It.IsAny<EventId> (),
+                                    It.Is<It.IsAnyType> ((v, t) => v.ToString () == "Init called multiple times without shutdown, ignoring."),
+                                    It.IsAny<Exception> (),
+                                    It.IsAny<Func<It.IsAnyType, Exception, string>> ())
+                          ,Times.Exactly (2));
+
+        v2.Shutdown ();
+
+        // Restore the original null logger to be polite to other tests
+        Logging.Logger = beforeLogger;
+    }
 
 }

From e7fb30aee6d878d6d14a445ba5353319fd86d70d Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 9 Feb 2025 12:26:56 +0000
Subject: [PATCH 157/198] Tests for generic Run in Application2 and add/remove
 idle

---
 .../ConsoleDrivers/V2/ApplicationV2.cs        | 10 +++++++
 .../ConsoleDrivers/V2/ApplicationV2Tests.cs   | 29 ++++++++++---------
 2 files changed, 25 insertions(+), 14 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index 76c30f0a0d..73f9087f39 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -221,6 +221,16 @@ public override void Invoke (Action action)
     /// <inheritdoc/>
     public override void AddIdle (Func<bool> func) { _timedEvents.AddIdle (func); }
 
+    /// <summary>
+    /// Removes an idle function added by <see cref="AddIdle"/>
+    /// </summary>
+    /// <param name="fnTrue">Function to remove</param>
+    /// <returns>True if it was found and removed</returns>
+    public bool RemoveIdle (Func<bool> fnTrue)
+    {
+        return _timedEvents.RemoveIdle (fnTrue);
+    }
+
     /// <inheritdoc/>
     public override object AddTimeout (TimeSpan time, Func<bool> callback) { return _timedEvents.AddTimeout (time, callback); }
 
diff --git a/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs b/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
index 1a696641b3..1afd367847 100644
--- a/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
+++ b/UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
@@ -172,7 +172,7 @@ public void Test_InitRunShutdown ()
 
         v2.Init ();
 
-        v2.AddTimeout (TimeSpan.FromMilliseconds (150),
+        var timeoutToken = v2.AddTimeout (TimeSpan.FromMilliseconds (150),
                        () =>
                        {
                            if (Application.Top != null)
@@ -190,6 +190,8 @@ public void Test_InitRunShutdown ()
 
         v2.Run (new Window ());
 
+        Assert.True(v2.RemoveTimeout (timeoutToken));
+
         Assert.Null (Application.Top);
         v2.Shutdown ();
 
@@ -198,7 +200,7 @@ public void Test_InitRunShutdown ()
 
 
     [Fact]
-    public void Test_InitRunShutdown_Generic ()
+    public void Test_InitRunShutdown_Generic_IdleForExit ()
     {
         var orig = ApplicationImpl.Instance;
 
@@ -207,18 +209,7 @@ public void Test_InitRunShutdown_Generic ()
 
         v2.Init ();
 
-        v2.AddTimeout (TimeSpan.FromMilliseconds (150),
-                       () =>
-                       {
-                           if (Application.Top != null)
-                           {
-                               Application.RequestStop ();
-                               return true;
-                           }
-
-                           return true;
-                       }
-                      );
+        v2.AddIdle (IdleExit);
         Assert.Null (Application.Top);
 
         // Blocks until the timeout call is hit
@@ -230,6 +221,16 @@ public void Test_InitRunShutdown_Generic ()
 
         ApplicationImpl.ChangeInstance (orig);
     }
+    private bool IdleExit ()
+    {
+        if (Application.Top != null)
+        {
+            Application.RequestStop ();
+            return true;
+        }
+
+        return true;
+    }
 
     [Fact]
     public void TestRepeatedShutdownCalls_DoNotDuplicateDisposeOutput ()

From 28a71108ce31bd6a8627b2081f4399e03763bdf4 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 9 Feb 2025 13:33:30 +0000
Subject: [PATCH 158/198] Tests for MainLoop<T> access properties before init

---
 UnitTests/ConsoleDrivers/V2/MainLoopTTests.cs | 33 +++++++++++++++++++
 1 file changed, 33 insertions(+)
 create mode 100644 UnitTests/ConsoleDrivers/V2/MainLoopTTests.cs

diff --git a/UnitTests/ConsoleDrivers/V2/MainLoopTTests.cs b/UnitTests/ConsoleDrivers/V2/MainLoopTTests.cs
new file mode 100644
index 0000000000..727c105536
--- /dev/null
+++ b/UnitTests/ConsoleDrivers/V2/MainLoopTTests.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using Moq;
+
+namespace UnitTests.ConsoleDrivers.V2;
+public class MainLoopTTests
+{
+    [Fact]
+    public void MainLoopT_NotInitialized_Throws()
+    {
+        var m = new MainLoop<int> ();
+
+        Assert.Throws<NotInitializedException> (() => m.TimedEvents);
+        Assert.Throws<NotInitializedException> (() => m.InputBuffer);
+        Assert.Throws<NotInitializedException> (() => m.InputProcessor);
+        Assert.Throws<NotInitializedException> (() => m.Out);
+        Assert.Throws<NotInitializedException> (() => m.AnsiRequestScheduler);
+        Assert.Throws<NotInitializedException> (() => m.WindowSizeMonitor);
+
+        m.Initialize (new TimedEvents (),
+                      new ConcurrentQueue<int> (),
+                      Mock.Of <IInputProcessor>(),
+                      Mock.Of<IConsoleOutput>());
+
+        Assert.NotNull (m.TimedEvents);
+        Assert.NotNull (m.InputBuffer);
+        Assert.NotNull (m.InputProcessor);
+        Assert.NotNull (m.Out);
+        Assert.NotNull (m.AnsiRequestScheduler);
+        Assert.NotNull (m.WindowSizeMonitor);
+    }
+}

From 8b02f2dea362cbd50aedceb0d165bd962e3aa47a Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 9 Feb 2025 14:33:51 +0000
Subject: [PATCH 159/198] Add main loop coordinator test for input crash on
 boot

---
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  |  9 ++
 .../V2/MainLoopCoordinatorTests.cs            | 91 +++++++++++++++++++
 2 files changed, 100 insertions(+)
 create mode 100644 UnitTests/ConsoleDrivers/V2/MainLoopCoordinatorTests.cs

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index c9997d1e0c..881a3b89b5 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -122,6 +122,15 @@ private void RunInput ()
 
             throw;
         }
+
+        if (_stopCalled)
+        {
+            Logging.Logger.LogInformation ("Input loop exited cleanly");
+        }
+        else
+        {
+            Logging.Logger.LogCritical ("Input loop exited early (stop not called)");
+        }
     }
 
     /// <inheritdoc/>
diff --git a/UnitTests/ConsoleDrivers/V2/MainLoopCoordinatorTests.cs b/UnitTests/ConsoleDrivers/V2/MainLoopCoordinatorTests.cs
new file mode 100644
index 0000000000..d678c93e97
--- /dev/null
+++ b/UnitTests/ConsoleDrivers/V2/MainLoopCoordinatorTests.cs
@@ -0,0 +1,91 @@
+using System.Collections.Concurrent;
+using Microsoft.Extensions.Logging;
+using Moq;
+
+namespace UnitTests.ConsoleDrivers.V2;
+public class MainLoopCoordinatorTests
+{
+    [Fact]
+    public void TestMainLoopCoordinator_InputCrashes_ExceptionSurfacesMainThread ()
+    {
+
+        var mockLogger = new Mock<ILogger> ();
+
+        var beforeLogger = Logging.Logger;
+        Logging.Logger = mockLogger.Object;
+
+        var c = new MainLoopCoordinator<char> (new TimedEvents (),
+                                               // Runs on a separate thread (input thread)
+                                               () => throw new Exception ("Crash on boot"),
+
+                                               // Rest runs on main thread
+                                               new ConcurrentQueue<char> (),
+                                               Mock.Of <IInputProcessor>(),
+                                               ()=>Mock.Of<IConsoleOutput>(),
+                                               Mock.Of<IMainLoop<char>>());
+
+        // StartAsync boots the main loop and the input thread. But if the input class bombs
+        // on startup it is important that the exception surface at the call site and not lost
+        var ex = Assert.ThrowsAsync<AggregateException>(c.StartAsync).Result;
+        Assert.Equal ("Crash on boot", ex.InnerExceptions [0].Message);
+
+
+        // Restore the original null logger to be polite to other tests
+        Logging.Logger = beforeLogger;
+
+
+        // Logs should explicitly call out that input loop crashed.
+        mockLogger.Verify (
+                           l => l.Log (LogLevel.Critical,
+                                       It.IsAny<EventId> (),
+                                       It.Is<It.IsAnyType> ((v, t) => v.ToString () == "Input loop crashed"),
+                                       It.IsAny<Exception> (),
+                                       It.IsAny<Func<It.IsAnyType, Exception, string>> ())
+                         , Times.Once);
+    }
+    /*
+    [Fact]
+    public void TestMainLoopCoordinator_InputExitsImmediately_ExceptionRaisedInMainThread ()
+    {
+
+        // Runs on a separate thread (input thread)
+        // But because it's just a mock it immediately exists
+        var mockInputFactoryMethod = () => Mock.Of<IConsoleInput<char>> ();
+
+
+        var mockOutput = Mock.Of<IConsoleOutput> ();
+        var mockInputProcessor = Mock.Of<IInputProcessor> ();
+        var inputQueue = new ConcurrentQueue<char> ();
+        var timedEvents = new TimedEvents ();
+
+        var mainLoop = new MainLoop<char> ();
+        mainLoop.Initialize (timedEvents,
+                      inputQueue,
+                      mockInputProcessor,
+                      mockOutput
+                      );
+
+        var c = new MainLoopCoordinator<char> (timedEvents,
+                                               mockInputFactoryMethod,
+                                               inputQueue,
+                                               mockInputProcessor,
+                                               ()=>mockOutput,
+                                               mainLoop
+                                               );
+
+        // TODO: This test has race condition
+        //
+        // * When the input loop exits it can happen
+        // * - During boot
+        // * - After boot
+        // *
+        // * If it happens in boot you get input exited
+        // * If it happens after you get "Input loop exited early (stop not called)"
+        //
+
+        // Because the console input class does not block - i.e. breaks contract
+        // We need to let the user know input has silently exited and all has gone bad.
+        var ex = Assert.ThrowsAsync<Exception> (c.StartAsync).Result;
+        Assert.Equal ("Input loop exited during startup instead of entering read loop properly (i.e. and blocking)", ex.Message);
+    }*/
+}

From 407c549deee0198f779c8a1f35cc15766dad74e9 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 9 Feb 2025 14:57:01 +0000
Subject: [PATCH 160/198] Click tests for mouse interpreter buttons 2-4

---
 ...seButtonState.cs => MouseButtonStateEx.cs} |  0
 .../V2/MouseInterpreterTests.cs               | 76 ++++++++++++++++++-
 2 files changed, 75 insertions(+), 1 deletion(-)
 rename Terminal.Gui/ConsoleDrivers/V2/{MouseButtonState.cs => MouseButtonStateEx.cs} (100%)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseButtonState.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseButtonStateEx.cs
similarity index 100%
rename from Terminal.Gui/ConsoleDrivers/V2/MouseButtonState.cs
rename to Terminal.Gui/ConsoleDrivers/V2/MouseButtonStateEx.cs
diff --git a/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs b/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
index 5a31a6f4df..50e2ac4c29 100644
--- a/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
+++ b/UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
@@ -30,7 +30,6 @@ public void TestMouseEventSequences_InterpretedOnlyAsFlag (List<MouseEventArgs>
         }
     }
 
-
     public static IEnumerable<object []> SequenceTests ()
     {
         yield return new object []
@@ -59,6 +58,81 @@ public static IEnumerable<object []> SequenceTests ()
             MouseFlags.Button1DoubleClicked
         };
 
+        yield return new object []
+        {
+            new List<MouseEventArgs>
+            {
+                new() { Flags = MouseFlags.Button1Pressed },
+                new(),
+                new() { Flags = MouseFlags.Button1Pressed },
+                new(),
+                new() { Flags = MouseFlags.Button1Pressed },
+                new()
+            },
+            null,
+            MouseFlags.Button1Clicked,
+            null,
+            MouseFlags.Button1DoubleClicked,
+            null,
+            MouseFlags.Button1TripleClicked
+        };
+
+        yield return new object []
+        {
+            new List<MouseEventArgs>
+            {
+                new() { Flags = MouseFlags.Button2Pressed },
+                new(),
+                new() { Flags = MouseFlags.Button2Pressed },
+                new(),
+                new() { Flags = MouseFlags.Button2Pressed },
+                new()
+            },
+            null,
+            MouseFlags.Button2Clicked,
+            null,
+            MouseFlags.Button2DoubleClicked,
+            null,
+            MouseFlags.Button2TripleClicked
+        };
+
+        yield return new object []
+        {
+            new List<MouseEventArgs>
+            {
+                new() { Flags = MouseFlags.Button3Pressed },
+                new(),
+                new() { Flags = MouseFlags.Button3Pressed },
+                new(),
+                new() { Flags = MouseFlags.Button3Pressed },
+                new()
+            },
+            null,
+            MouseFlags.Button3Clicked,
+            null,
+            MouseFlags.Button3DoubleClicked,
+            null,
+            MouseFlags.Button3TripleClicked
+        };
+
+        yield return new object []
+        {
+            new List<MouseEventArgs>
+            {
+                new() { Flags = MouseFlags.Button4Pressed },
+                new(),
+                new() { Flags = MouseFlags.Button4Pressed },
+                new(),
+                new() { Flags = MouseFlags.Button4Pressed },
+                new()
+            },
+            null,
+            MouseFlags.Button4Clicked,
+            null,
+            MouseFlags.Button4DoubleClicked,
+            null,
+            MouseFlags.Button4TripleClicked
+        };
 
         yield return new object []
         {

From cc23dd4a5ab9b7bb8cfac687efacb5539fe96099 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 9 Feb 2025 15:09:08 +0000
Subject: [PATCH 161/198] Add test for NetInputProcessor ProcessQueue

---
 .../V2/NetInputProcessorTests.cs              | 30 ++++++++++++++++++-
 1 file changed, 29 insertions(+), 1 deletion(-)

diff --git a/UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs b/UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs
index 6b32f13903..ec7a4fff60 100644
--- a/UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs
+++ b/UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs
@@ -1,4 +1,5 @@
-using System.Text;
+using System.Collections.Concurrent;
+using System.Text;
 
 namespace UnitTests.ConsoleDrivers.V2;
 public class NetInputProcessorTests
@@ -89,4 +90,31 @@ public void ConsoleKeyInfoToKey_ValidInput_AsKey (ConsoleKeyInfo input, Key expe
         // Assert
         Assert.Equal (expected, result);
     }
+
+    [Fact]
+    public void Test_ProcessQueue_CapitalHLowerE ()
+    {
+        var queue = new ConcurrentQueue<ConsoleKeyInfo> ();
+
+        queue.Enqueue (new ConsoleKeyInfo ('H', ConsoleKey.None, true, false, false));
+        queue.Enqueue (new ConsoleKeyInfo ('e', ConsoleKey.None, false, false, false));
+
+        var processor = new NetInputProcessor (queue);
+
+        List<Key> ups = new List<Key> ();
+        List<Key> downs = new List<Key> ();
+
+        processor.KeyUp += (s, e) => { ups.Add (e); };
+        processor.KeyDown += (s, e) => { downs.Add (e); };
+
+        Assert.Empty (ups);
+        Assert.Empty (downs);
+
+        processor.ProcessQueue ();
+
+        Assert.Equal (Key.H.WithShift, ups [0]);
+        Assert.Equal (Key.H.WithShift, downs [0]);
+        Assert.Equal (Key.E, ups [1]);
+        Assert.Equal (Key.E, downs [1]);
+    }
 }

From eff1fe12d7ce345a3ff646d3b7f3caac6efefffa Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 9 Feb 2025 16:45:08 +0000
Subject: [PATCH 162/198] Added WindowSizeMonitorTests

---
 .../V2/WindowSizeMonitorTests.cs              | 76 +++++++++++++++++++
 1 file changed, 76 insertions(+)
 create mode 100644 UnitTests/ConsoleDrivers/V2/WindowSizeMonitorTests.cs

diff --git a/UnitTests/ConsoleDrivers/V2/WindowSizeMonitorTests.cs b/UnitTests/ConsoleDrivers/V2/WindowSizeMonitorTests.cs
new file mode 100644
index 0000000000..145f497490
--- /dev/null
+++ b/UnitTests/ConsoleDrivers/V2/WindowSizeMonitorTests.cs
@@ -0,0 +1,76 @@
+using Moq;
+
+namespace UnitTests.ConsoleDrivers.V2;
+public class WindowSizeMonitorTests
+{
+    [Fact]
+    public void TestWindowSizeMonitor_RaisesEventWhenChanges ()
+    {
+        var consoleOutput = new Mock<IConsoleOutput> ();
+
+        var queue = new Queue<Size>(new []{
+            new Size (30, 20),
+            new Size (20, 20)
+
+        });
+
+        consoleOutput.Setup (m => m.GetWindowSize ())
+                     .Returns (queue.Dequeue);
+
+        var outputBuffer = Mock.Of<IOutputBuffer> ();
+
+        var monitor = new WindowSizeMonitor (consoleOutput.Object, outputBuffer);
+
+        var result = new List<SizeChangedEventArgs> ();
+        monitor.SizeChanging += (s, e) => { result.Add (e);};
+
+        Assert.Empty (result);
+        monitor.Poll ();
+
+        Assert.Single (result);
+        Assert.Equal (new Size (30,20),result [0].Size);
+
+        monitor.Poll ();
+
+        Assert.Equal (2,result.Count);
+        Assert.Equal (new Size (30, 20), result [0].Size);
+        Assert.Equal (new Size (20, 20), result [0].Size);
+    }
+
+    [Fact]
+    public void TestWindowSizeMonitor_DoesNotRaiseEventWhen_NoChanges ()
+    {
+        var consoleOutput = new Mock<IConsoleOutput> ();
+
+        var queue = new Queue<Size> (new []{
+            new Size (30, 20),
+            new Size (30, 20),
+        });
+
+        consoleOutput.Setup (m => m.GetWindowSize ())
+                     .Returns (queue.Dequeue);
+
+        var outputBuffer = Mock.Of<IOutputBuffer> ();
+
+        var monitor = new WindowSizeMonitor (consoleOutput.Object, outputBuffer);
+
+        var result = new List<SizeChangedEventArgs> ();
+        monitor.SizeChanging += (s, e) => { result.Add (e); };
+
+        // First poll always raises event because going from unknown size i.e. 0,0
+        Assert.Empty (result);
+        monitor.Poll ();
+
+        Assert.Single (result);
+        Assert.Equal (new Size (30, 20), result [0].Size);
+
+        // No change 
+        monitor.Poll ();
+
+        Assert.Single (result);
+        Assert.Equal (new Size (30, 20), result [0].Size);
+    }
+
+
+
+}

From 2a9da501ec4bd380b95a955830067509e88ec400 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 9 Feb 2025 16:46:09 +0000
Subject: [PATCH 163/198] Fix typo in test

---
 UnitTests/ConsoleDrivers/V2/WindowSizeMonitorTests.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/UnitTests/ConsoleDrivers/V2/WindowSizeMonitorTests.cs b/UnitTests/ConsoleDrivers/V2/WindowSizeMonitorTests.cs
index 145f497490..8b7c7a7b64 100644
--- a/UnitTests/ConsoleDrivers/V2/WindowSizeMonitorTests.cs
+++ b/UnitTests/ConsoleDrivers/V2/WindowSizeMonitorTests.cs
@@ -34,7 +34,7 @@ public void TestWindowSizeMonitor_RaisesEventWhenChanges ()
 
         Assert.Equal (2,result.Count);
         Assert.Equal (new Size (30, 20), result [0].Size);
-        Assert.Equal (new Size (20, 20), result [0].Size);
+        Assert.Equal (new Size (20, 20), result [1].Size);
     }
 
     [Fact]

From 2f0d3292c3ebafb7f5083640ef43b7662fffd760 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 9 Feb 2025 17:55:05 +0000
Subject: [PATCH 164/198] Add WindowsInputProcessorTests

---
 .../V2/WindowsInputProcessorTests.cs          | 58 +++++++++++++++++++
 1 file changed, 58 insertions(+)
 create mode 100644 UnitTests/ConsoleDrivers/V2/WindowsInputProcessorTests.cs

diff --git a/UnitTests/ConsoleDrivers/V2/WindowsInputProcessorTests.cs b/UnitTests/ConsoleDrivers/V2/WindowsInputProcessorTests.cs
new file mode 100644
index 0000000000..c058c4e878
--- /dev/null
+++ b/UnitTests/ConsoleDrivers/V2/WindowsInputProcessorTests.cs
@@ -0,0 +1,58 @@
+using System.Collections.Concurrent;
+using Terminal.Gui.ConsoleDrivers;
+using InputRecord = Terminal.Gui.WindowsConsole.InputRecord;
+
+namespace UnitTests.ConsoleDrivers.V2;
+public class WindowsInputProcessorTests
+{
+
+    [Fact]
+    public void Test_ProcessQueue_CapitalHLowerE ()
+    {
+        var queue = new ConcurrentQueue<InputRecord> ();
+
+        queue.Enqueue (new  InputRecord()
+        {
+            EventType = WindowsConsole.EventType.Key,
+            KeyEvent = new WindowsConsole.KeyEventRecord ()
+            {
+                bKeyDown = true,
+                UnicodeChar = 'H',
+                dwControlKeyState = WindowsConsole.ControlKeyState.CapslockOn,
+                wVirtualKeyCode = (ConsoleKeyMapping.VK)72,
+                wVirtualScanCode = 35
+            }
+        });
+        queue.Enqueue (new InputRecord ()
+        {
+            EventType = WindowsConsole.EventType.Key,
+            KeyEvent = new WindowsConsole.KeyEventRecord ()
+            {
+                bKeyDown = true,
+                UnicodeChar = 'i',
+                dwControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed,
+                wVirtualKeyCode = (ConsoleKeyMapping.VK)73,
+                wVirtualScanCode = 23
+            }
+        });
+
+        var processor = new WindowsInputProcessor (queue);
+
+        List<Key> ups = new List<Key> ();
+        List<Key> downs = new List<Key> ();
+
+        processor.KeyUp += (s, e) => { ups.Add (e); };
+        processor.KeyDown += (s, e) => { downs.Add (e); };
+
+        Assert.Empty (ups);
+        Assert.Empty (downs);
+
+        processor.ProcessQueue ();
+
+        Assert.Equal (Key.H.WithShift, ups [0]);
+        Assert.Equal (Key.H.WithShift, downs [0]);
+        Assert.Equal (Key.I, ups [1]);
+        Assert.Equal (Key.I, downs [1]);
+    }
+}
+

From 250cefe333ec43c7cd68cdabdeb2e06f9759fca7 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 9 Feb 2025 18:45:22 +0000
Subject: [PATCH 165/198] Add more WindowsInputProcessorTests

---
 .../V2/WindowsInputProcessorTests.cs          | 128 ++++++++++++++++++
 1 file changed, 128 insertions(+)

diff --git a/UnitTests/ConsoleDrivers/V2/WindowsInputProcessorTests.cs b/UnitTests/ConsoleDrivers/V2/WindowsInputProcessorTests.cs
index c058c4e878..a9be501289 100644
--- a/UnitTests/ConsoleDrivers/V2/WindowsInputProcessorTests.cs
+++ b/UnitTests/ConsoleDrivers/V2/WindowsInputProcessorTests.cs
@@ -24,6 +24,18 @@ public void Test_ProcessQueue_CapitalHLowerE ()
             }
         });
         queue.Enqueue (new InputRecord ()
+        {
+            EventType = WindowsConsole.EventType.Key,
+            KeyEvent = new WindowsConsole.KeyEventRecord ()
+            {
+                bKeyDown = false,
+                UnicodeChar = 'H',
+                dwControlKeyState = WindowsConsole.ControlKeyState.CapslockOn,
+                wVirtualKeyCode = (ConsoleKeyMapping.VK)72,
+                wVirtualScanCode = 35
+            }
+        });
+        queue.Enqueue (new InputRecord ()
         {
             EventType = WindowsConsole.EventType.Key,
             KeyEvent = new WindowsConsole.KeyEventRecord ()
@@ -35,6 +47,18 @@ public void Test_ProcessQueue_CapitalHLowerE ()
                 wVirtualScanCode = 23
             }
         });
+        queue.Enqueue (new InputRecord ()
+        {
+            EventType = WindowsConsole.EventType.Key,
+            KeyEvent = new WindowsConsole.KeyEventRecord ()
+            {
+                bKeyDown = false,
+                UnicodeChar = 'i',
+                dwControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed,
+                wVirtualKeyCode = (ConsoleKeyMapping.VK)73,
+                wVirtualScanCode = 23
+            }
+        });
 
         var processor = new WindowsInputProcessor (queue);
 
@@ -54,5 +78,109 @@ public void Test_ProcessQueue_CapitalHLowerE ()
         Assert.Equal (Key.I, ups [1]);
         Assert.Equal (Key.I, downs [1]);
     }
+
+
+    [Fact]
+    public void Test_ProcessQueue_Mouse_Move ()
+    {
+        var queue = new ConcurrentQueue<InputRecord> ();
+
+        queue.Enqueue (new InputRecord ()
+        {
+            EventType = WindowsConsole.EventType.Mouse,
+            MouseEvent = new WindowsConsole.MouseEventRecord
+            {
+                MousePosition = new WindowsConsole.Coord(32,31),
+                ButtonState = WindowsConsole.ButtonState.NoButtonPressed,
+                ControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed,
+                EventFlags = WindowsConsole.EventFlags.MouseMoved
+            }
+        });
+
+        var processor = new WindowsInputProcessor (queue);
+
+        List<MouseEventArgs> mouseEvents = new List<MouseEventArgs> ();
+
+        processor.MouseEvent += (s, e) => { mouseEvents.Add (e); };
+
+        Assert.Empty (mouseEvents);
+
+        processor.ProcessQueue ();
+
+        var s = Assert.Single (mouseEvents);
+        Assert.Equal (s.Flags,MouseFlags.ReportMousePosition);
+        Assert.Equal (s.ScreenPosition,new Point (32,31));
+    }
+
+    [Theory]
+    [InlineData(WindowsConsole.ButtonState.Button1Pressed,MouseFlags.Button1Pressed)]
+    [InlineData (WindowsConsole.ButtonState.Button2Pressed, MouseFlags.Button2Pressed)]
+    [InlineData (WindowsConsole.ButtonState.Button3Pressed, MouseFlags.Button3Pressed)]
+    [InlineData (WindowsConsole.ButtonState.Button4Pressed, MouseFlags.Button4Pressed)]
+    internal void Test_ProcessQueue_Mouse_Pressed (WindowsConsole.ButtonState state,MouseFlags expectedFlag )
+    {
+        var queue = new ConcurrentQueue<InputRecord> ();
+
+        queue.Enqueue (new InputRecord ()
+        {
+            EventType = WindowsConsole.EventType.Mouse,
+            MouseEvent = new WindowsConsole.MouseEventRecord
+            {
+                MousePosition = new WindowsConsole.Coord (32, 31),
+                ButtonState = state,
+                ControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed,
+                EventFlags = WindowsConsole.EventFlags.MouseMoved
+            }
+        });
+
+        var processor = new WindowsInputProcessor (queue);
+
+        List<MouseEventArgs> mouseEvents = new List<MouseEventArgs> ();
+
+        processor.MouseEvent += (s, e) => { mouseEvents.Add (e); };
+
+        Assert.Empty (mouseEvents);
+
+        processor.ProcessQueue ();
+
+        var s = Assert.Single (mouseEvents);
+        Assert.Equal (s.Flags, MouseFlags.ReportMousePosition | expectedFlag);
+        Assert.Equal (s.ScreenPosition, new Point (32, 31));
+    }
+
+
+    [Theory]
+    [InlineData (100, MouseFlags.WheeledUp)]
+    [InlineData ( -100, MouseFlags.WheeledDown)]
+    internal void Test_ProcessQueue_Mouse_Wheel (int wheelValue, MouseFlags expectedFlag)
+    {
+        var queue = new ConcurrentQueue<InputRecord> ();
+
+        queue.Enqueue (new InputRecord ()
+        {
+            EventType = WindowsConsole.EventType.Mouse,
+            MouseEvent = new WindowsConsole.MouseEventRecord
+            {
+                MousePosition = new WindowsConsole.Coord (32, 31),
+                ButtonState = (WindowsConsole.ButtonState)wheelValue,
+                ControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed,
+                EventFlags = WindowsConsole.EventFlags.MouseWheeled
+            }
+        });
+
+        var processor = new WindowsInputProcessor (queue);
+
+        List<MouseEventArgs> mouseEvents = new List<MouseEventArgs> ();
+
+        processor.MouseEvent += (s, e) => { mouseEvents.Add (e); };
+
+        Assert.Empty (mouseEvents);
+
+        processor.ProcessQueue ();
+
+        var s = Assert.Single (mouseEvents);
+        Assert.Equal (s.Flags,expectedFlag);
+        Assert.Equal (s.ScreenPosition, new Point (32, 31));
+    }
 }
 

From cc96920cc308df12101015d7bce716e986a5dbdf Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Wed, 12 Feb 2025 19:21:06 +0000
Subject: [PATCH 166/198] Fix for never raising Released

---
 .../V2/WindowsInputProcessor.cs               | 49 ++++++++++++-------
 1 file changed, 32 insertions(+), 17 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
index af3b976f16..8a88dfe819 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
@@ -76,43 +76,58 @@ protected override void ProcessAfterParsing (InputRecord input)
             OnKeyUp (key!);
         }
     }
-
+    bool[] _lastWasPressed = new bool[4];
     private MouseEventArgs ToDriverMouse (MouseEventRecord e)
     {
-        var result = new MouseEventArgs
-        {
-            Position = new (e.MousePosition.X, e.MousePosition.Y),
+        MouseFlags mouseFlags = MouseFlags.ReportMousePosition;
 
-            Flags = e.ButtonState switch
-                    {
-                        ButtonState.NoButtonPressed => MouseFlags.ReportMousePosition,
-                        ButtonState.Button1Pressed => MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition,
-                        ButtonState.Button2Pressed => MouseFlags.Button2Pressed | MouseFlags.ReportMousePosition,
-                        ButtonState.Button3Pressed => MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition,
-                        ButtonState.Button4Pressed => MouseFlags.Button4Pressed | MouseFlags.ReportMousePosition,
-                        ButtonState.RightmostButtonPressed => MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition,
-                        _=> MouseFlags.None
-                    }
-        };
+        mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, ButtonState.Button1Pressed,MouseFlags.Button1Pressed, MouseFlags.Button1Released, 0);
+        mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, ButtonState.Button2Pressed, MouseFlags.Button2Pressed, MouseFlags.Button2Released, 1);
+        mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, ButtonState.Button3Pressed, MouseFlags.Button3Pressed, MouseFlags.Button3Released, 2);
+        mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, ButtonState.Button4Pressed, MouseFlags.Button4Pressed, MouseFlags.Button4Released, 3);
 
         if (e.EventFlags == WindowsConsole.EventFlags.MouseWheeled)
         {
             switch ((int)e.ButtonState)
             {
                 case int v when v > 0:
-                    result.Flags = MouseFlags.WheeledUp;
+                    mouseFlags = MouseFlags.WheeledUp;
 
                     break;
 
                 case int v when v < 0:
-                    result.Flags = MouseFlags.WheeledDown;
+                    mouseFlags = MouseFlags.WheeledDown;
 
                     break;
             }
         }
 
+        var result = new MouseEventArgs
+        {
+            Position = new (e.MousePosition.X, e.MousePosition.Y),
+            Flags = mouseFlags
+        };
+
         // TODO: Return keys too
 
         return result;
     }
+    private MouseFlags UpdateMouseFlags (MouseFlags current, ButtonState newState,ButtonState pressedState, MouseFlags pressedFlag, MouseFlags releasedFlag, int buttonIndex)
+    {
+        if (newState.HasFlag (pressedState))
+        {
+            current |= pressedFlag;
+            _lastWasPressed [buttonIndex] = true;
+        }
+        else
+        {
+            if (_lastWasPressed [buttonIndex])
+            {
+                current |= releasedFlag;
+                _lastWasPressed [buttonIndex] = false;
+            }
+        }
+        return current;
+    }
+
 }
\ No newline at end of file

From c80c7965ed2c62292b8066f008e88c8b092b6d8a Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Wed, 12 Feb 2025 19:30:21 +0000
Subject: [PATCH 167/198] tidy

---
 Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
index 8a88dfe819..7c13ee0ed6 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
@@ -11,6 +11,8 @@ namespace Terminal.Gui;
 /// </summary>
 internal class WindowsInputProcessor : InputProcessor<InputRecord>
 {
+    private readonly bool[] _lastWasPressed = new bool[4];
+
     /// <inheritdoc/>
     public WindowsInputProcessor (ConcurrentQueue<InputRecord> inputBuffer) : base (inputBuffer, new WindowsKeyConverter()) { }
 
@@ -76,7 +78,6 @@ protected override void ProcessAfterParsing (InputRecord input)
             OnKeyUp (key!);
         }
     }
-    bool[] _lastWasPressed = new bool[4];
     private MouseEventArgs ToDriverMouse (MouseEventRecord e)
     {
         MouseFlags mouseFlags = MouseFlags.ReportMousePosition;
@@ -90,12 +91,12 @@ private MouseEventArgs ToDriverMouse (MouseEventRecord e)
         {
             switch ((int)e.ButtonState)
             {
-                case int v when v > 0:
+                case > 0:
                     mouseFlags = MouseFlags.WheeledUp;
 
                     break;
 
-                case int v when v < 0:
+                case < 0:
                     mouseFlags = MouseFlags.WheeledDown;
 
                     break;

From e24ba697ba7bea6800d7c5cb2d81dbfacbfb2bc4 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 13 Feb 2025 03:49:40 +0000
Subject: [PATCH 168/198] Fix not processing windows rightmost button after
 last commit

---
 .../ConsoleDrivers/V2/WindowsInputProcessor.cs   | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
index 7c13ee0ed6..ce496aa14a 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
@@ -84,9 +84,23 @@ private MouseEventArgs ToDriverMouse (MouseEventRecord e)
 
         mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, ButtonState.Button1Pressed,MouseFlags.Button1Pressed, MouseFlags.Button1Released, 0);
         mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, ButtonState.Button2Pressed, MouseFlags.Button2Pressed, MouseFlags.Button2Released, 1);
-        mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, ButtonState.Button3Pressed, MouseFlags.Button3Pressed, MouseFlags.Button3Released, 2);
         mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, ButtonState.Button4Pressed, MouseFlags.Button4Pressed, MouseFlags.Button4Released, 3);
 
+        // Deal with button 3 separately because it is considered same as 'rightmost button'
+        if (e.ButtonState.HasFlag (ButtonState.Button3Pressed) || e.ButtonState.HasFlag (ButtonState.RightmostButtonPressed))
+        {
+            mouseFlags |= MouseFlags.Button3Pressed;
+            _lastWasPressed [2] = true;
+        }
+        else
+        {
+            if (_lastWasPressed [2])
+            {
+                mouseFlags |= MouseFlags.Button3Released;
+                _lastWasPressed [2] = false;
+            }
+        }
+
         if (e.EventFlags == WindowsConsole.EventFlags.MouseWheeled)
         {
             switch ((int)e.ButtonState)

From 299855b0e21537b64943d2e4d95712bb949f534b Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 13 Feb 2025 04:02:48 +0000
Subject: [PATCH 169/198] Add tests for mouse flag processing in
 WindowsInputProcessor

---
 .../V2/WindowsInputProcessor.cs               |  3 +-
 .../V2/WindowsInputProcessorTests.cs          | 69 +++++++++++++++++++
 2 files changed, 71 insertions(+), 1 deletion(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
index ce496aa14a..ca516d93d1 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
@@ -78,7 +78,8 @@ protected override void ProcessAfterParsing (InputRecord input)
             OnKeyUp (key!);
         }
     }
-    private MouseEventArgs ToDriverMouse (MouseEventRecord e)
+
+    public MouseEventArgs ToDriverMouse (MouseEventRecord e)
     {
         MouseFlags mouseFlags = MouseFlags.ReportMousePosition;
 
diff --git a/UnitTests/ConsoleDrivers/V2/WindowsInputProcessorTests.cs b/UnitTests/ConsoleDrivers/V2/WindowsInputProcessorTests.cs
index a9be501289..4c3a2c4963 100644
--- a/UnitTests/ConsoleDrivers/V2/WindowsInputProcessorTests.cs
+++ b/UnitTests/ConsoleDrivers/V2/WindowsInputProcessorTests.cs
@@ -1,6 +1,8 @@
 using System.Collections.Concurrent;
 using Terminal.Gui.ConsoleDrivers;
 using InputRecord = Terminal.Gui.WindowsConsole.InputRecord;
+using ButtonState = Terminal.Gui.WindowsConsole.ButtonState;
+using MouseEventRecord = Terminal.Gui.WindowsConsole.MouseEventRecord;
 
 namespace UnitTests.ConsoleDrivers.V2;
 public class WindowsInputProcessorTests
@@ -182,5 +184,72 @@ internal void Test_ProcessQueue_Mouse_Wheel (int wheelValue, MouseFlags expected
         Assert.Equal (s.Flags,expectedFlag);
         Assert.Equal (s.ScreenPosition, new Point (32, 31));
     }
+
+    public static IEnumerable<object []> MouseFlagTestData ()
+    {
+        yield return new object []
+        {
+            new Tuple<ButtonState, MouseFlags>[]
+            {
+                Tuple.Create(ButtonState.Button1Pressed, MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition),
+                Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button1Released | MouseFlags.ReportMousePosition),
+                Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
+            }
+        };
+
+        yield return new object []
+        {
+            new Tuple<ButtonState, MouseFlags>[]
+            {
+                Tuple.Create ( ButtonState.Button2Pressed, MouseFlags.Button2Pressed  | MouseFlags.ReportMousePosition),
+                Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button2Released | MouseFlags.ReportMousePosition),
+                Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
+            }
+        }; 
+        yield return new object []
+        {
+            new Tuple<ButtonState, MouseFlags>[]
+            {
+                Tuple.Create(ButtonState.Button3Pressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
+                Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button3Released | MouseFlags.ReportMousePosition),
+                Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
+            }
+        };
+
+        yield return new object []
+        {
+            new Tuple<ButtonState, MouseFlags>[]
+            {
+                Tuple.Create(ButtonState.Button4Pressed, MouseFlags.Button4Pressed | MouseFlags.ReportMousePosition),
+                Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button4Released | MouseFlags.ReportMousePosition),
+                Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
+            }
+        };
+
+        yield return new object []
+        {
+            new Tuple<ButtonState, MouseFlags>[]
+            {
+                Tuple.Create(ButtonState.RightmostButtonPressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
+                Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button3Released | MouseFlags.ReportMousePosition),
+                Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
+            }
+        };
+    }
+
+    [Theory]
+    [MemberData (nameof (MouseFlagTestData))]
+    internal void MouseFlags_Should_Map_Correctly (Tuple<ButtonState, MouseFlags>[] inputOutputPairs)
+    {
+        var processor = new WindowsInputProcessor (new ());
+
+        foreach (var pair in inputOutputPairs)
+        {
+            var mockEvent = new MouseEventRecord { ButtonState = pair.Item1 };
+            var result = processor.ToDriverMouse (mockEvent);
+
+            Assert.Equal (pair.Item2, result.Flags);
+        }
+    }
 }
 

From 0bc58a12120a567e95a323d84b8ad505bf4793df Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 13 Feb 2025 04:10:35 +0000
Subject: [PATCH 170/198] Add more tests for corner cases

---
 .../V2/WindowsInputProcessorTests.cs          | 50 +++++++++++++++++++
 1 file changed, 50 insertions(+)

diff --git a/UnitTests/ConsoleDrivers/V2/WindowsInputProcessorTests.cs b/UnitTests/ConsoleDrivers/V2/WindowsInputProcessorTests.cs
index 4c3a2c4963..7c9fc14429 100644
--- a/UnitTests/ConsoleDrivers/V2/WindowsInputProcessorTests.cs
+++ b/UnitTests/ConsoleDrivers/V2/WindowsInputProcessorTests.cs
@@ -235,6 +235,56 @@ public static IEnumerable<object []> MouseFlagTestData ()
                 Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
             }
         };
+
+        // Tests for holding down 2 buttons at once and releasing them one after the other
+        yield return new object []
+        {
+            new Tuple<ButtonState, MouseFlags>[]
+            {
+                Tuple.Create(ButtonState.Button1Pressed | ButtonState.Button2Pressed, MouseFlags.Button1Pressed | MouseFlags.Button2Pressed | MouseFlags.ReportMousePosition),
+                Tuple.Create(ButtonState.Button1Pressed, MouseFlags.Button1Pressed | MouseFlags.Button2Released | MouseFlags.ReportMousePosition),
+                Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button1Released | MouseFlags.ReportMousePosition),
+                Tuple.Create(ButtonState.NoButtonPressed,  MouseFlags.ReportMousePosition)
+            }
+        };
+
+        yield return new object []
+        {
+            new Tuple<ButtonState, MouseFlags>[]
+            {
+                Tuple.Create(ButtonState.Button3Pressed | ButtonState.Button4Pressed, MouseFlags.Button3Pressed | MouseFlags.Button4Pressed | MouseFlags.ReportMousePosition),
+                Tuple.Create(ButtonState.Button3Pressed, MouseFlags.Button3Pressed | MouseFlags.Button4Released | MouseFlags.ReportMousePosition),
+                Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button3Released | MouseFlags.ReportMousePosition),
+                Tuple.Create(ButtonState.NoButtonPressed,  MouseFlags.ReportMousePosition)
+            }
+        };
+
+        // Test for holding down 2 buttons at once and releasing them simultaneously
+        yield return new object []
+        {
+            new Tuple<ButtonState, MouseFlags>[]
+            {
+                Tuple.Create(ButtonState.Button1Pressed | ButtonState.Button2Pressed, MouseFlags.Button1Pressed | MouseFlags.Button2Pressed | MouseFlags.ReportMousePosition),
+                Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button1Released | MouseFlags.Button2Released | MouseFlags.ReportMousePosition),
+                Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition)
+            }
+        };
+
+        // Test that rightmost and button 3 are the same button so 2 states is still only 1 flag
+        yield return new object []
+        {
+            new Tuple<ButtonState, MouseFlags>[]
+            {
+                Tuple.Create(ButtonState.Button3Pressed | ButtonState.RightmostButtonPressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
+                // Can swap between without raising the released
+                Tuple.Create(ButtonState.Button3Pressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
+                Tuple.Create(ButtonState.RightmostButtonPressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
+
+                // Now with neither we get released
+                Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button3Released | MouseFlags.ReportMousePosition),
+                Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition)
+            }
+        };
     }
 
     [Theory]

From 6b9367f0a4e6626726a6af1c89f63bca541acdca Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 13 Feb 2025 20:32:49 +0000
Subject: [PATCH 171/198] Fix for Ready event not being raised on top levels
 changing

---
 .../V2/IToplevelTransitionManager.cs          | 21 +++++++++++
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    | 28 ++++++---------
 .../V2/ToplevelTransitionManager.cs           | 36 +++++++++++++++++++
 3 files changed, 68 insertions(+), 17 deletions(-)
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/IToplevelTransitionManager.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/ToplevelTransitionManager.cs

diff --git a/Terminal.Gui/ConsoleDrivers/V2/IToplevelTransitionManager.cs b/Terminal.Gui/ConsoleDrivers/V2/IToplevelTransitionManager.cs
new file mode 100644
index 0000000000..a3d93d49b9
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/IToplevelTransitionManager.cs
@@ -0,0 +1,21 @@
+#nullable enable
+namespace Terminal.Gui;
+
+/// <summary>
+/// Interface for class that handles bespoke behaviours that occur when application
+/// top level changes.
+/// </summary>
+public interface IToplevelTransitionManager
+{
+    /// <summary>
+    /// Raises the <see cref="Toplevel.Ready"/> event on the current top level
+    /// if it has not been raised before now.
+    /// </summary>
+    void RaiseReadyEventIfNeeded ();
+
+    /// <summary>
+    /// Handles any state change needed when the application top changes e.g.
+    /// setting redraw flags
+    /// </summary>
+    void HandleTopMaybeChanging ();
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index 0aaeca2409..f2a5f65c7c 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -66,6 +66,11 @@ public IWindowSizeMonitor WindowSizeMonitor
         private set => _windowSizeMonitor = value;
     }
 
+    /// <summary>
+    /// Handles raising events and setting required draw status etc when <see cref="Application.Top"/> changes
+    /// </summary>
+    public IToplevelTransitionManager ToplevelTransitionManager = new ToplevelTransitionManager ();
+
     /// <summary>
     ///     Determines how to get the current system type, adjust
     ///     in unit tests to simulate specific timings.
@@ -113,7 +118,8 @@ internal void IterationImpl ()
     {
         InputProcessor.ProcessQueue ();
 
-        HackLayoutDrawIfTopChanged ();
+        ToplevelTransitionManager.RaiseReadyEventIfNeeded ();
+        ToplevelTransitionManager.HandleTopMaybeChanging ();
 
         if (Application.Top != null)
         {
@@ -144,20 +150,7 @@ internal void IterationImpl ()
         Logging.IterationInvokesAndTimeouts.Record (swCallbacks.Elapsed.Milliseconds);
     }
 
-    private View? _lastTop;
-    private void HackLayoutDrawIfTopChanged ()
-    {
-        // TODO: This fixes closing a modal not making its host (below) refresh
-        //       until you click.  This should not be the job of the main loop!
-        var newTop = Application.Top;
-        if (_lastTop != null && _lastTop != newTop && newTop != null)
-        {
-            newTop.SetNeedsDraw();
-        }
-        
-        _lastTop = Application.Top;
-    }
-
+    
     private void SetCursor ()
     {
         View? mostFocused = Application.Top.MostFocused;
@@ -204,6 +197,7 @@ private bool AnySubviewsNeedDrawn (View v)
 
     /// <inheritdoc/>
     public void Dispose ()
-    { // TODO release managed resources here
+    {
+        // TODO release managed resources here
     }
-}
+}
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ToplevelTransitionManager.cs b/Terminal.Gui/ConsoleDrivers/V2/ToplevelTransitionManager.cs
new file mode 100644
index 0000000000..c6fd9c7bc3
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/ToplevelTransitionManager.cs
@@ -0,0 +1,36 @@
+#nullable enable
+namespace Terminal.Gui;
+
+
+/// <summary>
+/// Handles bespoke behaviours that occur when application top level changes.
+/// </summary>
+public class ToplevelTransitionManager : IToplevelTransitionManager
+{
+    private readonly HashSet<Toplevel> _readiedTopLevels = new HashSet<Toplevel> ();
+
+    private View? _lastTop;
+
+    /// <inheritdoc />
+    public void RaiseReadyEventIfNeeded ()
+    {
+        var top = Application.Top;
+        if (top != null && !_readiedTopLevels.Contains (top))
+        {
+            top.OnReady ();
+            _readiedTopLevels.Add (top);
+        }
+    }
+
+    /// <inheritdoc />
+    public void HandleTopMaybeChanging ()
+    {
+        var newTop = Application.Top;
+        if (_lastTop != null && _lastTop != newTop && newTop != null)
+        {
+            newTop.SetNeedsDraw ();
+        }
+
+        _lastTop = Application.Top;
+    }
+}

From 193f2a75796b4da415a01532337cf92cb2807b38 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Fri, 14 Feb 2025 19:11:16 +0000
Subject: [PATCH 172/198] update class diagram

---
 Terminal.Gui/ConsoleDrivers/V2/V2.cd | 51 +++++++++++++++++++++-------
 1 file changed, 39 insertions(+), 12 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index 53ffa8fa60..7443e97449 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -53,7 +53,7 @@
   </Class>
   <Class Name="Terminal.Gui.MainLoop&lt;T&gt;" Collapsed="true" BaseTypeListCollapsed="true">
     <Position X="11" Y="4.75" Width="1.5" />
-    <AssociationLine Name="TimedEvents" Type="Terminal.Gui.ITimedEvents" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
+    <AssociationLine Name="TimedEvents" Type="Terminal.Gui.ITimedEvents" ManuallyRouted="true">
       <Path>
         <Point X="11.312" Y="5.312" />
         <Point X="11.312" Y="6.292" />
@@ -64,18 +64,16 @@
         <Position X="-1.015" Y="1.019" />
       </MemberNameLabel>
     </AssociationLine>
-    <AssociationLine Name="OutputBuffer" Type="Terminal.Gui.IOutputBuffer" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
+    <AssociationLine Name="OutputBuffer" Type="Terminal.Gui.IOutputBuffer" ManuallyRouted="true">
       <Path>
-        <Point X="11.875" Y="5.312" />
-        <Point X="11.875" Y="5.687" />
-        <Point X="11.812" Y="5.687" />
-        <Point X="11.812" Y="7.25" />
+        <Point X="11.718" Y="5.312" />
+        <Point X="11.718" Y="7.25" />
       </Path>
       <MemberNameLabel ManuallyPlaced="true">
         <Position X="0.027" Y="0.102" />
       </MemberNameLabel>
     </AssociationLine>
-    <AssociationLine Name="Out" Type="Terminal.Gui.IConsoleOutput" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
+    <AssociationLine Name="Out" Type="Terminal.Gui.IConsoleOutput" ManuallyRouted="true">
       <Path>
         <Point X="12.5" Y="5.125" />
         <Point X="12.5" Y="5.792" />
@@ -107,11 +105,25 @@
         <Position X="0.047" Y="-0.336" />
       </MemberNameLabel>
     </AssociationLine>
+    <AssociationLine Name="ToplevelTransitionManager" Type="Terminal.Gui.IToplevelTransitionManager" ManuallyRouted="true">
+      <Path>
+        <Point X="11" Y="5.031" />
+        <Point X="11" Y="5.406" />
+        <Point X="9.021" Y="5.406" />
+        <Point X="9.021" Y="11.5" />
+        <Point X="10.375" Y="11.5" />
+        <Point X="10.375" Y="12" />
+      </Path>
+      <MemberNameLabel ManuallyPlaced="true">
+        <Position X="-0.671" Y="0.529" />
+      </MemberNameLabel>
+    </AssociationLine>
     <TypeIdentifier>
-      <HashCode>QQQAAAAQACABJQQAABAAAAAAACIAAAACIAEAAACAEgg=</HashCode>
+      <HashCode>QQQAAAAQACABJQQAABAAAQAAACAAAAACAAEAAACAEgg=</HashCode>
       <FileName>ConsoleDrivers\V2\MainLoop.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
+      <Field Name="ToplevelTransitionManager" />
       <Property Name="TimedEvents" />
       <Property Name="InputProcessor" />
       <Property Name="OutputBuffer" />
@@ -199,7 +211,7 @@
   <Class Name="Terminal.Gui.WindowsInputProcessor" Collapsed="true">
     <Position X="15.75" Y="5.75" Width="2" />
     <TypeIdentifier>
-      <HashCode>AQAAAAAAAAAACAAAAgAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <HashCode>AQAAAAAAAAAACAAAAgAAAAAAAgAEAAAAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Class>
@@ -240,8 +252,8 @@
     </TypeIdentifier>
     <ShowAsAssociation>
       <Field Name="_mouseParser" />
-      <Field Name="_heldContent" />
       <Field Name="_keyboardParser" />
+      <Field Name="_heldContent" />
     </ShowAsAssociation>
     <Lollipop Position="0.2" />
   </Class>
@@ -259,7 +271,7 @@
     <Position X="16.5" Y="10.25" Width="2" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAMwAIAAAAAAAAAAAABCAAAAAAAAABAAEAAg=</HashCode>
-      <FileName>ConsoleDrivers\V2\MouseButtonState.cs</FileName>
+      <FileName>ConsoleDrivers\V2\MouseButtonStateEx.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.StringHeld" Collapsed="true">
@@ -320,7 +332,7 @@
   <Class Name="Terminal.Gui.ApplicationV2" Collapsed="true">
     <Position X="4.75" Y="4.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>QAAAAAgABAEIBgAQAAAAAQAAAAAAgAEAAAAKgIAAAgI=</HashCode>
+      <HashCode>QAAAAAgABAEIBgAQAAAAAQBAAAAAgAEAAAAKgIAAAgI=</HashCode>
       <FileName>ConsoleDrivers\V2\ApplicationV2.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -358,6 +370,14 @@
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiKeyboardParser.cs</FileName>
     </TypeIdentifier>
   </Class>
+  <Class Name="Terminal.Gui.ToplevelTransitionManager" Collapsed="true">
+    <Position X="9.25" Y="13.75" Width="2.25" />
+    <TypeIdentifier>
+      <HashCode>AIAAAAAAAAAAAAEAAAAAAAAAAEIAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\ToplevelTransitionManager.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
   <Interface Name="Terminal.Gui.IConsoleInput&lt;T&gt;" Collapsed="true">
     <Position X="12.5" Y="1" Width="1.5" />
     <TypeIdentifier>
@@ -448,6 +468,13 @@
       <FileName>ConsoleDrivers\V2\IKeyConverter.cs</FileName>
     </TypeIdentifier>
   </Interface>
+  <Interface Name="Terminal.Gui.IToplevelTransitionManager">
+    <Position X="9.25" Y="12" Width="2.25" />
+    <TypeIdentifier>
+      <HashCode>AIAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\IToplevelTransitionManager.cs</FileName>
+    </TypeIdentifier>
+  </Interface>
   <Enum Name="Terminal.Gui.AnsiResponseParserState">
     <Position X="20.25" Y="7.25" Width="2" />
     <TypeIdentifier>

From c4c774d3a319526ceb327f46e5924c1b5bc8d74e Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 15 Feb 2025 13:49:11 +0000
Subject: [PATCH 173/198] Add logging docs

---
 docfx/docs/logging.md | 80 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 80 insertions(+)
 create mode 100644 docfx/docs/logging.md

diff --git a/docfx/docs/logging.md b/docfx/docs/logging.md
new file mode 100644
index 0000000000..21f359eef7
--- /dev/null
+++ b/docfx/docs/logging.md
@@ -0,0 +1,80 @@
+# Logging
+
+Logging has come to Terminal.Gui! You can now enable comprehensive logging of the internals of the libray. This can help diagnose issues with specific terminals, keyboard cultures and/or operating system specific issues.
+
+To enable file logging you should set the static property `Logging.Logger` to an instance of `Microsoft.Extensions.Logging.ILogger`.  If your program already uses logging you can provide a shared instance or instance from Dependency Injection (DI).
+
+Alternatively you can create a new log to ensure only Terminal.Gui logs appear.
+
+Any logging framework will work  (Serilog, NLog, Log4Net etc) but you should ensure you only log to File or UDP etc (i.e. not to console!).
+
+## Worked example with Serilog to file
+
+Here is an example of how to add logging of Terminal.Gui internals to your program using Serilog file log.
+
+Add the Serilog dependencies:
+
+```
+ dotnet add package Serilog
+ dotnet add package Serilog.Sinks.File
+ dotnet add package Serilog.Extensions.Logging 
+```
+
+Create a static helper function to create the logger and store in `Logging.Logger`:
+
+```csharp
+Logging.Logger = CreateLogger();
+
+
+ static ILogger CreateLogger()
+{
+    // Configure Serilog to write logs to a file
+    Log.Logger = new LoggerConfiguration()
+        .MinimumLevel.Verbose() // Verbose includes Trace and Debug
+        .WriteTo.File("logs/logfile.txt", rollingInterval: RollingInterval.Day)
+        .CreateLogger();
+
+    // Create a logger factory compatible with Microsoft.Extensions.Logging
+    using var loggerFactory = LoggerFactory.Create(builder =>
+    {
+        builder
+            .AddSerilog(dispose: true) // Integrate Serilog with ILogger
+            .SetMinimumLevel(LogLevel.Trace); // Set minimum log level
+    });
+
+    // Get an ILogger instance
+    return loggerFactory.CreateLogger("Global Logger");
+}
+```
+
+This will create logs in your executables directory (e.g.`\bin\Debug\net8.0`).
+
+Example logs:
+```
+2025-02-15 13:36:48.635 +00:00 [INF] Main Loop Coordinator booting...
+2025-02-15 13:36:48.663 +00:00 [INF] Creating NetOutput
+2025-02-15 13:36:48.668 +00:00 [INF] Creating NetInput
+2025-02-15 13:36:48.671 +00:00 [INF] Main Loop Coordinator booting complete
+2025-02-15 13:36:49.145 +00:00 [INF] Run 'MainWindow(){X=0,Y=0,Width=0,Height=0}'
+2025-02-15 13:36:49.163 +00:00 [VRB] Mouse Interpreter raising ReportMousePosition
+2025-02-15 13:36:49.165 +00:00 [VRB] AnsiResponseParser handled as mouse '[<35;50;23m'
+2025-02-15 13:36:49.166 +00:00 [VRB] MainWindow triggered redraw (NeedsDraw=True NeedsLayout=True) 
+2025-02-15 13:36:49.167 +00:00 [INF] Console size changes from '{Width=0, Height=0}' to {Width=120, Height=30}
+2025-02-15 13:36:49.859 +00:00 [VRB] AnsiResponseParser releasing '
+'
+2025-02-15 13:36:49.867 +00:00 [VRB] MainWindow triggered redraw (NeedsDraw=True NeedsLayout=True) 
+2025-02-15 13:36:50.857 +00:00 [VRB] MainWindow triggered redraw (NeedsDraw=True NeedsLayout=True) 
+2025-02-15 13:36:51.417 +00:00 [VRB] MainWindow triggered redraw (NeedsDraw=True NeedsLayout=True) 
+2025-02-15 13:36:52.224 +00:00 [VRB] Mouse Interpreter raising ReportMousePosition
+2025-02-15 13:36:52.226 +00:00 [VRB] AnsiResponseParser handled as mouse '[<35;51;23m'
+2025-02-15 13:36:52.226 +00:00 [VRB] Mouse Interpreter raising ReportMousePosition
+2025-02-15 13:36:52.226 +00:00 [VRB] AnsiResponseParser handled as mouse '[<35;52;23m'
+2025-02-15 13:36:52.226 +00:00 [VRB] Mouse Interpreter raising ReportMousePosition
+2025-02-15 13:36:52.226 +00:00 [VRB] AnsiResponseParser handled as mouse '[<35;53;23m'
+...
+2025-02-15 13:36:52.846 +00:00 [VRB] Mouse Interpreter raising ReportMousePosition
+2025-02-15 13:36:52.846 +00:00 [VRB] AnsiResponseParser handled as mouse '[<35;112;4m'
+2025-02-15 13:36:54.151 +00:00 [INF] RequestStop ''
+2025-02-15 13:36:54.151 +00:00 [VRB] AnsiResponseParser handled as keyboard '[21~'
+2025-02-15 13:36:54.225 +00:00 [INF] Input loop exited cleanly
+```
\ No newline at end of file

From f893a2b5b4d77c6e8f5fc9cc47e0c6428d04bf71 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 15 Feb 2025 13:57:36 +0000
Subject: [PATCH 174/198] Add section on metrics

---
 docfx/docs/logging.md | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/docfx/docs/logging.md b/docfx/docs/logging.md
index 21f359eef7..a842974a9a 100644
--- a/docfx/docs/logging.md
+++ b/docfx/docs/logging.md
@@ -77,4 +77,35 @@ Example logs:
 2025-02-15 13:36:54.151 +00:00 [INF] RequestStop ''
 2025-02-15 13:36:54.151 +00:00 [VRB] AnsiResponseParser handled as keyboard '[21~'
 2025-02-15 13:36:54.225 +00:00 [INF] Input loop exited cleanly
+```
+
+## Metrics
+
+If you are finding that the UI is slow or unresponsive - or are just interested in performance metrics.  You can see these by instaling the `dotnet-counter` tool and running it for your process.
+
+```
+dotnet tool install dotnet-counters --global
+```
+
+```
+ dotnet-counters monitor -n YourProcessName --counters Terminal.Gui
+```
+
+Example output:
+
+```
+Press p to pause, r to resume, q to quit.
+    Status: Running
+
+Name                                                                                                       Current Value
+[Terminal.Gui]
+    Drain Input (ms)
+        Percentile
+        50                                                                                                         0            95                                                                                                         0            99                                                                                                         0        
+    Invokes & Timers (ms)
+        Percentile
+        50                                                                                                         0            95                                                                                                         0            99                                                                                                         0        
+    Iteration (ms)
+        Percentile
+        50                                                                                                         0            95                                                                                                         1            99                                                                                                         1        Redraws (Count)                                                                                                9    
 ```
\ No newline at end of file

From 5b27c90843eb0d3eb09cd39c4ed12b338c95e22b Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 15 Feb 2025 14:21:45 +0000
Subject: [PATCH 175/198] Add more info about metrics

---
 docfx/docs/logging.md | 22 +++++++++++++++++-----
 1 file changed, 17 insertions(+), 5 deletions(-)

diff --git a/docfx/docs/logging.md b/docfx/docs/logging.md
index a842974a9a..6df97a0feb 100644
--- a/docfx/docs/logging.md
+++ b/docfx/docs/logging.md
@@ -97,15 +97,27 @@ Example output:
 Press p to pause, r to resume, q to quit.
     Status: Running
 
-Name                                                                                                       Current Value
+Name                                                      Current Value
 [Terminal.Gui]
     Drain Input (ms)
         Percentile
-        50                                                                                                         0            95                                                                                                         0            99                                                                                                         0        
+        50                                                      0
+        95                                                      0
+        99                                                      0
     Invokes & Timers (ms)
         Percentile
-        50                                                                                                         0            95                                                                                                         0            99                                                                                                         0        
+        50                                                      0
+        95                                                      0
+        99                                                      0
     Iteration (ms)
         Percentile
-        50                                                                                                         0            95                                                                                                         1            99                                                                                                         1        Redraws (Count)                                                                                                9    
-```
\ No newline at end of file
+        50                                                      0
+        95                                                      1
+        99                                                      1
+        Redraws (Count)                                         9
+```
+
+Metrics figures issues such as:
+
+- Your console constantly being refreshed (Redraws)
+- You are blocking main thread with long running Invokes / Timeout callbacks (Invokes & Timers)

From 8aa85bb7ad10848ad396fd313af2240bedce51b5 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 15 Feb 2025 14:58:52 +0000
Subject: [PATCH 176/198] More logging

---
 .../AnsiResponseParser/AnsiKeyboardParser.cs       | 14 ++++++++++++--
 Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs   |  6 +++++-
 2 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
index e3de89d62f..6876948397 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
@@ -1,5 +1,6 @@
 #nullable enable
 using System.Text.RegularExpressions;
+using Microsoft.Extensions.Logging;
 
 namespace Terminal.Gui;
 
@@ -52,7 +53,7 @@ Down Arrow	\u001bOB
         {
             char finalLetter = match.Groups [1].Value.Single();
 
-            return finalLetter switch
+            var k = finalLetter switch
                    {
                        'P' => Key.F1,
                        'Q' => Key.F2,
@@ -66,6 +67,9 @@ Down Arrow	\u001bOB
 
                        _ => null
                    };
+
+            Logging.Logger.LogTrace ($"{nameof (AnsiKeyboardParser)} interpreted ss3 pattern {input} as {k}");
+            return k;
         }
 
         return null;
@@ -116,6 +120,8 @@ Down Arrow	\u001bOB
                       };
             }
 
+            Logging.Logger.LogTrace ($"{nameof (AnsiKeyboardParser)} interpreted basic cursor pattern {input} as {key}");
+
             return key;
         }
 
@@ -135,7 +141,7 @@ Down Arrow	\u001bOB
 
             int digit = int.Parse (functionDigit);
 
-            return digit switch
+            var f = digit switch
                    {
                        24 => Key.F12,
                        23 => Key.F11,
@@ -151,6 +157,10 @@ Down Arrow	\u001bOB
                        11 => Key.F1,
                        _ => null,
                    };
+
+            Logging.Logger.LogTrace ($"{nameof (AnsiKeyboardParser)} interpreted function key pattern {input} as {f}");
+
+            return f;
         }
 
         return null;
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index 4ce2b40a18..c3c9b77881 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -38,7 +38,11 @@ public abstract class InputProcessor<T> : IInputProcessor
     ///     <see cref="OnKeyUp"/>.
     /// </summary>
     /// <param name="a"></param>
-    public void OnKeyDown (Key a) { KeyDown?.Invoke (this, a); }
+    public void OnKeyDown (Key a)
+    {
+        Logging.Logger.LogTrace ($"{nameof(InputProcessor<T>)} raised {a}");
+        KeyDown?.Invoke (this, a);
+    }
 
     /// <summary>Event fired when a key is released.</summary>
     /// <remarks>

From b7f4bc678b54820bb93985f5cc17ac2e9f22cbbf Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 15 Feb 2025 15:05:06 +0000
Subject: [PATCH 177/198] improve logging specificity for mouse

---
 .../ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs  | 8 +++++++-
 .../AnsiResponseParser/AnsiResponseParser.cs              | 4 ----
 2 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs
index ee3d3a3624..400a541261 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs
@@ -1,5 +1,7 @@
 #nullable enable
+using System.Runtime.ConstrainedExecution;
 using System.Text.RegularExpressions;
+using Microsoft.Extensions.Logging;
 
 namespace Terminal.Gui;
 
@@ -46,11 +48,15 @@ public bool IsMouse (string cur)
             int y = int.Parse (match.Groups [3].Value) - 1;
             char terminator = match.Groups [4].Value.Single ();
 
-            return new ()
+            var m = new MouseEventArgs()
             {
                 Position = new (x, y),
                 Flags = GetFlags (buttonCode, terminator)
             };
+
+
+            Logging.Logger.LogTrace ($"{nameof(AnsiMouseParser)} handled as {input} mouse {m.Flags} at {m.Position}");
+            return m;
         }
 
         // its some kind of odd mouse event that doesn't follow expected format?
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
index b3c0f97f11..098633d3bb 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
@@ -217,8 +217,6 @@ protected bool ShouldReleaseHeldContent ()
                 RaiseMouseEvent (cur);
                 ResetState ();
 
-                Logging.Logger.LogTrace ($"AnsiResponseParser handled as mouse '{cur}'");
-
                 return false;
             }
 
@@ -227,8 +225,6 @@ protected bool ShouldReleaseHeldContent ()
                 RaiseKeyboardEvent (cur);
                 ResetState ();
 
-                Logging.Logger.LogTrace ($"AnsiResponseParser handled as keyboard '{cur}'");
-
                 return false;
             }
 

From 62fef52c4fb99a483f0d4f197942f0f033f205df Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 20 Feb 2025 08:58:13 +0000
Subject: [PATCH 178/198] Refactor AnsiKeyboardParser to be more extensible

---
 .../AnsiResponseParser/AnsiKeyboardParser.cs  | 178 ------------------
 .../Keyboard/AnsiKeyboardParser.cs            |  39 ++++
 .../Keyboard/AnsiKeyboardParserPattern.cs     |  11 ++
 .../Keyboard/ArrowKeyPattern.cs               |  49 +++++
 .../Keyboard/FunctionKeyPattern.cs            |  38 ++++
 .../AnsiResponseParser/Keyboard/Ss3Pattern.cs |  35 ++++
 6 files changed, 172 insertions(+), 178 deletions(-)
 delete mode 100644 Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParser.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/ArrowKeyPattern.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/FunctionKeyPattern.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/Ss3Pattern.cs

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
deleted file mode 100644
index 6876948397..0000000000
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiKeyboardParser.cs
+++ /dev/null
@@ -1,178 +0,0 @@
-#nullable enable
-using System.Text.RegularExpressions;
-using Microsoft.Extensions.Logging;
-
-namespace Terminal.Gui;
-
-/// <summary>
-/// Parses ansi escape sequence strings that describe keyboard activity e.g. cursor keys
-/// into <see cref="Key"/>.
-/// </summary>
-public class AnsiKeyboardParser
-{
-    /*
-     *F1	\u001bOP
-       F2	\u001bOQ
-       F3	\u001bOR
-       F4	\u001bOS
-       F5 (sometimes)	\u001bOt
-       Left Arrow	\u001bOD
-       Right Arrow	\u001bOC
-       Up Arrow	\u001bOA
-       Down Arrow	\u001bOB
-     */
-    private readonly Regex _ss3Pattern = new (@"^\u001bO([PQRStDCAB])$");
-
-    /*
-     * F1 - F12
-     */
-    private readonly Regex _functionKey = new (@"^\u001b\[(\d+)~$");
-
-    // Regex patterns for ANSI arrow keys (Up, Down, Left, Right)
-    private readonly Regex _arrowKeyPattern = new (@"^\u001b\[(1;(\d+))?([A-D])$", RegexOptions.Compiled);
-
-    /// <summary>
-    ///     Parses an ANSI escape sequence into a keyboard event. Returns null if input
-    ///     is not a recognized keyboard event or its syntax is not understood.
-    /// </summary>
-    /// <param name="input"></param>
-    /// <returns></returns>
-    public Key? ProcessKeyboardInput (string input)
-    {
-        return MapAsSs3Key(input) ??
-            MapAsFunctionKey (input) ??
-            MapAsArrowKey (input);
-    }
-
-    private Key? MapAsSs3Key (string input)
-    {
-        // Match arrow key events
-        Match match = _ss3Pattern.Match (input);
-
-        if (match.Success)
-        {
-            char finalLetter = match.Groups [1].Value.Single();
-
-            var k = finalLetter switch
-                   {
-                       'P' => Key.F1,
-                       'Q' => Key.F2,
-                       'R' => Key.F3,
-                       'S' => Key.F4,
-                       't' => Key.F5,
-                       'D' => Key.CursorLeft,
-                       'C' => Key.CursorRight,
-                       'A' => Key.CursorUp,
-                       'B' => Key.CursorDown,
-
-                       _ => null
-                   };
-
-            Logging.Logger.LogTrace ($"{nameof (AnsiKeyboardParser)} interpreted ss3 pattern {input} as {k}");
-            return k;
-        }
-
-        return null;
-    }
-
-    private Key? MapAsArrowKey (string input)
-    {
-        // Match arrow key events
-        Match match = _arrowKeyPattern.Match (input);
-
-        if (match.Success)
-        {
-            // Group 2 captures the modifier number, if present
-            string modifierGroup = match.Groups [2].Value;
-            char direction = match.Groups [3].Value [0];
-
-            Key? key = direction switch
-                      {
-                          'A' => Key.CursorUp,
-                          'B' => Key.CursorDown,
-                          'C' => Key.CursorRight,
-                          'D' => Key.CursorLeft,
-                          _ => null
-                      };
-
-            if (key is null)
-            {
-                return null;
-            }
-
-            // Examples:
-            // without modifiers:
-            //   \u001b\[B
-            // with modifiers:
-            //   \u001b\[1; 2B
-
-            if (!string.IsNullOrWhiteSpace (modifierGroup) && int.TryParse (modifierGroup, out var modifier))
-            {
-                key = modifier switch
-                      {
-                          2 => key.WithShift,
-                          3 => key.WithAlt,
-                          4 => key.WithAlt.WithShift,
-                          5 => key.WithCtrl,
-                          6 => key.WithCtrl.WithShift,
-                          7 => key.WithCtrl.WithAlt,
-                          8 => key.WithCtrl.WithAlt.WithShift
-                      };
-            }
-
-            Logging.Logger.LogTrace ($"{nameof (AnsiKeyboardParser)} interpreted basic cursor pattern {input} as {key}");
-
-            return key;
-        }
-
-        // It's an unrecognized keyboard event
-        return null;
-
-    }
-
-    private Key? MapAsFunctionKey (string input)
-    {
-        // Match arrow key events
-        Match match = _functionKey.Match (input);
-
-        if (match.Success)
-        {
-            string functionDigit = match.Groups [1].Value;
-
-            int digit = int.Parse (functionDigit);
-
-            var f = digit switch
-                   {
-                       24 => Key.F12,
-                       23 => Key.F11,
-                       21 => Key.F10,
-                       20 => Key.F9,
-                       19 => Key.F8,
-                       18 => Key.F7,
-                       17 => Key.F6,
-                       15 => Key.F5,
-                       14 => Key.F4,
-                       13 => Key.F3,
-                       12 => Key.F2,
-                       11 => Key.F1,
-                       _ => null,
-                   };
-
-            Logging.Logger.LogTrace ($"{nameof (AnsiKeyboardParser)} interpreted function key pattern {input} as {f}");
-
-            return f;
-        }
-
-        return null;
-    }
-
-    /// <summary>
-    /// Returns <see langword="true"/> if the given escape code
-    /// is a keyboard escape code (e.g. cursor key)
-    /// </summary>
-    /// <param name="cur">escape code</param>
-    /// <returns></returns>
-    public bool IsKeyboard (string cur) {
-        return _ss3Pattern.IsMatch(cur) || _functionKey.IsMatch (cur) || _arrowKeyPattern.IsMatch (cur);
-    }
-}
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParser.cs
new file mode 100644
index 0000000000..0e7a099b16
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParser.cs
@@ -0,0 +1,39 @@
+#nullable enable
+using Microsoft.Extensions.Logging;
+
+namespace Terminal.Gui;
+
+/// <summary>
+/// Parses ANSI escape sequence strings that describe keyboard activity into <see cref="Key"/>.
+/// </summary>
+public class AnsiKeyboardParser
+{
+    private readonly List<AnsiKeyboardParserPattern> _patterns = new ()
+    {
+        new Ss3Pattern(),
+        new FunctionKeyPattern(),
+        new ArrowKeyPattern()
+    };
+
+    public Key? ProcessKeyboardInput (string input)
+    {
+        foreach (var pattern in _patterns)
+        {
+            if (pattern.IsMatch (input))
+            {
+                var key = pattern.GetKey (input);
+                if (key != null)
+                {
+                    Logging.Logger.LogTrace ($"{nameof (AnsiKeyboardParser)} interpreted {input} as {key}");
+                    return key;
+                }
+            }
+        }
+        return null;
+    }
+
+    public bool IsKeyboard (string input)
+    {
+        return _patterns.Any (pattern => pattern.IsMatch (input));
+    }
+}
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs
new file mode 100644
index 0000000000..3a7a13b658
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs
@@ -0,0 +1,11 @@
+#nullable enable
+namespace Terminal.Gui;
+
+/// <summary>
+/// Base class for ANSI keyboard parsing patterns.
+/// </summary>
+public abstract class AnsiKeyboardParserPattern
+{
+    public abstract bool IsMatch (string input);
+    public abstract Key? GetKey (string input);
+}
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/ArrowKeyPattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/ArrowKeyPattern.cs
new file mode 100644
index 0000000000..4bd543d6fc
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/ArrowKeyPattern.cs
@@ -0,0 +1,49 @@
+#nullable enable
+using System.Text.RegularExpressions;
+
+namespace Terminal.Gui;
+
+public class ArrowKeyPattern : AnsiKeyboardParserPattern
+{
+    private static readonly Regex _pattern = new (@"^\u001b\[(1;(\d+))?([A-D])$");
+
+    public override bool IsMatch (string input) => _pattern.IsMatch (input);
+
+    public override Key? GetKey (string input)
+    {
+        var match = _pattern.Match (input);
+
+        if (!match.Success)
+        {
+            return null;
+        }
+
+        char direction = match.Groups [3].Value [0];
+        string modifierGroup = match.Groups [2].Value;
+
+        Key? key = direction switch
+                   {
+                       'A' => Key.CursorUp,
+                       'B' => Key.CursorDown,
+                       'C' => Key.CursorRight,
+                       'D' => Key.CursorLeft,
+                       _ => null
+                   };
+
+        if (key != null && int.TryParse (modifierGroup, out var modifier))
+        {
+            key = modifier switch
+                  {
+                      2 => key.WithShift,
+                      3 => key.WithAlt,
+                      4 => key.WithAlt.WithShift,
+                      5 => key.WithCtrl,
+                      6 => key.WithCtrl.WithShift,
+                      7 => key.WithCtrl.WithAlt,
+                      8 => key.WithCtrl.WithAlt.WithShift,
+                      _ => key
+                  };
+        }
+        return key;
+    }
+}
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/FunctionKeyPattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/FunctionKeyPattern.cs
new file mode 100644
index 0000000000..8d8ab66aaa
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/FunctionKeyPattern.cs
@@ -0,0 +1,38 @@
+#nullable enable
+using System.Text.RegularExpressions;
+
+namespace Terminal.Gui;
+
+public class FunctionKeyPattern : AnsiKeyboardParserPattern
+{
+    private static readonly Regex _pattern = new (@"^\u001b\[(\d+)~$");
+
+    public override bool IsMatch (string input) => _pattern.IsMatch (input);
+
+    public override Key? GetKey (string input)
+    {
+        var match = _pattern.Match (input);
+
+        if (!match.Success)
+        {
+            return null;
+        }
+
+        return int.Parse (match.Groups [1].Value) switch
+               {
+                   11 => Key.F1,
+                   12 => Key.F2,
+                   13 => Key.F3,
+                   14 => Key.F4,
+                   15 => Key.F5,
+                   17 => Key.F6,
+                   18 => Key.F7,
+                   19 => Key.F8,
+                   20 => Key.F9,
+                   21 => Key.F10,
+                   23 => Key.F11,
+                   24 => Key.F12,
+                   _ => null
+               };
+    }
+}
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/Ss3Pattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/Ss3Pattern.cs
new file mode 100644
index 0000000000..c49337a892
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/Ss3Pattern.cs
@@ -0,0 +1,35 @@
+#nullable enable
+using System.Text.RegularExpressions;
+
+namespace Terminal.Gui;
+
+public class Ss3Pattern : AnsiKeyboardParserPattern
+{
+    private static readonly Regex _pattern = new (@"^\u001bO([PQRStDCAB])$");
+
+    public override bool IsMatch (string input) => _pattern.IsMatch (input);
+
+    public override Key? GetKey (string input)
+    {
+        var match = _pattern.Match (input);
+
+        if (!match.Success)
+        {
+            return null;
+        }
+
+        return match.Groups [1].Value.Single () switch
+               {
+                   'P' => Key.F1,
+                   'Q' => Key.F2,
+                   'R' => Key.F3,
+                   'S' => Key.F4,
+                   't' => Key.F5,
+                   'D' => Key.CursorLeft,
+                   'C' => Key.CursorRight,
+                   'A' => Key.CursorUp,
+                   'B' => Key.CursorDown,
+                   _ => null
+               };
+    }
+}

From f97a31d692f9c7bf2141aebc292269dba7d2eb10 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 20 Feb 2025 19:51:13 +0000
Subject: [PATCH 179/198] Add concept of 'last minute sequences' for Alt+A etc

---
 .../AnsiResponseParser/AnsiResponseParser.cs  | 42 ++++++++++++++-----
 .../Keyboard/AnsiKeyboardParser.cs            | 35 ++++++----------
 .../Keyboard/AnsiKeyboardParserPattern.cs     | 24 ++++++++++-
 .../Keyboard/ArrowKeyPattern.cs               |  2 +-
 .../Keyboard/EscAsAltPattern.cs               | 30 +++++++++++++
 .../Keyboard/FunctionKeyPattern.cs            |  2 +-
 .../AnsiResponseParser/Keyboard/Ss3Pattern.cs |  2 +-
 .../ConsoleDrivers/V2/InputProcessor.cs       |  3 +-
 .../ConsoleDrivers/AnsiKeyboardParserTests.cs |  2 +-
 9 files changed, 103 insertions(+), 39 deletions(-)
 create mode 100644 Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/EscAsAltPattern.cs

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
index 098633d3bb..1c7f0e8a30 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
@@ -1,5 +1,6 @@
 #nullable enable
 
+using System.Runtime.ConstrainedExecution;
 using Microsoft.Extensions.Logging;
 
 namespace Terminal.Gui;
@@ -7,7 +8,7 @@ namespace Terminal.Gui;
 internal abstract class AnsiResponseParserBase : IAnsiResponseParser
 {
     private readonly AnsiMouseParser _mouseParser = new ();
-    private readonly AnsiKeyboardParser _keyboardParser = new ();
+    protected readonly AnsiKeyboardParser _keyboardParser = new ();
     protected object _lockExpectedResponses = new ();
 
     protected object _lockState = new ();
@@ -161,13 +162,14 @@ int inputLength
                         ReleaseHeld (appendOutput, AnsiResponseParserState.ExpectingEscapeSequence);
                         _heldContent.AddToHeld (currentObj); // Hold the new escape
                     }
-                    else if (currentChar == '[' || currentChar == 'O')
+                    else if (currentChar == '[' || char.IsLetterOrDigit (currentChar))
                     {
                         //We need O for SS3 mode F1-F4 e.g. "<esc>OP" => F1
+                        //We need any letter or digit for Alt+Letter (see EscAsAltPattern)
 
                         // Detected '[' or 'O', transition to InResponse state
                         State = AnsiResponseParserState.InResponse;
-                        _heldContent.AddToHeld (currentObj); // Hold the '['
+                        _heldContent.AddToHeld (currentObj); // Hold the letter
                     }
                     else
                     {
@@ -220,12 +222,17 @@ protected bool ShouldReleaseHeldContent ()
                 return false;
             }
 
-            if (HandleKeyboard && IsKeyboard (cur))
+            if (HandleKeyboard)
             {
-                RaiseKeyboardEvent (cur);
-                ResetState ();
+                var pattern = _keyboardParser.IsKeyboard (cur);
 
-                return false;
+                if (pattern != null)
+                {
+
+                    RaiseKeyboardEvent (pattern, cur);
+                    ResetState ();
+                    return false;
+                }
             }
 
             lock (_lockExpectedResponses)
@@ -301,9 +308,9 @@ private void RaiseMouseEvent (string cur)
 
     private bool IsMouse (string cur) { return _mouseParser.IsMouse (cur); }
 
-    private void RaiseKeyboardEvent (string cur)
+    protected void RaiseKeyboardEvent (AnsiKeyboardParserPattern pattern, string cur)
     {
-        Key? k = _keyboardParser.ProcessKeyboardInput (cur);
+        Key? k = pattern.GetKey (cur);
 
         if (k is null)
         {
@@ -315,8 +322,6 @@ private void RaiseKeyboardEvent (string cur)
         }
     }
 
-    private bool IsKeyboard (string cur) { return _keyboardParser.IsKeyboard (cur); }
-
     /// <summary>
     ///     <para>
     ///         When overriden in a derived class, indicates whether the unexpected response
@@ -447,6 +452,21 @@ public Tuple<char, T> [] Release ()
         // Lock in case Release is called from different Thread from parse
         lock (_lockState)
         {
+            var cur = _heldContent.HeldToString ();
+
+            if (HandleKeyboard)
+            {
+                var pattern = _keyboardParser.IsKeyboard (cur,true);
+
+                if (pattern != null)
+                {
+                    RaiseKeyboardEvent (pattern, cur);
+                    ResetState ();
+                    return [];
+                }
+            }
+
+
             Tuple<char, T> [] result = HeldToEnumerable ().ToArray ();
 
             ResetState ();
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParser.cs
index 0e7a099b16..654acf25d0 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParser.cs
@@ -12,28 +12,19 @@ public class AnsiKeyboardParser
     {
         new Ss3Pattern(),
         new FunctionKeyPattern(),
-        new ArrowKeyPattern()
+        new ArrowKeyPattern(),
+        new EscAsAltPattern(){IsLastMinute=true}
     };
-
-    public Key? ProcessKeyboardInput (string input)
-    {
-        foreach (var pattern in _patterns)
-        {
-            if (pattern.IsMatch (input))
-            {
-                var key = pattern.GetKey (input);
-                if (key != null)
-                {
-                    Logging.Logger.LogTrace ($"{nameof (AnsiKeyboardParser)} interpreted {input} as {key}");
-                    return key;
-                }
-            }
-        }
-        return null;
-    }
-
-    public bool IsKeyboard (string input)
+    
+    /// <summary>
+    /// Looks for any pattern that matches the <paramref name="input"/> and returns
+    /// the matching pattern or <see langword="null"/> if no matches.
+    /// </summary>
+    /// <param name="input"></param>
+    /// <param name="isLastMinute"></param>
+    /// <returns></returns>
+    public AnsiKeyboardParserPattern? IsKeyboard (string input, bool isLastMinute = false)
     {
-        return _patterns.Any (pattern => pattern.IsMatch (input));
+        return _patterns.FirstOrDefault(pattern => pattern.IsLastMinute == isLastMinute && pattern.IsMatch (input));
     }
-}
+}
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs
index 3a7a13b658..1623694c7c 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs
@@ -1,4 +1,6 @@
 #nullable enable
+using Microsoft.Extensions.Logging;
+
 namespace Terminal.Gui;
 
 /// <summary>
@@ -6,6 +8,26 @@ namespace Terminal.Gui;
 /// </summary>
 public abstract class AnsiKeyboardParserPattern
 {
+    /// <summary>
+    /// Does this pattern dangerously overlap with other sequences
+    /// such that it should only be applied at the lsat second after
+    /// all other sequences have been tried.
+    /// <remarks>
+    /// When <see langword="true"/> this pattern will only be used
+    /// at <see cref="AnsiResponseParser.Release"/> time.
+    /// </remarks>
+    /// </summary>
+    public bool IsLastMinute { get; set; }
+
     public abstract bool IsMatch (string input);
-    public abstract Key? GetKey (string input);
+
+    public Key? GetKey (string input)
+    {
+        var key = GetKeyImpl (input);
+        Logging.Logger.LogTrace ($"{nameof (AnsiKeyboardParser)} interpreted {input} as {key}");
+
+        return key;
+    }
+
+    protected abstract Key? GetKeyImpl (string input);
 }
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/ArrowKeyPattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/ArrowKeyPattern.cs
index 4bd543d6fc..cc93c43d27 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/ArrowKeyPattern.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/ArrowKeyPattern.cs
@@ -9,7 +9,7 @@ public class ArrowKeyPattern : AnsiKeyboardParserPattern
 
     public override bool IsMatch (string input) => _pattern.IsMatch (input);
 
-    public override Key? GetKey (string input)
+    protected override Key? GetKeyImpl (string input)
     {
         var match = _pattern.Match (input);
 
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/EscAsAltPattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/EscAsAltPattern.cs
new file mode 100644
index 0000000000..e3f566627e
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/EscAsAltPattern.cs
@@ -0,0 +1,30 @@
+#nullable enable
+using System.Text.RegularExpressions;
+
+namespace Terminal.Gui;
+
+internal class EscAsAltPattern : AnsiKeyboardParserPattern
+{
+    public EscAsAltPattern ()
+    {
+        IsLastMinute=true;
+    }
+
+    private static readonly Regex _pattern = new (@"^\u001b([a-zA-Z0-9_])$");
+
+    public override bool IsMatch (string input) => _pattern.IsMatch (input);
+
+    protected override Key? GetKeyImpl (string input)
+    {
+        var match = _pattern.Match (input);
+
+        if (!match.Success)
+        {
+            return null;
+        }
+
+        char key = match.Groups [1].Value [0];
+
+        return new Key (key).WithAlt;
+    }
+}
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/FunctionKeyPattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/FunctionKeyPattern.cs
index 8d8ab66aaa..f17191339a 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/FunctionKeyPattern.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/FunctionKeyPattern.cs
@@ -9,7 +9,7 @@ public class FunctionKeyPattern : AnsiKeyboardParserPattern
 
     public override bool IsMatch (string input) => _pattern.IsMatch (input);
 
-    public override Key? GetKey (string input)
+    protected override Key? GetKeyImpl (string input)
     {
         var match = _pattern.Match (input);
 
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/Ss3Pattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/Ss3Pattern.cs
index c49337a892..49eeb3365e 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/Ss3Pattern.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/Ss3Pattern.cs
@@ -9,7 +9,7 @@ public class Ss3Pattern : AnsiKeyboardParserPattern
 
     public override bool IsMatch (string input) => _pattern.IsMatch (input);
 
-    public override Key? GetKey (string input)
+    protected override Key? GetKeyImpl (string input)
     {
         var match = _pattern.Match (input);
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index c3c9b77881..2ca6a820cf 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -124,7 +124,8 @@ public void ProcessQueue ()
 
     private IEnumerable<T> ReleaseParserHeldKeysIfStale ()
     {
-        if (Parser.State == AnsiResponseParserState.ExpectingEscapeSequence && DateTime.Now - Parser.StateChangedAt > _escTimeout)
+        if (Parser.State is AnsiResponseParserState.ExpectingEscapeSequence or AnsiResponseParserState.InResponse
+            && DateTime.Now - Parser.StateChangedAt > _escTimeout)
         {
             return Parser.Release ().Select (o => o.Item2);
         }
diff --git a/UnitTests/ConsoleDrivers/AnsiKeyboardParserTests.cs b/UnitTests/ConsoleDrivers/AnsiKeyboardParserTests.cs
index 09e7cc25e6..1554ac65e3 100644
--- a/UnitTests/ConsoleDrivers/AnsiKeyboardParserTests.cs
+++ b/UnitTests/ConsoleDrivers/AnsiKeyboardParserTests.cs
@@ -65,7 +65,7 @@ public static IEnumerable<object []> GetKeyboardTestData ()
     public void ProcessKeyboardInput_ReturnsCorrectKey (string input, Key? expectedKey)
     {
         // Act
-        Key? result = _parser.ProcessKeyboardInput (input);
+        Key? result = _parser.IsKeyboard (input)?.GetKey (input);
 
         // Assert
         Assert.Equal (expectedKey, result); // Verify the returned key matches the expected one

From 54a4c973fb35045025ae8d2b39dc6e5db647b033 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 20 Feb 2025 20:14:33 +0000
Subject: [PATCH 180/198] Only enter ansi response if processing keyboard

---
 .../ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
index 1c7f0e8a30..e498f61f23 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
@@ -162,7 +162,7 @@ int inputLength
                         ReleaseHeld (appendOutput, AnsiResponseParserState.ExpectingEscapeSequence);
                         _heldContent.AddToHeld (currentObj); // Hold the new escape
                     }
-                    else if (currentChar == '[' || char.IsLetterOrDigit (currentChar))
+                    else if (currentChar == '[' || (HandleKeyboard && char.IsLetterOrDigit (currentChar)))
                     {
                         //We need O for SS3 mode F1-F4 e.g. "<esc>OP" => F1
                         //We need any letter or digit for Alt+Letter (see EscAsAltPattern)

From a138d500d7cf47b29768d79cb0f4e88d64dc6820 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 22 Feb 2025 02:41:35 +0000
Subject: [PATCH 181/198] Fix holding down alt+key Prevent ever having 2+ esc
 in held

---
 .../AnsiResponseParser/AnsiResponseParser.cs  | 64 +++++++++++++------
 1 file changed, 46 insertions(+), 18 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
index e498f61f23..9d52e84254 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
@@ -7,6 +7,7 @@ namespace Terminal.Gui;
 
 internal abstract class AnsiResponseParserBase : IAnsiResponseParser
 {
+    private const char Escape = '\x1B';
     private readonly AnsiMouseParser _mouseParser = new ();
     protected readonly AnsiKeyboardParser _keyboardParser = new ();
     protected object _lockExpectedResponses = new ();
@@ -136,7 +137,7 @@ int inputLength
             char currentChar = getCharAtIndex (index);
             object currentObj = getObjectAtIndex (index);
 
-            bool isEscape = currentChar == '\x1B';
+            bool isEscape = currentChar == Escape;
 
             switch (State)
             {
@@ -181,12 +182,24 @@ int inputLength
                     break;
 
                 case AnsiResponseParserState.InResponse:
-                    _heldContent.AddToHeld (currentObj);
 
-                    // Check if the held content should be released
-                    if (ShouldReleaseHeldContent ())
+                    // if seeing another esc, we must resolve the current one first
+                    if (isEscape)
                     {
                         ReleaseHeld (appendOutput);
+                        State = AnsiResponseParserState.InResponse;
+                        _heldContent.AddToHeld (currentObj);
+                    }
+                    else
+                    {
+                        // Non esc, so continue to build sequence
+                        _heldContent.AddToHeld (currentObj);
+
+                        // Check if the held content should be released
+                        if (ShouldReleaseHeldContent ())
+                        {
+                            ReleaseHeld (appendOutput);
+                        }
                     }
 
                     break;
@@ -198,6 +211,7 @@ int inputLength
 
     private void ReleaseHeld (Action<object> appendOutput, AnsiResponseParserState newState = AnsiResponseParserState.Normal)
     {
+        TryLastMinuteSequences ();
         foreach (object o in _heldContent.HeldToObjects ())
         {
             appendOutput (o);
@@ -207,6 +221,31 @@ private void ReleaseHeld (Action<object> appendOutput, AnsiResponseParserState n
         _heldContent.ClearHeld ();
     }
 
+    /// <summary>
+    /// Checks current held chars against any sequences that have
+    /// conflicts with longer sequences e.g. Esc as Alt sequences
+    /// which can conflict if resolved earlier e.g. with EscOP ss3
+    /// sequences.
+    /// </summary>
+    protected void TryLastMinuteSequences ()
+    {
+        lock (_lockState)
+        {
+            string cur = _heldContent.HeldToString ();
+
+            if (HandleKeyboard)
+            {
+                var pattern = _keyboardParser.IsKeyboard (cur, true);
+
+                if (pattern != null)
+                {
+                    RaiseKeyboardEvent (pattern, cur);
+                    _heldContent.ClearHeld ();
+                }
+            }
+        }
+    }
+
     // Common response handler logic
     protected bool ShouldReleaseHeldContent ()
     {
@@ -452,20 +491,7 @@ public Tuple<char, T> [] Release ()
         // Lock in case Release is called from different Thread from parse
         lock (_lockState)
         {
-            var cur = _heldContent.HeldToString ();
-
-            if (HandleKeyboard)
-            {
-                var pattern = _keyboardParser.IsKeyboard (cur,true);
-
-                if (pattern != null)
-                {
-                    RaiseKeyboardEvent (pattern, cur);
-                    ResetState ();
-                    return [];
-                }
-            }
-
+            TryLastMinuteSequences ();
 
             Tuple<char, T> [] result = HeldToEnumerable ().ToArray ();
 
@@ -542,6 +568,8 @@ public string Release ()
     {
         lock (_lockState)
         {
+            TryLastMinuteSequences ();
+
             string output = _heldContent.HeldToString ();
             ResetState ();
 

From 4508da42e2cef298794920e4a85e0ae96125b7aa Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 22 Feb 2025 10:41:19 +0000
Subject: [PATCH 182/198] swallow odd stuff like esc,eot

---
 .../AnsiResponseParser/AnsiResponseParser.cs    | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
index 9d52e84254..a3b7863a3d 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
@@ -243,6 +243,21 @@ protected void TryLastMinuteSequences ()
                     _heldContent.ClearHeld ();
                 }
             }
+
+            // We have something totally unexpected, not a CSI and
+            // still Esc+<something>. So give last minute swallow chance
+            if (cur.Length >= 2 && cur [0] == Escape)
+            {
+                // Maybe swallow anyway if user has custom delegate
+                bool swallow = ShouldSwallowUnexpectedResponse ();
+
+                if (swallow)
+                {
+                    _heldContent.ClearHeld ();
+
+                    Logging.Logger.LogTrace ($"AnsiResponseParser last minute swallowed '{cur}'");
+                }
+            }
         }
     }
 
@@ -321,7 +336,7 @@ protected bool ShouldReleaseHeldContent ()
                 {
                     _heldContent.ClearHeld ();
 
-                    Logging.Logger.LogTrace ($"AnsiResponseParser bespoke processed '{cur}'");
+                    Logging.Logger.LogTrace ($"AnsiResponseParser swallowed '{cur}'");
 
                     // Do not send back to input stream
                     return false;

From 0ebd1fd499823f3553a5b6804b81b99070559fe1 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 22 Feb 2025 10:42:59 +0000
Subject: [PATCH 183/198] Fix for stale operation

---
 .../ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs     | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
index a3b7863a3d..7a34920cad 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
@@ -241,6 +241,8 @@ protected void TryLastMinuteSequences ()
                 {
                     RaiseKeyboardEvent (pattern, cur);
                     _heldContent.ClearHeld ();
+
+                    return;
                 }
             }
 

From 504f892f20aae5fec5c7a349ddf060bbde767232 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 22 Feb 2025 11:13:08 +0000
Subject: [PATCH 184/198] WIP fix alt semicolon interpreted as esc semicolon in
 v2net - test broken

---
 .../ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs   | 3 ++-
 Terminal.Gui/ConsoleDrivers/AnsiResponseParser/GenericHeld.cs | 3 +++
 Terminal.Gui/ConsoleDrivers/AnsiResponseParser/IHeld.cs       | 2 ++
 Terminal.Gui/ConsoleDrivers/AnsiResponseParser/StringHeld.cs  | 3 +++
 UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs           | 4 ++--
 5 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
index 7a34920cad..1355ed9fe0 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
@@ -163,10 +163,11 @@ int inputLength
                         ReleaseHeld (appendOutput, AnsiResponseParserState.ExpectingEscapeSequence);
                         _heldContent.AddToHeld (currentObj); // Hold the new escape
                     }
-                    else if (currentChar == '[' || (HandleKeyboard && char.IsLetterOrDigit (currentChar)))
+                    else if (_heldContent.Length == 1)
                     {
                         //We need O for SS3 mode F1-F4 e.g. "<esc>OP" => F1
                         //We need any letter or digit for Alt+Letter (see EscAsAltPattern)
+                        //In fact lets just always see what comes after esc
 
                         // Detected '[' or 'O', transition to InResponse state
                         State = AnsiResponseParserState.InResponse;
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/GenericHeld.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/GenericHeld.cs
index ffd8d46891..ec54b0ce55 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/GenericHeld.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/GenericHeld.cs
@@ -16,4 +16,7 @@ internal class GenericHeld<T> : IHeld
     public IEnumerable<object> HeldToObjects () { return held; }
 
     public void AddToHeld (object o) { held.Add ((Tuple<char, T>)o); }
+
+    /// <inheritdoc />
+    public int Length => held.Count;
 }
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/IHeld.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/IHeld.cs
index ab23f477fd..1f0079dbed 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/IHeld.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/IHeld.cs
@@ -30,4 +30,6 @@ internal interface IHeld
     /// </summary>
     /// <param name="o"></param>
     void AddToHeld (object o);
+
+    int Length { get; }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/StringHeld.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/StringHeld.cs
index f8b7f4e0a0..caa5fd765d 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/StringHeld.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/StringHeld.cs
@@ -15,4 +15,7 @@ internal class StringHeld : IHeld
     public IEnumerable<object> HeldToObjects () { return _held.ToString ().Select (c => (object)c); }
 
     public void AddToHeld (object o) { _held.Append ((char)o); }
+
+    /// <inheritdoc />
+    public int Length => _held.Length;
 }
diff --git a/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs b/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs
index 46516b5f7a..d3fc03d4ac 100644
--- a/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs
+++ b/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs
@@ -164,8 +164,8 @@ public static IEnumerable<object []> TestInputSequencesExact_Cases ()
             new []
             {
                 new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingEscapeSequence,string.Empty),
-                new StepExpectation ('H',AnsiResponseParserState.Normal,"\u001bH"), // H is known terminator and not expected one so here we release both chars
-                new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingEscapeSequence,string.Empty),
+                new StepExpectation ('H',AnsiResponseParserState.InResponse,string.Empty), // H is known terminator and not expected one so here we release both chars
+                new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingEscapeSequence,"\u001bH"),
                 new StepExpectation ('[',AnsiResponseParserState.InResponse,string.Empty),
                 new StepExpectation ('0',AnsiResponseParserState.InResponse,string.Empty),
                 new StepExpectation ('c',AnsiResponseParserState.Normal,string.Empty,"\u001b[0c"), // c is expected terminator so here we swallow input and populate expected response

From 1e86f288f6114a35ac8596ffe1b9eeed00748141 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 22 Feb 2025 16:39:49 +0000
Subject: [PATCH 185/198] Fix esc logic and remove test that no longer makes
 sense

---
 .../AnsiResponseParser/AnsiResponseParser.cs  |  2 +-
 .../ConsoleDrivers/AnsiResponseParserTests.cs | 33 +++----------------
 2 files changed, 5 insertions(+), 30 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
index 1355ed9fe0..f2839b3745 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
@@ -188,7 +188,7 @@ int inputLength
                     if (isEscape)
                     {
                         ReleaseHeld (appendOutput);
-                        State = AnsiResponseParserState.InResponse;
+                        State = AnsiResponseParserState.ExpectingEscapeSequence;
                         _heldContent.AddToHeld (currentObj);
                     }
                     else
diff --git a/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs b/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs
index d3fc03d4ac..fcc2b6127d 100644
--- a/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs
+++ b/UnitTests/ConsoleDrivers/AnsiResponseParserTests.cs
@@ -227,8 +227,10 @@ public void TestInputSequencesExact (string caseName, char? terminator, IEnumera
         {
             parser.ExpectResponse (terminator.Value.ToString (),(s)=> response = s,null, false);
         }
+        int step= 0;
         foreach (var state in expectedStates)
         {
+            step++;
             // If we expect the response to be detected at this step
             if (!string.IsNullOrWhiteSpace (state.ExpectedAnsiResponse))
             {
@@ -247,6 +249,8 @@ public void TestInputSequencesExact (string caseName, char? terminator, IEnumera
                 // And after passing input it shuld be the expected value
                 Assert.Equal (state.ExpectedAnsiResponse, response);
             }
+
+            output.WriteLine ($"Step {step} passed");
         }
     }
 
@@ -289,35 +293,6 @@ public void TwoExcapesInARow ()
         AssertManualReleaseIs ("\u001b",1);
     }
 
-    [Fact]
-    public void TwoExcapesInARowWithTextBetween ()
-    {
-        // Example user presses Esc key and types at the speed of light (normally the consumer should be handling Esc timeout)
-        // then a DAR comes in.
-        string input = "\u001bfish\u001b";
-        int i = 0;
-
-        // First Esc gets grabbed
-        AssertConsumed (input, ref i); // Esc
-        Assert.Equal (AnsiResponseParserState.ExpectingEscapeSequence,_parser1.State);
-        Assert.Equal (AnsiResponseParserState.ExpectingEscapeSequence, _parser2.State);
-
-        // Because next char is 'f' we do not see a bracket so release both
-        AssertReleased (input, ref i, "\u001bf", 0,1); // f
-
-        Assert.Equal (AnsiResponseParserState.Normal, _parser1.State);
-        Assert.Equal (AnsiResponseParserState.Normal, _parser2.State);
-
-        AssertReleased (input, ref i,"i",2);
-        AssertReleased (input, ref i, "s", 3);
-        AssertReleased (input, ref i, "h", 4);
-
-        AssertConsumed (input, ref i); // Second Esc
-
-        // Assume 50ms or something has passed, lets force release as no new content
-        AssertManualReleaseIs ("\u001b", 5);
-    }
-
     [Fact]
     public void TestLateResponses ()
     {

From c5c4991381ca84c23b5ceb7e39a88c9283ddf406 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sat, 22 Feb 2025 17:52:34 +0000
Subject: [PATCH 186/198] Start article about ansi parser

---
 docfx/docs/ansiparser.md | 50 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 50 insertions(+)
 create mode 100644 docfx/docs/ansiparser.md

diff --git a/docfx/docs/ansiparser.md b/docfx/docs/ansiparser.md
new file mode 100644
index 0000000000..d18bbe8882
--- /dev/null
+++ b/docfx/docs/ansiparser.md
@@ -0,0 +1,50 @@
+# AnsiResponseParser
+
+## Background
+Terminals send input to the running process as a stream of characters. In addition to regular characters ('a','b','c' etc), the terminal needs to be able to communicate more advanced concepts ('Alt+x', 'Mouse Moved', 'Terminal resized' etc). This is done through the use of 'terminal sequences'.
+
+All terminal sequences start with Esc (`'\x1B'`) and are then followed by specific characters per the event to which they correspond. For example:
+
+| Input Sequence  | Meaning                               |
+|----------------|-------------------------------------------|
+| `<Esc>[A`       | Up Arrow Key                              |
+| `<Esc>[B`       | Down Arrow Key                            |
+| `<Esc>[5~`      | Page Up Key                               |
+| `<Esc>[6~`      | Page Down Key                             |
+
+Most sequences begin with what is called a `CSI` which is just `\x1B[` (`<Esc>[`). But some terminals send older sequences such as:
+
+| Input Sequence  | Meaning                               |
+|----------------|-------------------------------------------|
+| `<Esc>OR`     | F3 (SS3 Pattern)                         |
+| `<Esc>OD`       | Left Arrow Key (SS3 Pattern)             |
+| `<Esc>g`      | Alt+G (Escape as alt)                      |
+| `<Esc>O`      |  Alt+Shift+O  (Escape as alt)|
+
+When using the windows driver, this is mostly already dealt with automatically by the relevant native APIs (e.g. `ReadConsoleInputW` from kernel32).  In contrast the net driver operates in 'raw mode' and processes input almost exclusively using these sequences.
+
+Regardless of the driver used, some escape codes will always be processed e.g. responses to Device Attributes Requests etc.
+
+## Role of AnsiResponseParser
+
+This class is responsible for filtering the input stream to distinguish between regular user key presses and terminal sequences. 
+
+## Timing 
+Timing is a critical component of interpreting the input stream. For example if the stream serves the escape (Esc), the parser must decide whether it's a standalone keypress or the start of a sequence. Similarly seeing the sequence `<Esc>O` could be Alt+Upper Case O, or the beginning of an SS3 pattern for F3 (were R to follow).
+
+Because it is such a critical component, it is abstracted away from the parser itself. Instead the host class (e.g. InputProcessor) must decide when a suitable time has elapsed, after which the `Release` method should be invoked which will forcibly resolve any waiting state.
+
+This can be controlled through the `_escTimeout` field. This approach is consistent with other terminal interfaces e.g. bash.
+
+## State and Held
+
+The parser has 3 states:
+
+| State  | Meaning                               |
+|----------------|-------------------------------------------|
+| `Normal`     | We are encountering regular keys and letting them pass through unprocessed|
+| `ExpectingEscapeSequence`       | We encountered an `Esc` and are holding it to see if a sequence follows |
+| `InResponse`      | The `Esc` was followed by more keys that look like they will make a full terminal sequence (hold them all) |
+
+Extensive trace logging is built into the implementation, to allow for reproducing corner cases and/or rare environments. See the logging article for more details on how to set this up.
+

From 99d359b10d2f11cff6706add0ba6a1c6f857bdd1 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 23 Feb 2025 11:06:10 +0000
Subject: [PATCH 187/198] Refactor arrow key pattern to be more extensible

---
 .../Keyboard/ArrowKeyPattern.cs               | 26 ++++++++++++-------
 1 file changed, 16 insertions(+), 10 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/ArrowKeyPattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/ArrowKeyPattern.cs
index cc93c43d27..f3deaa3b0f 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/ArrowKeyPattern.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/ArrowKeyPattern.cs
@@ -5,10 +5,23 @@ namespace Terminal.Gui;
 
 public class ArrowKeyPattern : AnsiKeyboardParserPattern
 {
-    private static readonly Regex _pattern = new (@"^\u001b\[(1;(\d+))?([A-D])$");
+    private Dictionary<char, Key> _terminators = new Dictionary<char, Key> ()
+    {
+        { 'A', Key.CursorUp },
+        {'B',Key.CursorDown},
+        {'C',Key.CursorRight},
+        {'D',Key.CursorLeft}
+    };
+
+    private readonly Regex _pattern;
 
     public override bool IsMatch (string input) => _pattern.IsMatch (input);
 
+    public ArrowKeyPattern ()
+    {
+        var terms = new string (_terminators.Select (k => k.Key).ToArray ());
+        _pattern = new (@$"^\u001b\[(1;(\d+))?([{terms}])$");
+    }
     protected override Key? GetKeyImpl (string input)
     {
         var match = _pattern.Match (input);
@@ -18,17 +31,10 @@ public class ArrowKeyPattern : AnsiKeyboardParserPattern
             return null;
         }
 
-        char direction = match.Groups [3].Value [0];
+        char terminator = match.Groups [3].Value [0];
         string modifierGroup = match.Groups [2].Value;
 
-        Key? key = direction switch
-                   {
-                       'A' => Key.CursorUp,
-                       'B' => Key.CursorDown,
-                       'C' => Key.CursorRight,
-                       'D' => Key.CursorLeft,
-                       _ => null
-                   };
+        var key = _terminators.GetValueOrDefault (terminator);
 
         if (key != null && int.TryParse (modifierGroup, out var modifier))
         {

From 436ab27112937710f81f5c483e0bef593defe110 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 23 Feb 2025 12:14:33 +0000
Subject: [PATCH 188/198] Merge arrow and function patterns and add page up
 home etc

---
 .../Keyboard/AnsiKeyboardParser.cs            |  3 +-
 .../Keyboard/AnsiKeyboardParserPattern.cs     |  7 +-
 .../Keyboard/ArrowKeyPattern.cs               | 55 --------------
 .../Keyboard/CsiKeyPattern.cs                 | 76 +++++++++++++++++++
 .../Keyboard/FunctionKeyPattern.cs            | 38 ----------
 5 files changed, 83 insertions(+), 96 deletions(-)
 delete mode 100644 Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/ArrowKeyPattern.cs
 create mode 100644 Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/CsiKeyPattern.cs
 delete mode 100644 Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/FunctionKeyPattern.cs

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParser.cs
index 654acf25d0..0db3ed38a6 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParser.cs
@@ -11,8 +11,7 @@ public class AnsiKeyboardParser
     private readonly List<AnsiKeyboardParserPattern> _patterns = new ()
     {
         new Ss3Pattern(),
-        new FunctionKeyPattern(),
-        new ArrowKeyPattern(),
+        new CsiKeyPattern(),
         new EscAsAltPattern(){IsLastMinute=true}
     };
     
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs
index 1623694c7c..e02a08c136 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs
@@ -20,11 +20,16 @@ public abstract class AnsiKeyboardParserPattern
     public bool IsLastMinute { get; set; }
 
     public abstract bool IsMatch (string input);
+    private string _name;
 
+    public AnsiKeyboardParserPattern ()
+    {
+        _name = GetType ().Name;
+    }
     public Key? GetKey (string input)
     {
         var key = GetKeyImpl (input);
-        Logging.Logger.LogTrace ($"{nameof (AnsiKeyboardParser)} interpreted {input} as {key}");
+        Logging.Logger.LogTrace ($"{nameof (AnsiKeyboardParser)} interpreted {input} as {key} using {_name}");
 
         return key;
     }
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/ArrowKeyPattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/ArrowKeyPattern.cs
deleted file mode 100644
index f3deaa3b0f..0000000000
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/ArrowKeyPattern.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-#nullable enable
-using System.Text.RegularExpressions;
-
-namespace Terminal.Gui;
-
-public class ArrowKeyPattern : AnsiKeyboardParserPattern
-{
-    private Dictionary<char, Key> _terminators = new Dictionary<char, Key> ()
-    {
-        { 'A', Key.CursorUp },
-        {'B',Key.CursorDown},
-        {'C',Key.CursorRight},
-        {'D',Key.CursorLeft}
-    };
-
-    private readonly Regex _pattern;
-
-    public override bool IsMatch (string input) => _pattern.IsMatch (input);
-
-    public ArrowKeyPattern ()
-    {
-        var terms = new string (_terminators.Select (k => k.Key).ToArray ());
-        _pattern = new (@$"^\u001b\[(1;(\d+))?([{terms}])$");
-    }
-    protected override Key? GetKeyImpl (string input)
-    {
-        var match = _pattern.Match (input);
-
-        if (!match.Success)
-        {
-            return null;
-        }
-
-        char terminator = match.Groups [3].Value [0];
-        string modifierGroup = match.Groups [2].Value;
-
-        var key = _terminators.GetValueOrDefault (terminator);
-
-        if (key != null && int.TryParse (modifierGroup, out var modifier))
-        {
-            key = modifier switch
-                  {
-                      2 => key.WithShift,
-                      3 => key.WithAlt,
-                      4 => key.WithAlt.WithShift,
-                      5 => key.WithCtrl,
-                      6 => key.WithCtrl.WithShift,
-                      7 => key.WithCtrl.WithAlt,
-                      8 => key.WithCtrl.WithAlt.WithShift,
-                      _ => key
-                  };
-        }
-        return key;
-    }
-}
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/CsiKeyPattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/CsiKeyPattern.cs
new file mode 100644
index 0000000000..b7935f8a57
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/CsiKeyPattern.cs
@@ -0,0 +1,76 @@
+#nullable enable
+using System.Text.RegularExpressions;
+
+namespace Terminal.Gui;
+
+public class CsiKeyPattern : AnsiKeyboardParserPattern
+{
+    private Dictionary<string, Key> _terminators = new Dictionary<string, Key> ()
+    {
+        { "A", Key.CursorUp },
+        { "B", Key.CursorDown },
+        { "C", Key.CursorRight },
+        { "D", Key.CursorLeft },
+        { "H", Key.Home },       // Home (older variant)
+        { "F", Key.End },        // End (older variant)
+        { "1~", Key.Home },      // Home (modern variant)
+        { "4~", Key.End },       // End (modern variant)
+        { "5~", Key.PageUp },
+        { "6~", Key.PageDown },
+        { "2~", Key.InsertChar },
+        { "3~", Key.Delete },
+        { "11~", Key.F1 },
+        { "12~", Key.F2 },
+        { "13~", Key.F3 },
+        { "14~", Key.F4 },
+        { "15~", Key.F5 },
+        { "17~", Key.F6 },
+        { "18~", Key.F7 },
+        { "19~", Key.F8 },
+        { "20~", Key.F9 },
+        { "21~", Key.F10 },
+        { "23~", Key.F11 },
+        { "24~", Key.F12 }
+    };
+
+
+    private readonly Regex _pattern;
+
+    public override bool IsMatch (string input) => _pattern.IsMatch (input);
+
+    public CsiKeyPattern ()
+    {
+        var terms = new string (_terminators.Select (k => k.Key [0]).Where (k=> !char.IsDigit (k)).ToArray ());
+        _pattern = new (@$"^\u001b\[(1;(\d+))?([{terms}]|\d+~)$");
+    }
+    protected override Key? GetKeyImpl (string input)
+    {
+        var match = _pattern.Match (input);
+
+        if (!match.Success)
+        {
+            return null;
+        }
+
+        var terminator = match.Groups [3].Value;
+        string modifierGroup = match.Groups [2].Value;
+
+        var key = _terminators.GetValueOrDefault (terminator);
+
+        if (key != null && int.TryParse (modifierGroup, out var modifier))
+        {
+            key = modifier switch
+                  {
+                      2 => key.WithShift,
+                      3 => key.WithAlt,
+                      4 => key.WithAlt.WithShift,
+                      5 => key.WithCtrl,
+                      6 => key.WithCtrl.WithShift,
+                      7 => key.WithCtrl.WithAlt,
+                      8 => key.WithCtrl.WithAlt.WithShift,
+                      _ => key
+                  };
+        }
+        return key;
+    }
+}
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/FunctionKeyPattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/FunctionKeyPattern.cs
deleted file mode 100644
index f17191339a..0000000000
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/FunctionKeyPattern.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-#nullable enable
-using System.Text.RegularExpressions;
-
-namespace Terminal.Gui;
-
-public class FunctionKeyPattern : AnsiKeyboardParserPattern
-{
-    private static readonly Regex _pattern = new (@"^\u001b\[(\d+)~$");
-
-    public override bool IsMatch (string input) => _pattern.IsMatch (input);
-
-    protected override Key? GetKeyImpl (string input)
-    {
-        var match = _pattern.Match (input);
-
-        if (!match.Success)
-        {
-            return null;
-        }
-
-        return int.Parse (match.Groups [1].Value) switch
-               {
-                   11 => Key.F1,
-                   12 => Key.F2,
-                   13 => Key.F3,
-                   14 => Key.F4,
-                   15 => Key.F5,
-                   17 => Key.F6,
-                   18 => Key.F7,
-                   19 => Key.F8,
-                   20 => Key.F9,
-                   21 => Key.F10,
-                   23 => Key.F11,
-                   24 => Key.F12,
-                   _ => null
-               };
-    }
-}

From f5a0cf3bdd5fadb6d00f395df35debb4e0f270bc Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 23 Feb 2025 13:21:39 +0000
Subject: [PATCH 189/198] Add IConsoleDriverFacade and add Swallowed log to
 Keys Scenario

---
 .../ConsoleDrivers/V2/ConsoleDriverFacade.cs  |  6 ++---
 .../ConsoleDrivers/V2/IConsoleDriverFacade.cs |  9 +++++++
 .../ConsoleDrivers/V2/IInputProcessor.cs      |  3 +++
 .../ConsoleDrivers/V2/InputProcessor.cs       |  7 ++++-
 Terminal.Gui/ConsoleDrivers/V2/V2.cd          | 23 ++++++++++------
 UICatalog/Scenarios/Keys.cs                   | 27 +++++++++++++++++++
 6 files changed, 63 insertions(+), 12 deletions(-)
 create mode 100644 Terminal.Gui/ConsoleDrivers/V2/IConsoleDriverFacade.cs

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
index 25ee57552d..60b5700c1d 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
@@ -2,7 +2,7 @@
 
 namespace Terminal.Gui;
 
-internal class ConsoleDriverFacade<T> : IConsoleDriver
+internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
 {
     private readonly IInputProcessor _inputProcessor;
     private readonly IConsoleOutput _output;
@@ -12,7 +12,7 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver
 
     /// <summary>The event fired when the terminal is resized.</summary>
     public event EventHandler<SizeChangedEventArgs> SizeChanged;
-
+    public IInputProcessor InputProcessor => _inputProcessor;
     public ConsoleDriverFacade (
         IInputProcessor inputProcessor,
         IOutputBuffer outputBuffer,
@@ -384,4 +384,4 @@ public void Refresh ()
     {
         // No need we will always draw when dirty
     }
-}
+}
\ No newline at end of file
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/IConsoleDriverFacade.cs
new file mode 100644
index 0000000000..19134b532c
--- /dev/null
+++ b/Terminal.Gui/ConsoleDrivers/V2/IConsoleDriverFacade.cs
@@ -0,0 +1,9 @@
+namespace Terminal.Gui;
+
+/// <summary>
+/// Interface for v2 driver abstraction layer
+/// </summary>
+public interface IConsoleDriverFacade
+{
+    public IInputProcessor InputProcessor { get; }
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
index 64fdb0f396..0fc0c572fe 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
@@ -18,6 +18,9 @@ public interface IInputProcessor
     /// </remarks>
     event EventHandler<Key>? KeyUp;
 
+    /// <summary>Event fired when a terminal sequence read from input is not recognized and therefore ignored.</summary>
+    public event EventHandler<string>? AnsiSequenceSwallowed;
+
     /// <summary>Event fired when a mouse event occurs.</summary>
     event EventHandler<MouseEventArgs>? MouseEvent;
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index 2ca6a820cf..add35572f6 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -33,6 +33,9 @@ public abstract class InputProcessor<T> : IInputProcessor
     /// <summary>Event fired when a key is pressed down. This is a precursor to <see cref="KeyUp"/>.</summary>
     public event EventHandler<Key>? KeyDown;
 
+    /// <summary>Event fired when a terminal sequence read from input is not recognized and therefore ignored.</summary>
+    public event EventHandler<string>? AnsiSequenceSwallowed;
+
     /// <summary>
     ///     Called when a key is pressed down. Fires the <see cref="KeyDown"/> event. This is a precursor to
     ///     <see cref="OnKeyUp"/>.
@@ -100,7 +103,9 @@ protected InputProcessor (ConcurrentQueue<T> inputBuffer, IKeyConverter<T> keyCo
         // TODO: For now handle all other escape codes with ignore
         Parser.UnexpectedResponseHandler = str =>
                                            {
-                                               Logging.Logger.LogInformation ($"{nameof (InputProcessor<T>)} ignored unrecognized response '{new string (str.Select (k => k.Item1).ToArray ())}'");
+                                               var cur = new string (str.Select (k => k.Item1).ToArray ());
+                                               Logging.Logger.LogInformation ($"{nameof (InputProcessor<T>)} ignored unrecognized response '{cur}'");
+                                               AnsiSequenceSwallowed?.Invoke (this,cur);
                                                return true;
                                            };
         KeyConverter = keyConverter;
diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index 7443e97449..9fe54f7fc9 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -191,7 +191,7 @@
       </Path>
     </AssociationLine>
     <TypeIdentifier>
-      <HashCode>AQAkEAAAAASAiAAEAgwgAAAABAIAAAAAAAAAAAAAAAA=</HashCode>
+      <HashCode>AQAkEAAAAASAiAAEAgwgAAAABAIAAAAAAAAAAAAEAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\InputProcessor.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -229,7 +229,7 @@
       <Compartment Name="Fields" Collapsed="true" />
     </Compartments>
     <TypeIdentifier>
-      <HashCode>AQMgAAAAAKBAgFEIBBgAQJEAAjkaQiIAGQADKABDggQ=</HashCode>
+      <HashCode>AQMgAAAAAKBAgFEIBBgAAJEAAjkaQiIAGQADKABDAgQ=</HashCode>
       <FileName>ConsoleDrivers\V2\ConsoleDriverFacade.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
@@ -247,7 +247,7 @@
   <Class Name="Terminal.Gui.AnsiResponseParserBase" Collapsed="true">
     <Position X="20.25" Y="9" Width="2" />
     <TypeIdentifier>
-      <HashCode>UAiASAAAEICQALAAQAAAKAAAoAIAAABAAQIAJgAQASU=</HashCode>
+      <HashCode>UAiASAAAEICQALAAQAAAKAAAoAIAAABAAQIAJiAQASQ=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -277,7 +277,7 @@
   <Class Name="Terminal.Gui.StringHeld" Collapsed="true">
     <Position X="21.5" Y="11" Width="1.75" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAAIAACAAAAAAAIAAAAAAAACAAAAAAAgAAAA=</HashCode>
+      <HashCode>AAAAAAAAAAIAACAAAAAAAIBAAAAAAACAAAAAAAgAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\StringHeld.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
@@ -285,7 +285,7 @@
   <Class Name="Terminal.Gui.GenericHeld&lt;T&gt;" Collapsed="true">
     <Position X="19.75" Y="11" Width="1.75" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAgAIAACAAAAAAAIAAAAAAAACAAAAAAAAAAAA=</HashCode>
+      <HashCode>AAAAAAAAgAIAACAAAAAAAIBAAAAAAACAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\GenericHeld.cs</FileName>
     </TypeIdentifier>
     <Lollipop Position="0.2" />
@@ -366,8 +366,8 @@
   <Class Name="Terminal.Gui.AnsiKeyboardParser">
     <Position X="25.5" Y="9.5" Width="1.75" />
     <TypeIdentifier>
-      <HashCode>AAAAAQEAAAAAAAAAAAAAAAAAAAQgAAAAAAABCAAAAAE=</HashCode>
-      <FileName>ConsoleDrivers\AnsiResponseParser\AnsiKeyboardParser.cs</FileName>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAE=</HashCode>
+      <FileName>ConsoleDrivers\AnsiResponseParser\Keyboard\AnsiKeyboardParser.cs</FileName>
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.ToplevelTransitionManager" Collapsed="true">
@@ -416,7 +416,7 @@
   <Interface Name="Terminal.Gui.IHeld">
     <Position X="23.75" Y="6.5" Width="1.75" />
     <TypeIdentifier>
-      <HashCode>AAAAAAAAAAIAACAAAAAAAIAAAAAAAACAAAAAAAAAAAA=</HashCode>
+      <HashCode>AAAAAAAAAAIAACAAAAAAAIBAAAAAAACAAAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\IHeld.cs</FileName>
     </TypeIdentifier>
   </Interface>
@@ -475,6 +475,13 @@
       <FileName>ConsoleDrivers\V2\IToplevelTransitionManager.cs</FileName>
     </TypeIdentifier>
   </Interface>
+  <Interface Name="Terminal.Gui.IConsoleDriverFacade">
+    <Position X="4.5" Y="8.75" Width="1.75" />
+    <TypeIdentifier>
+      <HashCode>AAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\IConsoleDriverFacade.cs</FileName>
+    </TypeIdentifier>
+  </Interface>
   <Enum Name="Terminal.Gui.AnsiResponseParserState">
     <Position X="20.25" Y="7.25" Width="2" />
     <TypeIdentifier>
diff --git a/UICatalog/Scenarios/Keys.cs b/UICatalog/Scenarios/Keys.cs
index f2796a344d..b4988a3c93 100644
--- a/UICatalog/Scenarios/Keys.cs
+++ b/UICatalog/Scenarios/Keys.cs
@@ -12,6 +12,7 @@ public override void Main ()
         Application.Init ();
         ObservableCollection<string> keyDownList = [];
         ObservableCollection<string> keyDownNotHandledList = new ();
+        ObservableCollection<string> swallowedList = new ();
 
         var win = new Window { Title = GetQuitKeyAndName () };
 
@@ -137,6 +138,32 @@ public override void Main ()
         onKeyDownNotHandledListView.ColorScheme = Colors.ColorSchemes ["TopLevel"];
         win.Add (onKeyDownNotHandledListView);
 
+
+        // Swallowed
+        label = new Label
+        {
+            X = Pos.Right (onKeyDownNotHandledListView) + 1,
+            Y = Pos.Top (label),
+            Text = "Swallowed:"
+        };
+        win.Add (label);
+
+        var onSwallowedListView = new ListView
+        {
+            X = Pos.Left (label),
+            Y = Pos.Bottom (label),
+            Width = maxKeyString,
+            Height = Dim.Fill (),
+            Source = new ListWrapper<string> (swallowedList)
+        };
+        onSwallowedListView.ColorScheme = Colors.ColorSchemes ["TopLevel"];
+        win.Add (onSwallowedListView);
+
+        if (Application.Driver is IConsoleDriverFacade fac)
+        {
+            fac.InputProcessor.AnsiSequenceSwallowed += (s, e) => { swallowedList.Add (e.Replace ("\x1b","Esc")); };
+        }
+
         Application.KeyDown += (s, a) => KeyDownPressUp (a, "Down");
         Application.KeyUp += (s, a) => KeyDownPressUp (a, "Up");
 

From 0c70dc2e588230d85c996464e9aea87ff9980bf9 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 23 Feb 2025 14:32:13 +0000
Subject: [PATCH 190/198] update class diagram

---
 Terminal.Gui/ConsoleDrivers/V2/V2.cd | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index 9fe54f7fc9..579aefd565 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -229,9 +229,12 @@
       <Compartment Name="Fields" Collapsed="true" />
     </Compartments>
     <TypeIdentifier>
-      <HashCode>AQMgAAAAAKBAgFEIBBgAAJEAAjkaQiIAGQADKABDAgQ=</HashCode>
+      <HashCode>AQcgAAAAAKBAgFEIBBgAAJEAAjkaQiIAGQADKABDAgQ=</HashCode>
       <FileName>ConsoleDrivers\V2\ConsoleDriverFacade.cs</FileName>
     </TypeIdentifier>
+    <ShowAsAssociation>
+      <Property Name="InputProcessor" />
+    </ShowAsAssociation>
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.AnsiRequestScheduler" Collapsed="true">
@@ -409,7 +412,7 @@
   <Interface Name="Terminal.Gui.IInputProcessor">
     <Position X="14" Y="4.5" Width="1.5" />
     <TypeIdentifier>
-      <HashCode>AAAkAAAAAACAgAAAAAggAAAABAIAAAAAAAAAAAAAAAA=</HashCode>
+      <HashCode>AAAkAAAAAACAgAAAAAggAAAABAIAAAAAAAAAAAAEAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IInputProcessor.cs</FileName>
     </TypeIdentifier>
   </Interface>

From f550e0658a165f44181b61f8e10eb3326d5e466e Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Sun, 23 Feb 2025 15:04:34 +0000
Subject: [PATCH 191/198] Move generate test cases code to see all input values
 in net input processor

---
 .../ConsoleDrivers/V2/NetInputProcessor.cs       | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
index ae93c5d67b..fba1b9b2b6 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
@@ -24,6 +24,12 @@ public class NetInputProcessor : InputProcessor<ConsoleKeyInfo>
     /// <inheritdoc/>
     protected override void Process (ConsoleKeyInfo consoleKeyInfo)
     {
+        // For building test cases
+        if (GenerateTestCasesForKeyPresses)
+        {
+            Logging.Logger.LogTrace (FormatConsoleKeyInfoForTestCase (consoleKeyInfo));
+        }
+
         foreach (Tuple<char, ConsoleKeyInfo> released in Parser.ProcessInput (Tuple.Create (consoleKeyInfo.KeyChar, consoleKeyInfo)))
         {
             ProcessAfterParsing (released.Item2);
@@ -33,12 +39,6 @@ protected override void Process (ConsoleKeyInfo consoleKeyInfo)
     /// <inheritdoc/>
     protected override void ProcessAfterParsing (ConsoleKeyInfo input)
     {
-        // For building test cases
-        if (GenerateTestCasesForKeyPresses)
-        {
-            Logging.Logger.LogTrace (FormatConsoleKeyInfoForTestCase (input));
-        }
-
         Key key = KeyConverter.ToKey (input);
         OnKeyDown (key);
         OnKeyUp (key);
@@ -51,9 +51,9 @@ private static string FormatConsoleKeyInfoForTestCase (ConsoleKeyInfo input)
         string charLiteral = input.KeyChar == '\0' ? @"'\0'" : $"'{input.KeyChar}'";
         string expectedLiteral = $"new Rune('todo')";
 
-        return $"yield return new object[] {{ new ConsoleKeyInfo({charLiteral}, ConsoleKey.{input.Key}, " +
+        return $"new ConsoleKeyInfo({charLiteral}, ConsoleKey.{input.Key}, " +
                $"{input.Modifiers.HasFlag (ConsoleModifiers.Shift).ToString ().ToLower ()}, " +
                $"{input.Modifiers.HasFlag (ConsoleModifiers.Alt).ToString ().ToLower ()}, " +
-               $"{input.Modifiers.HasFlag (ConsoleModifiers.Control).ToString ().ToLower ()}), {expectedLiteral} }};";
+               $"{input.Modifiers.HasFlag (ConsoleModifiers.Control).ToString ().ToLower ()}), {expectedLiteral}";
     }
 }
\ No newline at end of file

From cd1a2271e781c1272a4b21ae4a8b7c61a6fc46d9 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 27 Feb 2025 01:32:23 +0000
Subject: [PATCH 192/198] Better trace logging (call site and class name) and
 extensive logging for collection navigator

---
 .../AnsiResponseParser/AnsiMouseParser.cs     |  2 +-
 .../AnsiResponseParser/AnsiResponseParser.cs  | 10 ++++----
 .../Keyboard/AnsiKeyboardParserPattern.cs     |  2 +-
 .../ConsoleDrivers/V2/InputProcessor.cs       |  4 ++--
 Terminal.Gui/ConsoleDrivers/V2/Logging.cs     | 14 +++++++++++
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    |  2 +-
 .../ConsoleDrivers/V2/MouseInterpreter.cs     |  2 +-
 .../ConsoleDrivers/V2/NetInputProcessor.cs    |  2 +-
 Terminal.Gui/Text/CollectionNavigatorBase.cs  | 23 +++++++++++++++++--
 UICatalog/UICatalog.cs                        |  4 +++-
 10 files changed, 50 insertions(+), 15 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs
index 400a541261..0b2980538e 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs
@@ -55,7 +55,7 @@ public bool IsMouse (string cur)
             };
 
 
-            Logging.Logger.LogTrace ($"{nameof(AnsiMouseParser)} handled as {input} mouse {m.Flags} at {m.Position}");
+            Logging.Trace($"{nameof(AnsiMouseParser)} handled as {input} mouse {m.Flags} at {m.Position}");
             return m;
         }
 
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
index f2839b3745..29ddfb151c 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
@@ -258,7 +258,7 @@ protected void TryLastMinuteSequences ()
                 {
                     _heldContent.ClearHeld ();
 
-                    Logging.Logger.LogTrace ($"AnsiResponseParser last minute swallowed '{cur}'");
+                    Logging.Trace($"AnsiResponseParser last minute swallowed '{cur}'");
                 }
             }
         }
@@ -339,7 +339,7 @@ protected bool ShouldReleaseHeldContent ()
                 {
                     _heldContent.ClearHeld ();
 
-                    Logging.Logger.LogTrace ($"AnsiResponseParser swallowed '{cur}'");
+                    Logging.Trace($"AnsiResponseParser swallowed '{cur}'");
 
                     // Do not send back to input stream
                     return false;
@@ -400,7 +400,7 @@ private bool MatchResponse (string cur, List<AnsiResponseExpectation> collection
 
         if (matchingResponse?.Response != null)
         {
-            Logging.Logger.LogTrace ($"AnsiResponseParser processed '{cur}'");
+            Logging.Trace($"AnsiResponseParser processed '{cur}'");
 
             if (invokeCallback)
             {
@@ -500,7 +500,7 @@ private void AppendOutput (List<Tuple<char, T>> output, object c)
     {
         Tuple<char, T> tuple = (Tuple<char, T>)c;
 
-        Logging.Logger.LogTrace ($"AnsiResponseParser releasing '{tuple.Item1}'");
+        Logging.Trace($"AnsiResponseParser releasing '{tuple.Item1}'");
         output.Add (tuple);
     }
 
@@ -578,7 +578,7 @@ public string ProcessInput (string input)
 
     private void AppendOutput (StringBuilder output, char c)
     {
-        Logging.Logger.LogTrace ($"AnsiResponseParser releasing '{c}'");
+        Logging.Trace($"AnsiResponseParser releasing '{c}'");
         output.Append (c);
     }
 
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs
index e02a08c136..d653db3fc8 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs
@@ -29,7 +29,7 @@ public AnsiKeyboardParserPattern ()
     public Key? GetKey (string input)
     {
         var key = GetKeyImpl (input);
-        Logging.Logger.LogTrace ($"{nameof (AnsiKeyboardParser)} interpreted {input} as {key} using {_name}");
+        Logging.Trace($"{nameof (AnsiKeyboardParser)} interpreted {input} as {key} using {_name}");
 
         return key;
     }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index add35572f6..ac96ae334c 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -43,7 +43,7 @@ public abstract class InputProcessor<T> : IInputProcessor
     /// <param name="a"></param>
     public void OnKeyDown (Key a)
     {
-        Logging.Logger.LogTrace ($"{nameof(InputProcessor<T>)} raised {a}");
+        Logging.Trace($"{nameof(InputProcessor<T>)} raised {a}");
         KeyDown?.Invoke (this, a);
     }
 
@@ -74,7 +74,7 @@ public void OnMouseEvent (MouseEventArgs a)
 
         foreach (var e in _mouseInterpreter.Process (a))
         {
-            Logging.Logger.LogTrace ($"Mouse Interpreter raising {e.Flags}");
+            Logging.Trace($"Mouse Interpreter raising {e.Flags}");
             // Pass on
             MouseEvent?.Invoke (this, e);
         }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/Logging.cs b/Terminal.Gui/ConsoleDrivers/V2/Logging.cs
index 68bada3fe3..dc74692bef 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/Logging.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/Logging.cs
@@ -1,4 +1,5 @@
 using System.Diagnostics.Metrics;
+using System.Runtime.CompilerServices;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging.Abstractions;
 
@@ -49,4 +50,17 @@ public static class Logging
     /// </summary>
     public static readonly Histogram<int> DrainInputStream = Logging.Meter.CreateHistogram<int> ("Drain Input (ms)");
 
+    /// <summary>
+    /// Logs a trace message including the 
+    /// </summary>
+    /// <param name="message"></param>
+    /// <param name="caller"></param>
+    /// <param name="filePath"></param>
+    public static void Trace (string message,
+                                 [CallerMemberName] string caller = "",
+                                 [CallerFilePath] string filePath = "")
+    {
+        string className = Path.GetFileNameWithoutExtension (filePath);
+        Logger.LogTrace ($"[{className}] [{caller}] {message}");
+    }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index f2a5f65c7c..eb289f9c2d 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -180,7 +180,7 @@ private bool AnySubviewsNeedDrawn (View v)
     {
         if (v.NeedsDraw || v.NeedsLayout)
         {
-            Logging.Logger.LogTrace ( $"{v.GetType ().Name} triggered redraw (NeedsDraw={v.NeedsDraw} NeedsLayout={v.NeedsLayout}) ");
+            Logging.Trace( $"{v.GetType ().Name} triggered redraw (NeedsDraw={v.NeedsDraw} NeedsLayout={v.NeedsLayout}) ");
             return true;
         }
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
index 5f896a729e..6211c68268 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
@@ -63,7 +63,7 @@ private MouseEventArgs RaiseClick (int button, int numberOfClicks, MouseEventArg
             View = mouseEventArgs.View,
             Position = mouseEventArgs.Position
         };
-        Logging.Logger.LogTrace ($"Raising click event:{newClick.Flags} at screen {newClick.ScreenPosition}");
+        Logging.Trace($"Raising click event:{newClick.Flags} at screen {newClick.ScreenPosition}");
         return newClick;
     }
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
index fba1b9b2b6..9f6426a75f 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
@@ -27,7 +27,7 @@ protected override void Process (ConsoleKeyInfo consoleKeyInfo)
         // For building test cases
         if (GenerateTestCasesForKeyPresses)
         {
-            Logging.Logger.LogTrace (FormatConsoleKeyInfoForTestCase (consoleKeyInfo));
+            Logging.Trace(FormatConsoleKeyInfoForTestCase (consoleKeyInfo));
         }
 
         foreach (Tuple<char, ConsoleKeyInfo> released in Parser.ProcessInput (Tuple.Create (consoleKeyInfo.KeyChar, consoleKeyInfo)))
diff --git a/Terminal.Gui/Text/CollectionNavigatorBase.cs b/Terminal.Gui/Text/CollectionNavigatorBase.cs
index 4caa219cd9..cdf5a11706 100644
--- a/Terminal.Gui/Text/CollectionNavigatorBase.cs
+++ b/Terminal.Gui/Text/CollectionNavigatorBase.cs
@@ -1,4 +1,6 @@
-namespace Terminal.Gui;
+using Microsoft.Extensions.Logging;
+
+namespace Terminal.Gui;
 
 /// <summary>
 ///     Navigates a collection of items using keystrokes. The keystrokes are used to build a search string. The
@@ -57,18 +59,23 @@ public int GetNextMatchingItem (int currentIndex, char keyStruck)
             // but if we find none then we must fallback on cycling
             // d instead and discard the candidate state
             var candidateState = "";
+            var elapsedTime = DateTime.Now - _lastKeystroke;
+
+            Logging.Trace($"CollectionNavigator began processing '{keyStruck}', it has been {elapsedTime} since last keystroke");
 
             // is it a second or third (etc) keystroke within a short time
-            if (SearchString.Length > 0 && DateTime.Now - _lastKeystroke < TimeSpan.FromMilliseconds (TypingDelay))
+            if (SearchString.Length > 0 && elapsedTime < TimeSpan.FromMilliseconds (TypingDelay))
             {
                 // "dd" is a candidate
                 candidateState = SearchString + keyStruck;
+                Logging.Trace($"Appending, search is now for '{SearchString}'");
             }
             else
             {
                 // its a fresh keystroke after some time
                 // or its first ever key press
                 SearchString = new string (keyStruck, 1);
+                Logging.Trace($"It has been too long since last key press so beginning new search for '{SearchString}'");
             }
 
             int idxCandidate = GetNextMatchingItem (
@@ -79,12 +86,14 @@ public int GetNextMatchingItem (int currentIndex, char keyStruck)
                                                     candidateState.Length > 1
                                                    );
 
+            Logging.Trace($"CollectionNavigator searching (preferring minimum movement) matched:{idxCandidate}");
             if (idxCandidate != -1)
             {
                 // found "dd" so candidate search string is accepted
                 _lastKeystroke = DateTime.Now;
                 SearchString = candidateState;
 
+                Logging.Trace($"Found collection item that matched search:{idxCandidate}");
                 return idxCandidate;
             }
 
@@ -93,10 +102,13 @@ public int GetNextMatchingItem (int currentIndex, char keyStruck)
             _lastKeystroke = DateTime.Now;
             idxCandidate = GetNextMatchingItem (currentIndex, candidateState);
 
+            Logging.Trace($"CollectionNavigator searching (any match) matched:{idxCandidate}");
+
             // if a match wasn't found, the user typed a 'wrong' key in their search ("can" + 'z'
             // instead of "can" + 'd').
             if (SearchString.Length > 1 && idxCandidate == -1)
             {
+                Logging.Trace("CollectionNavigator ignored key and returned existing index");
                 // ignore it since we're still within the typing delay
                 // don't add it to SearchString either
                 return currentIndex;
@@ -105,6 +117,8 @@ public int GetNextMatchingItem (int currentIndex, char keyStruck)
             // if no changes to current state manifested
             if (idxCandidate == currentIndex || idxCandidate == -1)
             {
+                Logging.Trace("CollectionNavigator found no changes to current index, so clearing search");
+
                 // clear history and treat as a fresh letter
                 ClearSearchString ();
 
@@ -112,13 +126,18 @@ public int GetNextMatchingItem (int currentIndex, char keyStruck)
                 SearchString = new string (keyStruck, 1);
                 idxCandidate = GetNextMatchingItem (currentIndex, SearchString);
 
+                Logging.Trace($"CollectionNavigator new SearchString {SearchString} matched index:{idxCandidate}" );
+
                 return idxCandidate == -1 ? currentIndex : idxCandidate;
             }
 
+            Logging.Trace($"CollectionNavigator final answer was:{idxCandidate}" );
             // Found another "d" or just leave index as it was
             return idxCandidate;
         }
 
+        Logging.Trace("CollectionNavigator found key press was not actionable so clearing search and returning -1");
+
         // clear state because keypress was a control char
         ClearSearchString ();
 
diff --git a/UICatalog/UICatalog.cs b/UICatalog/UICatalog.cs
index 7aa687d9a1..5bdf68f8ba 100644
--- a/UICatalog/UICatalog.cs
+++ b/UICatalog/UICatalog.cs
@@ -219,7 +219,9 @@ private static ILogger CreateLogger ()
         // Configure Serilog to write logs to a file
         Log.Logger = new LoggerConfiguration ()
                      .MinimumLevel.Verbose () // Verbose includes Trace and Debug
-                     .WriteTo.File ("logs/logfile.txt", rollingInterval: RollingInterval.Day)
+                     .Enrich.FromLogContext () // Enables dynamic enrichment
+                     .WriteTo.File ("logs/logfile.txt", rollingInterval: RollingInterval.Day,
+                                    outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
                      .CreateLogger ();
 
         // Create a logger factory compatible with Microsoft.Extensions.Logging

From 28e8c32c23a5732d00ea4c207a86036dcb1d7a84 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 27 Feb 2025 01:38:04 +0000
Subject: [PATCH 193/198] Fixed log message being out by 1 letter

---
 Terminal.Gui/Text/CollectionNavigatorBase.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Terminal.Gui/Text/CollectionNavigatorBase.cs b/Terminal.Gui/Text/CollectionNavigatorBase.cs
index cdf5a11706..3ffe411d54 100644
--- a/Terminal.Gui/Text/CollectionNavigatorBase.cs
+++ b/Terminal.Gui/Text/CollectionNavigatorBase.cs
@@ -68,14 +68,14 @@ public int GetNextMatchingItem (int currentIndex, char keyStruck)
             {
                 // "dd" is a candidate
                 candidateState = SearchString + keyStruck;
-                Logging.Trace($"Appending, search is now for '{SearchString}'");
+                Logging.Trace($"Appending, search is now for '{candidateState}'");
             }
             else
             {
                 // its a fresh keystroke after some time
                 // or its first ever key press
                 SearchString = new string (keyStruck, 1);
-                Logging.Trace($"It has been too long since last key press so beginning new search for '{SearchString}'");
+                Logging.Trace($"It has been too long since last key press so beginning new search");
             }
 
             int idxCandidate = GetNextMatchingItem (

From ea3118bfce0db5edfb66207fa59b49cad0e681d2 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 27 Feb 2025 19:44:24 +0000
Subject: [PATCH 194/198] xmldoc comments

---
 .../Keyboard/AnsiKeyboardParserPattern.cs     | 25 +++++++++++++++++--
 .../Keyboard/CsiKeyPattern.cs                 |  9 +++++++
 .../AnsiResponseParser/Keyboard/Ss3Pattern.cs | 10 ++++++++
 .../ConsoleDrivers/V2/IConsoleDriverFacade.cs |  5 ++++
 .../ConsoleDrivers/V2/InputProcessor.cs       |  9 ++++++-
 .../ConsoleDrivers/V2/NetKeyConverter.cs      |  8 +++++-
 .../ConsoleDrivers/V2/WindowsKeyConverter.cs  |  7 ++++++
 7 files changed, 69 insertions(+), 4 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs
index d653db3fc8..db39de3052 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs
@@ -19,13 +19,28 @@ public abstract class AnsiKeyboardParserPattern
     /// </summary>
     public bool IsLastMinute { get; set; }
 
+    /// <summary>
+    /// Returns <see langword="true"/> if <paramref name="input"/> is one
+    /// of the terminal sequences recognised by this class.
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
     public abstract bool IsMatch (string input);
-    private string _name;
+    private readonly string _name;
 
-    public AnsiKeyboardParserPattern ()
+    /// <summary>
+    /// Creates a new instance of the class.
+    /// </summary>
+    protected AnsiKeyboardParserPattern ()
     {
         _name = GetType ().Name;
     }
+
+    /// <summary>
+    /// Returns the <see cref="Key"/> described by the escape sequence.
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
     public Key? GetKey (string input)
     {
         var key = GetKeyImpl (input);
@@ -34,5 +49,11 @@ public AnsiKeyboardParserPattern ()
         return key;
     }
 
+    /// <summary>
+    /// When overriden in a derived class, returns the <see cref="Key"/>
+    /// that matches the input ansi escape sequence.
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
     protected abstract Key? GetKeyImpl (string input);
 }
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/CsiKeyPattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/CsiKeyPattern.cs
index b7935f8a57..c7bd1ca762 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/CsiKeyPattern.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/CsiKeyPattern.cs
@@ -3,6 +3,11 @@
 
 namespace Terminal.Gui;
 
+/// <summary>
+/// Detects ansi escape sequences in strings that have been read from
+/// the terminal (see <see cref="IAnsiResponseParser"/>). This pattern
+/// handles keys that begin <c>Esc[</c> e.g. <c>Esc[A</c> - cursor up
+/// </summary>
 public class CsiKeyPattern : AnsiKeyboardParserPattern
 {
     private Dictionary<string, Key> _terminators = new Dictionary<string, Key> ()
@@ -36,8 +41,12 @@ public class CsiKeyPattern : AnsiKeyboardParserPattern
 
     private readonly Regex _pattern;
 
+    /// <inheritdoc/>
     public override bool IsMatch (string input) => _pattern.IsMatch (input);
 
+    /// <summary>
+    /// Creates a new instance of the <see cref="CsiKeyPattern"/> class.
+    /// </summary>
     public CsiKeyPattern ()
     {
         var terms = new string (_terminators.Select (k => k.Key [0]).Where (k=> !char.IsDigit (k)).ToArray ());
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/Ss3Pattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/Ss3Pattern.cs
index 49eeb3365e..b2b946c7b3 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/Ss3Pattern.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/Ss3Pattern.cs
@@ -3,12 +3,22 @@
 
 namespace Terminal.Gui;
 
+/// <summary>
+/// Parser for SS3 terminal escape sequences. These describe specific keys e.g.
+/// <c>EscOP</c> is F1.
+/// </summary>
 public class Ss3Pattern : AnsiKeyboardParserPattern
 {
     private static readonly Regex _pattern = new (@"^\u001bO([PQRStDCAB])$");
 
+    /// <inheritdoc/>
     public override bool IsMatch (string input) => _pattern.IsMatch (input);
 
+    /// <summary>
+    /// Returns the ss3 key that corresponds to the provided input escape sequence
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
     protected override Key? GetKeyImpl (string input)
     {
         var match = _pattern.Match (input);
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/IConsoleDriverFacade.cs
index 19134b532c..de71a433ce 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IConsoleDriverFacade.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IConsoleDriverFacade.cs
@@ -5,5 +5,10 @@
 /// </summary>
 public interface IConsoleDriverFacade
 {
+    /// <summary>
+    /// Class responsible for processing native driver input objects
+    /// e.g. <see cref="ConsoleKeyInfo"/> into <see cref="Key"/> events
+    /// and detecting and processing ansi escape sequences.
+    /// </summary>
     public IInputProcessor InputProcessor { get; }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index ac96ae334c..7ebb9fd1c6 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -18,6 +18,11 @@ public abstract class InputProcessor<T> : IInputProcessor
 
     internal AnsiResponseParser<T> Parser { get; } = new ();
 
+    /// <summary>
+    /// Class responsible for translating the driver specific native input class <typeparamref name="T"/> e.g.
+    /// <see cref="ConsoleKeyInfo"/> into the Terminal.Gui <see cref="Key"/> class (used for all
+    /// internal library representations of Keys).
+    /// </summary>
     public IKeyConverter<T> KeyConverter { get; }
 
     /// <summary>
@@ -85,7 +90,9 @@ public void OnMouseEvent (MouseEventArgs a)
     /// parser events and setting <see cref="InputBuffer"/> to
     /// the provided thread safe input collection.
     /// </summary>
-    /// <param name="inputBuffer"></param>
+    /// <param name="inputBuffer">The collection that will be populated with new input (see <see cref="IConsoleInput{T}"/>)</param>
+    /// <param name="keyConverter">Key converter for translating driver specific
+    /// <typeparamref name="T"/> class into Terminal.Gui <see cref="Key"/>.</param>
     protected InputProcessor (ConcurrentQueue<T> inputBuffer, IKeyConverter<T> keyConverter)
     {
         InputBuffer = inputBuffer;
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetKeyConverter.cs b/Terminal.Gui/ConsoleDrivers/V2/NetKeyConverter.cs
index 2df67e7410..efac409ebb 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetKeyConverter.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetKeyConverter.cs
@@ -1,6 +1,12 @@
 namespace Terminal.Gui;
 
-public class NetKeyConverter : IKeyConverter<ConsoleKeyInfo>
+/// <summary>
+/// <see cref="IKeyConverter{T}"/> capable of converting the
+/// dotnet <see cref="ConsoleKeyInfo"/> class into Terminal.Gui
+/// shared <see cref="Key"/> representation (used by <see cref="View"/>
+/// etc).
+/// </summary>
+internal class NetKeyConverter : IKeyConverter<ConsoleKeyInfo>
 {
     /// <inheritdoc />
     public Key ToKey (ConsoleKeyInfo input)
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsKeyConverter.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsKeyConverter.cs
index 0edf381a9c..535c6d417f 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsKeyConverter.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsKeyConverter.cs
@@ -3,6 +3,13 @@
 
 namespace Terminal.Gui;
 
+
+/// <summary>
+/// <see cref="IKeyConverter{T}"/> capable of converting the
+/// windows native <see cref="WindowsConsole.InputRecord"/> class
+/// into Terminal.Gui shared <see cref="Key"/> representation
+/// (used by <see cref="View"/> etc).
+/// </summary>
 internal class WindowsKeyConverter : IKeyConverter<WindowsConsole.InputRecord>
 {
     /// <inheritdoc />

From 8e3a521983080b62f050c30e6cfdc4da051ef2c7 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 27 Feb 2025 19:46:16 +0000
Subject: [PATCH 195/198] Swap Action for EventHandler

---
 .../ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
index 29ddfb151c..0b3ee07e43 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
@@ -22,7 +22,7 @@ internal abstract class AnsiResponseParserBase : IAnsiResponseParser
     /// <summary>
     ///     Event raised when keyboard event is detected (e.g. cursors) - requires setting <see cref="HandleKeyboard"/>
     /// </summary>
-    public event Action<object, Key>? Keyboard;
+    public event EventHandler<Key>? Keyboard;
 
     /// <summary>
     ///     True to explicitly handle mouse escape sequences by passing them to <see cref="Mouse"/> event.

From 52caf2988d217b11269ef2fd88a6dd147655130d Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 27 Feb 2025 19:48:09 +0000
Subject: [PATCH 196/198] Run ReSharper code cleanup

---
 .../AnsiResponseParser/AnsiMouseParser.cs     |  6 +--
 .../AnsiResponseParser/AnsiResponseParser.cs  | 27 +++++++------
 .../AnsiResponseParser/GenericHeld.cs         |  2 +-
 .../Keyboard/AnsiKeyboardParser.cs            | 20 +++++-----
 .../Keyboard/AnsiKeyboardParserPattern.cs     | 40 +++++++++----------
 .../Keyboard/CsiKeyPattern.cs                 | 33 +++++++--------
 .../Keyboard/EscAsAltPattern.cs               |  9 ++---
 .../AnsiResponseParser/Keyboard/Ss3Pattern.cs | 10 ++---
 .../AnsiResponseParser/StringHeld.cs          |  2 +-
 9 files changed, 70 insertions(+), 79 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs
index 0b2980538e..5ae29060bf 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiMouseParser.cs
@@ -1,7 +1,5 @@
 #nullable enable
-using System.Runtime.ConstrainedExecution;
 using System.Text.RegularExpressions;
-using Microsoft.Extensions.Logging;
 
 namespace Terminal.Gui;
 
@@ -48,14 +46,14 @@ public bool IsMouse (string cur)
             int y = int.Parse (match.Groups [3].Value) - 1;
             char terminator = match.Groups [4].Value.Single ();
 
-            var m = new MouseEventArgs()
+            var m = new MouseEventArgs
             {
                 Position = new (x, y),
                 Flags = GetFlags (buttonCode, terminator)
             };
 
+            Logging.Trace ($"{nameof (AnsiMouseParser)} handled as {input} mouse {m.Flags} at {m.Position}");
 
-            Logging.Trace($"{nameof(AnsiMouseParser)} handled as {input} mouse {m.Flags} at {m.Position}");
             return m;
         }
 
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
index 0b3ee07e43..93d2bbeb9a 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/AnsiResponseParser.cs
@@ -1,6 +1,5 @@
 #nullable enable
 
-using System.Runtime.ConstrainedExecution;
 using Microsoft.Extensions.Logging;
 
 namespace Terminal.Gui;
@@ -86,6 +85,7 @@ protected set
                                                                   'l', 'm', 'n',
                                                                   'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
                                                               ]);
+
     protected AnsiResponseParserBase (IHeld heldContent) { _heldContent = heldContent; }
 
     protected void ResetState ()
@@ -213,6 +213,7 @@ int inputLength
     private void ReleaseHeld (Action<object> appendOutput, AnsiResponseParserState newState = AnsiResponseParserState.Normal)
     {
         TryLastMinuteSequences ();
+
         foreach (object o in _heldContent.HeldToObjects ())
         {
             appendOutput (o);
@@ -223,10 +224,10 @@ private void ReleaseHeld (Action<object> appendOutput, AnsiResponseParserState n
     }
 
     /// <summary>
-    /// Checks current held chars against any sequences that have
-    /// conflicts with longer sequences e.g. Esc as Alt sequences
-    /// which can conflict if resolved earlier e.g. with EscOP ss3
-    /// sequences.
+    ///     Checks current held chars against any sequences that have
+    ///     conflicts with longer sequences e.g. Esc as Alt sequences
+    ///     which can conflict if resolved earlier e.g. with EscOP ss3
+    ///     sequences.
     /// </summary>
     protected void TryLastMinuteSequences ()
     {
@@ -236,7 +237,7 @@ protected void TryLastMinuteSequences ()
 
             if (HandleKeyboard)
             {
-                var pattern = _keyboardParser.IsKeyboard (cur, true);
+                AnsiKeyboardParserPattern? pattern = _keyboardParser.IsKeyboard (cur, true);
 
                 if (pattern != null)
                 {
@@ -258,7 +259,7 @@ protected void TryLastMinuteSequences ()
                 {
                     _heldContent.ClearHeld ();
 
-                    Logging.Trace($"AnsiResponseParser last minute swallowed '{cur}'");
+                    Logging.Trace ($"AnsiResponseParser last minute swallowed '{cur}'");
                 }
             }
         }
@@ -281,13 +282,13 @@ protected bool ShouldReleaseHeldContent ()
 
             if (HandleKeyboard)
             {
-                var pattern = _keyboardParser.IsKeyboard (cur);
+                AnsiKeyboardParserPattern? pattern = _keyboardParser.IsKeyboard (cur);
 
                 if (pattern != null)
                 {
-
                     RaiseKeyboardEvent (pattern, cur);
                     ResetState ();
+
                     return false;
                 }
             }
@@ -339,7 +340,7 @@ protected bool ShouldReleaseHeldContent ()
                 {
                     _heldContent.ClearHeld ();
 
-                    Logging.Trace($"AnsiResponseParser swallowed '{cur}'");
+                    Logging.Trace ($"AnsiResponseParser swallowed '{cur}'");
 
                     // Do not send back to input stream
                     return false;
@@ -400,7 +401,7 @@ private bool MatchResponse (string cur, List<AnsiResponseExpectation> collection
 
         if (matchingResponse?.Response != null)
         {
-            Logging.Trace($"AnsiResponseParser processed '{cur}'");
+            Logging.Trace ($"AnsiResponseParser processed '{cur}'");
 
             if (invokeCallback)
             {
@@ -500,7 +501,7 @@ private void AppendOutput (List<Tuple<char, T>> output, object c)
     {
         Tuple<char, T> tuple = (Tuple<char, T>)c;
 
-        Logging.Trace($"AnsiResponseParser releasing '{tuple.Item1}'");
+        Logging.Trace ($"AnsiResponseParser releasing '{tuple.Item1}'");
         output.Add (tuple);
     }
 
@@ -578,7 +579,7 @@ public string ProcessInput (string input)
 
     private void AppendOutput (StringBuilder output, char c)
     {
-        Logging.Trace($"AnsiResponseParser releasing '{c}'");
+        Logging.Trace ($"AnsiResponseParser releasing '{c}'");
         output.Append (c);
     }
 
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/GenericHeld.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/GenericHeld.cs
index ec54b0ce55..8a5955da40 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/GenericHeld.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/GenericHeld.cs
@@ -17,6 +17,6 @@ internal class GenericHeld<T> : IHeld
 
     public void AddToHeld (object o) { held.Add ((Tuple<char, T>)o); }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public int Length => held.Count;
 }
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParser.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParser.cs
index 0db3ed38a6..c0da8e023a 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParser.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParser.cs
@@ -1,29 +1,27 @@
 #nullable enable
-using Microsoft.Extensions.Logging;
-
 namespace Terminal.Gui;
 
 /// <summary>
-/// Parses ANSI escape sequence strings that describe keyboard activity into <see cref="Key"/>.
+///     Parses ANSI escape sequence strings that describe keyboard activity into <see cref="Key"/>.
 /// </summary>
 public class AnsiKeyboardParser
 {
     private readonly List<AnsiKeyboardParserPattern> _patterns = new ()
     {
-        new Ss3Pattern(),
-        new CsiKeyPattern(),
-        new EscAsAltPattern(){IsLastMinute=true}
+        new Ss3Pattern (),
+        new CsiKeyPattern (),
+        new EscAsAltPattern { IsLastMinute = true }
     };
-    
+
     /// <summary>
-    /// Looks for any pattern that matches the <paramref name="input"/> and returns
-    /// the matching pattern or <see langword="null"/> if no matches.
+    ///     Looks for any pattern that matches the <paramref name="input"/> and returns
+    ///     the matching pattern or <see langword="null"/> if no matches.
     /// </summary>
     /// <param name="input"></param>
     /// <param name="isLastMinute"></param>
     /// <returns></returns>
     public AnsiKeyboardParserPattern? IsKeyboard (string input, bool isLastMinute = false)
     {
-        return _patterns.FirstOrDefault(pattern => pattern.IsLastMinute == isLastMinute && pattern.IsMatch (input));
+        return _patterns.FirstOrDefault (pattern => pattern.IsLastMinute == isLastMinute && pattern.IsMatch (input));
     }
-}
\ No newline at end of file
+}
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs
index db39de3052..52b6526853 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/AnsiKeyboardParserPattern.cs
@@ -1,57 +1,53 @@
 #nullable enable
-using Microsoft.Extensions.Logging;
-
 namespace Terminal.Gui;
 
 /// <summary>
-/// Base class for ANSI keyboard parsing patterns.
+///     Base class for ANSI keyboard parsing patterns.
 /// </summary>
 public abstract class AnsiKeyboardParserPattern
 {
     /// <summary>
-    /// Does this pattern dangerously overlap with other sequences
-    /// such that it should only be applied at the lsat second after
-    /// all other sequences have been tried.
-    /// <remarks>
-    /// When <see langword="true"/> this pattern will only be used
-    /// at <see cref="AnsiResponseParser.Release"/> time.
-    /// </remarks>
+    ///     Does this pattern dangerously overlap with other sequences
+    ///     such that it should only be applied at the lsat second after
+    ///     all other sequences have been tried.
+    ///     <remarks>
+    ///         When <see langword="true"/> this pattern will only be used
+    ///         at <see cref="AnsiResponseParser.Release"/> time.
+    ///     </remarks>
     /// </summary>
     public bool IsLastMinute { get; set; }
 
     /// <summary>
-    /// Returns <see langword="true"/> if <paramref name="input"/> is one
-    /// of the terminal sequences recognised by this class.
+    ///     Returns <see langword="true"/> if <paramref name="input"/> is one
+    ///     of the terminal sequences recognised by this class.
     /// </summary>
     /// <param name="input"></param>
     /// <returns></returns>
     public abstract bool IsMatch (string input);
+
     private readonly string _name;
 
     /// <summary>
-    /// Creates a new instance of the class.
+    ///     Creates a new instance of the class.
     /// </summary>
-    protected AnsiKeyboardParserPattern ()
-    {
-        _name = GetType ().Name;
-    }
+    protected AnsiKeyboardParserPattern () { _name = GetType ().Name; }
 
     /// <summary>
-    /// Returns the <see cref="Key"/> described by the escape sequence.
+    ///     Returns the <see cref="Key"/> described by the escape sequence.
     /// </summary>
     /// <param name="input"></param>
     /// <returns></returns>
     public Key? GetKey (string input)
     {
-        var key = GetKeyImpl (input);
-        Logging.Trace($"{nameof (AnsiKeyboardParser)} interpreted {input} as {key} using {_name}");
+        Key? key = GetKeyImpl (input);
+        Logging.Trace ($"{nameof (AnsiKeyboardParser)} interpreted {input} as {key} using {_name}");
 
         return key;
     }
 
     /// <summary>
-    /// When overriden in a derived class, returns the <see cref="Key"/>
-    /// that matches the input ansi escape sequence.
+    ///     When overriden in a derived class, returns the <see cref="Key"/>
+    ///     that matches the input ansi escape sequence.
     /// </summary>
     /// <param name="input"></param>
     /// <returns></returns>
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/CsiKeyPattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/CsiKeyPattern.cs
index c7bd1ca762..9c442e5e21 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/CsiKeyPattern.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/CsiKeyPattern.cs
@@ -4,22 +4,22 @@
 namespace Terminal.Gui;
 
 /// <summary>
-/// Detects ansi escape sequences in strings that have been read from
-/// the terminal (see <see cref="IAnsiResponseParser"/>). This pattern
-/// handles keys that begin <c>Esc[</c> e.g. <c>Esc[A</c> - cursor up
+///     Detects ansi escape sequences in strings that have been read from
+///     the terminal (see <see cref="IAnsiResponseParser"/>). This pattern
+///     handles keys that begin <c>Esc[</c> e.g. <c>Esc[A</c> - cursor up
 /// </summary>
 public class CsiKeyPattern : AnsiKeyboardParserPattern
 {
-    private Dictionary<string, Key> _terminators = new Dictionary<string, Key> ()
+    private readonly Dictionary<string, Key> _terminators = new()
     {
         { "A", Key.CursorUp },
         { "B", Key.CursorDown },
         { "C", Key.CursorRight },
         { "D", Key.CursorLeft },
-        { "H", Key.Home },       // Home (older variant)
-        { "F", Key.End },        // End (older variant)
-        { "1~", Key.Home },      // Home (modern variant)
-        { "4~", Key.End },       // End (modern variant)
+        { "H", Key.Home }, // Home (older variant)
+        { "F", Key.End }, // End (older variant)
+        { "1~", Key.Home }, // Home (modern variant)
+        { "4~", Key.End }, // End (modern variant)
         { "5~", Key.PageUp },
         { "6~", Key.PageDown },
         { "2~", Key.InsertChar },
@@ -38,35 +38,35 @@ public class CsiKeyPattern : AnsiKeyboardParserPattern
         { "24~", Key.F12 }
     };
 
-
     private readonly Regex _pattern;
 
     /// <inheritdoc/>
-    public override bool IsMatch (string input) => _pattern.IsMatch (input);
+    public override bool IsMatch (string input) { return _pattern.IsMatch (input); }
 
     /// <summary>
-    /// Creates a new instance of the <see cref="CsiKeyPattern"/> class.
+    ///     Creates a new instance of the <see cref="CsiKeyPattern"/> class.
     /// </summary>
     public CsiKeyPattern ()
     {
-        var terms = new string (_terminators.Select (k => k.Key [0]).Where (k=> !char.IsDigit (k)).ToArray ());
+        var terms = new string (_terminators.Select (k => k.Key [0]).Where (k => !char.IsDigit (k)).ToArray ());
         _pattern = new (@$"^\u001b\[(1;(\d+))?([{terms}]|\d+~)$");
     }
+
     protected override Key? GetKeyImpl (string input)
     {
-        var match = _pattern.Match (input);
+        Match match = _pattern.Match (input);
 
         if (!match.Success)
         {
             return null;
         }
 
-        var terminator = match.Groups [3].Value;
+        string terminator = match.Groups [3].Value;
         string modifierGroup = match.Groups [2].Value;
 
-        var key = _terminators.GetValueOrDefault (terminator);
+        Key? key = _terminators.GetValueOrDefault (terminator);
 
-        if (key != null && int.TryParse (modifierGroup, out var modifier))
+        if (key != null && int.TryParse (modifierGroup, out int modifier))
         {
             key = modifier switch
                   {
@@ -80,6 +80,7 @@ public CsiKeyPattern ()
                       _ => key
                   };
         }
+
         return key;
     }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/EscAsAltPattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/EscAsAltPattern.cs
index e3f566627e..05fc5ebe48 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/EscAsAltPattern.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/EscAsAltPattern.cs
@@ -5,18 +5,15 @@ namespace Terminal.Gui;
 
 internal class EscAsAltPattern : AnsiKeyboardParserPattern
 {
-    public EscAsAltPattern ()
-    {
-        IsLastMinute=true;
-    }
+    public EscAsAltPattern () { IsLastMinute = true; }
 
     private static readonly Regex _pattern = new (@"^\u001b([a-zA-Z0-9_])$");
 
-    public override bool IsMatch (string input) => _pattern.IsMatch (input);
+    public override bool IsMatch (string input) { return _pattern.IsMatch (input); }
 
     protected override Key? GetKeyImpl (string input)
     {
-        var match = _pattern.Match (input);
+        Match match = _pattern.Match (input);
 
         if (!match.Success)
         {
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/Ss3Pattern.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/Ss3Pattern.cs
index b2b946c7b3..2e5847e0d1 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/Ss3Pattern.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/Keyboard/Ss3Pattern.cs
@@ -4,24 +4,24 @@
 namespace Terminal.Gui;
 
 /// <summary>
-/// Parser for SS3 terminal escape sequences. These describe specific keys e.g.
-/// <c>EscOP</c> is F1.
+///     Parser for SS3 terminal escape sequences. These describe specific keys e.g.
+///     <c>EscOP</c> is F1.
 /// </summary>
 public class Ss3Pattern : AnsiKeyboardParserPattern
 {
     private static readonly Regex _pattern = new (@"^\u001bO([PQRStDCAB])$");
 
     /// <inheritdoc/>
-    public override bool IsMatch (string input) => _pattern.IsMatch (input);
+    public override bool IsMatch (string input) { return _pattern.IsMatch (input); }
 
     /// <summary>
-    /// Returns the ss3 key that corresponds to the provided input escape sequence
+    ///     Returns the ss3 key that corresponds to the provided input escape sequence
     /// </summary>
     /// <param name="input"></param>
     /// <returns></returns>
     protected override Key? GetKeyImpl (string input)
     {
-        var match = _pattern.Match (input);
+        Match match = _pattern.Match (input);
 
         if (!match.Success)
         {
diff --git a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/StringHeld.cs b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/StringHeld.cs
index caa5fd765d..376781e702 100644
--- a/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/StringHeld.cs
+++ b/Terminal.Gui/ConsoleDrivers/AnsiResponseParser/StringHeld.cs
@@ -16,6 +16,6 @@ internal class StringHeld : IHeld
 
     public void AddToHeld (object o) { _held.Append ((char)o); }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public int Length => _held.Length;
 }

From e020f425f0a72bc587a16eabfbe0c3e37204b641 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 27 Feb 2025 19:51:21 +0000
Subject: [PATCH 197/198] Code cleanup on all v2 classes

---
 .../ConsoleDrivers/V2/ApplicationV2.cs        | 66 +++++++++----------
 .../ConsoleDrivers/V2/ConsoleDriverFacade.cs  | 23 +++----
 .../ConsoleDrivers/V2/ConsoleInput.cs         |  4 +-
 .../ConsoleDrivers/V2/IConsoleDriverFacade.cs |  8 +--
 .../ConsoleDrivers/V2/IConsoleInput.cs        |  8 +--
 .../ConsoleDrivers/V2/IConsoleOutput.cs       | 20 +++---
 .../ConsoleDrivers/V2/IInputProcessor.cs      |  8 +--
 .../ConsoleDrivers/V2/IKeyConverter.cs        | 14 ++--
 .../ConsoleDrivers/V2/IMainLoopCoordinator.cs | 11 ++--
 .../ConsoleDrivers/V2/IOutputBuffer.cs        | 22 +++----
 .../V2/IToplevelTransitionManager.cs          | 15 ++---
 .../ConsoleDrivers/V2/IWindowSizeMonitor.cs   |  8 +--
 .../ConsoleDrivers/V2/InputProcessor.cs       | 44 +++++++------
 Terminal.Gui/ConsoleDrivers/V2/Logging.cs     | 28 ++++----
 Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs    | 22 +++----
 .../ConsoleDrivers/V2/MainLoopCoordinator.cs  | 19 +++---
 .../ConsoleDrivers/V2/MouseButtonStateEx.cs   |  2 +-
 .../ConsoleDrivers/V2/MouseInterpreter.cs     |  5 +-
 Terminal.Gui/ConsoleDrivers/V2/NetInput.cs    |  8 +--
 .../ConsoleDrivers/V2/NetInputProcessor.cs    | 34 +++++-----
 .../ConsoleDrivers/V2/NetKeyConverter.cs      | 10 +--
 Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs   | 12 ++--
 .../V2/NotInitializedException.cs             | 16 ++---
 .../ConsoleDrivers/V2/OutputBuffer.cs         |  4 +-
 .../V2/ToplevelTransitionManager.cs           | 15 +++--
 .../V2/WindowsInputProcessor.cs               | 26 +++++---
 .../ConsoleDrivers/V2/WindowsKeyConverter.cs  | 15 ++---
 .../ConsoleDrivers/V2/WindowsOutput.cs        | 18 ++---
 28 files changed, 245 insertions(+), 240 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
index 73f9087f39..5679f19136 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ApplicationV2.cs
@@ -1,6 +1,5 @@
 #nullable enable
 using System.Collections.Concurrent;
-using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using Microsoft.Extensions.Logging;
 
@@ -22,8 +21,8 @@ public class ApplicationV2 : ApplicationImpl
     private readonly ITimedEvents _timedEvents = new TimedEvents ();
 
     /// <summary>
-    /// Creates anew instance of the Application backend. The provided
-    /// factory methods will be used on Init calls to get things booted.
+    ///     Creates anew instance of the Application backend. The provided
+    ///     factory methods will be used on Init calls to get things booted.
     /// </summary>
     public ApplicationV2 () : this (
                                     () => new NetInput (),
@@ -52,10 +51,10 @@ Func<IConsoleOutput> winOutputFactory
     [RequiresDynamicCode ("AOT")]
     public override void Init (IConsoleDriver? driver = null, string? driverName = null)
     {
-
         if (Application.Initialized)
         {
             Logging.Logger.LogError ("Init called multiple times without shutdown, ignoring.");
+
             return;
         }
 
@@ -80,7 +79,6 @@ public override void Init (IConsoleDriver? driver = null, string? driverName = n
         Application.SubscribeDriverEvents ();
     }
 
-
     private void CreateDriver (string? driverName)
     {
         PlatformID p = Environment.OSVersion.Platform;
@@ -98,7 +96,7 @@ private void CreateDriver (string? driverName)
         }
         else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
         {
-            _coordinator = CreateWindowsSubcomponents();
+            _coordinator = CreateWindowsSubcomponents ();
         }
         else
         {
@@ -115,30 +113,30 @@ private void CreateDriver (string? driverName)
 
     private IMainLoopCoordinator CreateWindowsSubcomponents ()
     {
-        ConcurrentQueue<WindowsConsole.InputRecord> inputBuffer = new ConcurrentQueue<WindowsConsole.InputRecord> ();
-        MainLoop<WindowsConsole.InputRecord> loop = new MainLoop<WindowsConsole.InputRecord> ();
+        ConcurrentQueue<WindowsConsole.InputRecord> inputBuffer = new ();
+        MainLoop<WindowsConsole.InputRecord> loop = new ();
 
         return new MainLoopCoordinator<WindowsConsole.InputRecord> (
-                                                                            _timedEvents,
-                                                                            _winInputFactory,
-                                                                            inputBuffer,
-                                                                            new WindowsInputProcessor (inputBuffer),
-                                                                            _winOutputFactory,
-                                                                            loop);
+                                                                    _timedEvents,
+                                                                    _winInputFactory,
+                                                                    inputBuffer,
+                                                                    new WindowsInputProcessor (inputBuffer),
+                                                                    _winOutputFactory,
+                                                                    loop);
     }
 
     private IMainLoopCoordinator CreateNetSubcomponents ()
     {
-        ConcurrentQueue<ConsoleKeyInfo> inputBuffer = new ConcurrentQueue<ConsoleKeyInfo> ();
-        MainLoop<ConsoleKeyInfo> loop = new MainLoop<ConsoleKeyInfo> ();
+        ConcurrentQueue<ConsoleKeyInfo> inputBuffer = new ();
+        MainLoop<ConsoleKeyInfo> loop = new ();
 
         return new MainLoopCoordinator<ConsoleKeyInfo> (
-                                                                _timedEvents,
-                                                                _netInputFactory,
-                                                                inputBuffer,
-                                                                new NetInputProcessor (inputBuffer),
-                                                                _netOutputFactory,
-                                                                loop);
+                                                        _timedEvents,
+                                                        _netInputFactory,
+                                                        inputBuffer,
+                                                        new NetInputProcessor (inputBuffer),
+                                                        _netOutputFactory,
+                                                        loop);
     }
 
     /// <inheritdoc/>
@@ -161,7 +159,7 @@ public override void Run (Toplevel view, Func<Exception, bool>? errorHandler = n
 
         if (!Application.Initialized)
         {
-            throw new NotInitializedException (nameof(Run));
+            throw new NotInitializedException (nameof (Run));
         }
 
         Application.Top = view;
@@ -173,8 +171,9 @@ public override void Run (Toplevel view, Func<Exception, bool>? errorHandler = n
         {
             if (_coordinator is null)
             {
-                throw new Exception ($"{nameof (IMainLoopCoordinator)}inexplicably became null during Run");
+                throw new ($"{nameof (IMainLoopCoordinator)}inexplicably became null during Run");
             }
+
             _coordinator.RunIteration ();
         }
     }
@@ -209,27 +208,24 @@ public override void RequestStop (Toplevel? top)
     public override void Invoke (Action action)
     {
         _timedEvents.AddIdle (
-                             () =>
-                             {
-                                 action ();
+                              () =>
+                              {
+                                  action ();
 
-                                 return false;
-                             }
-                            );
+                                  return false;
+                              }
+                             );
     }
 
     /// <inheritdoc/>
     public override void AddIdle (Func<bool> func) { _timedEvents.AddIdle (func); }
 
     /// <summary>
-    /// Removes an idle function added by <see cref="AddIdle"/>
+    ///     Removes an idle function added by <see cref="AddIdle"/>
     /// </summary>
     /// <param name="fnTrue">Function to remove</param>
     /// <returns>True if it was found and removed</returns>
-    public bool RemoveIdle (Func<bool> fnTrue)
-    {
-        return _timedEvents.RemoveIdle (fnTrue);
-    }
+    public bool RemoveIdle (Func<bool> fnTrue) { return _timedEvents.RemoveIdle (fnTrue); }
 
     /// <inheritdoc/>
     public override object AddTimeout (TimeSpan time, Func<bool> callback) { return _timedEvents.AddTimeout (time, callback); }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
index 60b5700c1d..aae6c855d8 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleDriverFacade.cs
@@ -4,7 +4,6 @@ namespace Terminal.Gui;
 
 internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
 {
-    private readonly IInputProcessor _inputProcessor;
     private readonly IConsoleOutput _output;
     private readonly IOutputBuffer _outputBuffer;
     private readonly AnsiRequestScheduler _ansiRequestScheduler;
@@ -12,7 +11,9 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
 
     /// <summary>The event fired when the terminal is resized.</summary>
     public event EventHandler<SizeChangedEventArgs> SizeChanged;
-    public IInputProcessor InputProcessor => _inputProcessor;
+
+    public IInputProcessor InputProcessor { get; }
+
     public ConsoleDriverFacade (
         IInputProcessor inputProcessor,
         IOutputBuffer outputBuffer,
@@ -21,14 +22,14 @@ public ConsoleDriverFacade (
         IWindowSizeMonitor windowSizeMonitor
     )
     {
-        _inputProcessor = inputProcessor;
+        InputProcessor = inputProcessor;
         _output = output;
         _outputBuffer = outputBuffer;
         _ansiRequestScheduler = ansiRequestScheduler;
 
-        _inputProcessor.KeyDown += (s, e) => KeyDown?.Invoke (s, e);
-        _inputProcessor.KeyUp += (s, e) => KeyUp?.Invoke (s, e);
-        _inputProcessor.MouseEvent += (s, e) => MouseEvent?.Invoke (s, e);
+        InputProcessor.KeyDown += (s, e) => KeyDown?.Invoke (s, e);
+        InputProcessor.KeyUp += (s, e) => KeyUp?.Invoke (s, e);
+        InputProcessor.MouseEvent += (s, e) => MouseEvent?.Invoke (s, e);
 
         windowSizeMonitor.SizeChanging += (_, e) => SizeChanged?.Invoke (this, e);
 
@@ -226,18 +227,18 @@ public void ClearContents ()
     /// <inheritdoc/>
     public virtual string GetVersionInfo ()
     {
-        string type = "";
+        var type = "";
 
-        if (_inputProcessor is WindowsInputProcessor)
+        if (InputProcessor is WindowsInputProcessor)
         {
             type = "(win)";
         }
-        else if (_inputProcessor is NetInputProcessor)
+        else if (InputProcessor is NetInputProcessor)
         {
             type = "(net)";
         }
 
-        return GetType().Name.TrimEnd('`','1') + type;
+        return GetType ().Name.TrimEnd ('`', '1') + type;
     }
 
     /// <summary>Tests if the specified rune is supported by the driver.</summary>
@@ -384,4 +385,4 @@ public void Refresh ()
     {
         // No need we will always draw when dirty
     }
-}
\ No newline at end of file
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs b/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
index 3c93608fc4..d3ddbbd02d 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ConsoleInput.cs
@@ -1,11 +1,10 @@
 #nullable enable
 using System.Collections.Concurrent;
-using System.Diagnostics.Metrics;
 
 namespace Terminal.Gui;
 
 /// <summary>
-/// Base class for reading console input in perpetual loop
+///     Base class for reading console input in perpetual loop
 /// </summary>
 /// <typeparam name="T"></typeparam>
 public abstract class ConsoleInput<T> : IConsoleInput<T>
@@ -18,7 +17,6 @@ public abstract class ConsoleInput<T> : IConsoleInput<T>
     /// </summary>
     public Func<DateTime> Now { get; set; } = () => DateTime.Now;
 
-    
     /// <inheritdoc/>
     public virtual void Dispose () { }
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IConsoleDriverFacade.cs b/Terminal.Gui/ConsoleDrivers/V2/IConsoleDriverFacade.cs
index de71a433ce..6d316e6f20 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IConsoleDriverFacade.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IConsoleDriverFacade.cs
@@ -1,14 +1,14 @@
 namespace Terminal.Gui;
 
 /// <summary>
-/// Interface for v2 driver abstraction layer
+///     Interface for v2 driver abstraction layer
 /// </summary>
 public interface IConsoleDriverFacade
 {
     /// <summary>
-    /// Class responsible for processing native driver input objects
-    /// e.g. <see cref="ConsoleKeyInfo"/> into <see cref="Key"/> events
-    /// and detecting and processing ansi escape sequences.
+    ///     Class responsible for processing native driver input objects
+    ///     e.g. <see cref="ConsoleKeyInfo"/> into <see cref="Key"/> events
+    ///     and detecting and processing ansi escape sequences.
     /// </summary>
     public IInputProcessor InputProcessor { get; }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs b/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs
index ee80a8081f..03f7128614 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IConsoleInput.cs
@@ -3,10 +3,10 @@
 namespace Terminal.Gui;
 
 /// <summary>
-/// Interface for reading console input indefinitely -
-/// i.e. in an infinite loop. The class is responsible only
-/// for reading and storing the input in a thread safe input buffer
-/// which is then processed downstream e.g. on main UI thread.
+///     Interface for reading console input indefinitely -
+///     i.e. in an infinite loop. The class is responsible only
+///     for reading and storing the input in a thread safe input buffer
+///     which is then processed downstream e.g. on main UI thread.
 /// </summary>
 /// <typeparam name="T"></typeparam>
 public interface IConsoleInput<T> : IDisposable
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
index 2b16dd8b8c..6004bf5e1b 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IConsoleOutput.cs
@@ -1,40 +1,40 @@
 namespace Terminal.Gui;
 
 /// <summary>
-/// Interface for writing console output
+///     Interface for writing console output
 /// </summary>
 public interface IConsoleOutput : IDisposable
 {
     /// <summary>
-    /// Writes the given text directly to the console. Use to send
-    /// ansi escape codes etc. Regular screen output should use the
-    /// <see cref="IOutputBuffer"/> overload.
+    ///     Writes the given text directly to the console. Use to send
+    ///     ansi escape codes etc. Regular screen output should use the
+    ///     <see cref="IOutputBuffer"/> overload.
     /// </summary>
     /// <param name="text"></param>
     void Write (string text);
 
     /// <summary>
-    /// Write the contents of the <paramref name="buffer"/> to the console
+    ///     Write the contents of the <paramref name="buffer"/> to the console
     /// </summary>
     /// <param name="buffer"></param>
     void Write (IOutputBuffer buffer);
 
     /// <summary>
-    /// Returns the current size of the console window in rows/columns (i.e.
-    /// of characters not pixels).
+    ///     Returns the current size of the console window in rows/columns (i.e.
+    ///     of characters not pixels).
     /// </summary>
     /// <returns></returns>
     public Size GetWindowSize ();
 
     /// <summary>
-    /// Updates the console cursor (the blinking underscore) to be hidden,
-    /// visible etc.
+    ///     Updates the console cursor (the blinking underscore) to be hidden,
+    ///     visible etc.
     /// </summary>
     /// <param name="visibility"></param>
     void SetCursorVisibility (CursorVisibility visibility);
 
     /// <summary>
-    /// Moves the console cursor to the given location.
+    ///     Moves the console cursor to the given location.
     /// </summary>
     /// <param name="col"></param>
     /// <param name="row"></param>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
index 0fc0c572fe..bb79ec83f4 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IInputProcessor.cs
@@ -2,9 +2,9 @@
 namespace Terminal.Gui;
 
 /// <summary>
-/// Interface for main loop class that will process the queued input buffer contents.
-/// Is responsible for <see cref="ProcessQueue"/> and translating into common Terminal.Gui
-/// events and data models.
+///     Interface for main loop class that will process the queued input buffer contents.
+///     Is responsible for <see cref="ProcessQueue"/> and translating into common Terminal.Gui
+///     events and data models.
 /// </summary>
 public interface IInputProcessor
 {
@@ -53,7 +53,7 @@ public interface IInputProcessor
     void ProcessQueue ();
 
     /// <summary>
-    /// Gets the response parser currently configured on this input processor.
+    ///     Gets the response parser currently configured on this input processor.
     /// </summary>
     /// <returns></returns>
     public IAnsiResponseParser GetParser ();
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IKeyConverter.cs b/Terminal.Gui/ConsoleDrivers/V2/IKeyConverter.cs
index f53695a397..12cc379fab 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IKeyConverter.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IKeyConverter.cs
@@ -1,18 +1,18 @@
 namespace Terminal.Gui;
 
 /// <summary>
-/// Interface for subcomponent of a <see cref="InputProcessor{T}"/> which
-/// can translate the raw console input type T (which typically varies by
-/// driver) to the shared Terminal.Gui <see cref="Key"/> class.
+///     Interface for subcomponent of a <see cref="InputProcessor{T}"/> which
+///     can translate the raw console input type T (which typically varies by
+///     driver) to the shared Terminal.Gui <see cref="Key"/> class.
 /// </summary>
 /// <typeparam name="T"></typeparam>
 public interface IKeyConverter<in T>
 {
     /// <summary>
-    /// Converts the native keyboard class read from console into
-    /// the shared <see cref="Key"/> class used by Terminal.Gui views.
+    ///     Converts the native keyboard class read from console into
+    ///     the shared <see cref="Key"/> class used by Terminal.Gui views.
     /// </summary>
     /// <param name="value"></param>
     /// <returns></returns>
-    Key ToKey(T value);
-}
\ No newline at end of file
+    Key ToKey (T value);
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
index dd3598dbcb..7a1ccf35bb 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IMainLoopCoordinator.cs
@@ -1,25 +1,24 @@
 namespace Terminal.Gui;
 
 /// <summary>
-/// Interface for main Terminal.Gui loop manager in v2.
+///     Interface for main Terminal.Gui loop manager in v2.
 /// </summary>
 public interface IMainLoopCoordinator
 {
     /// <summary>
-    /// Create all required subcomponents and boot strap.
+    ///     Create all required subcomponents and boot strap.
     /// </summary>
     /// <returns></returns>
     public Task StartAsync ();
 
-
     /// <summary>
-    /// Stops the input thread, blocking till it exits.
-    /// Call this method only from the main UI loop.
+    ///     Stops the input thread, blocking till it exits.
+    ///     Call this method only from the main UI loop.
     /// </summary>
     public void Stop ();
 
     /// <summary>
-    /// Run a single iteration of the main UI loop
+    ///     Run a single iteration of the main UI loop
     /// </summary>
     void RunIteration ();
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
index 01de7958c7..29c3475efe 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IOutputBuffer.cs
@@ -2,9 +2,9 @@
 namespace Terminal.Gui;
 
 /// <summary>
-/// Describes the screen state that you want the console to be in.
-/// Is designed to be drawn to repeatedly then manifest into the console
-/// once at the end of iteration after all drawing is finalized.
+///     Describes the screen state that you want the console to be in.
+///     Is designed to be drawn to repeatedly then manifest into the console
+///     once at the end of iteration after all drawing is finalized.
 /// </summary>
 public interface IOutputBuffer
 {
@@ -50,14 +50,14 @@ public interface IOutputBuffer
     public int Col { get; }
 
     /// <summary>
-    /// The first cell index on left of screen - basically always 0.
-    /// Changing this may have unexpected consequences.
+    ///     The first cell index on left of screen - basically always 0.
+    ///     Changing this may have unexpected consequences.
     /// </summary>
     int Left { get; set; }
 
     /// <summary>
-    /// The first cell index on top of screen - basically always 0.
-    /// Changing this may have unexpected consequences.
+    ///     The first cell index on top of screen - basically always 0.
+    ///     Changing this may have unexpected consequences.
     /// </summary>
     int Top { get; set; }
 
@@ -105,16 +105,16 @@ public interface IOutputBuffer
     void SetWindowSize (int cols, int rows);
 
     /// <summary>
-    /// Fills the given <paramref name="rect"/> with the given
-    /// symbol using the currently selected attribute.
+    ///     Fills the given <paramref name="rect"/> with the given
+    ///     symbol using the currently selected attribute.
     /// </summary>
     /// <param name="rect"></param>
     /// <param name="rune"></param>
     void FillRect (Rectangle rect, Rune rune);
 
     /// <summary>
-    /// Fills the given <paramref name="rect"/> with the given
-    /// symbol using the currently selected attribute.
+    ///     Fills the given <paramref name="rect"/> with the given
+    ///     symbol using the currently selected attribute.
     /// </summary>
     /// <param name="rect"></param>
     /// <param name="rune"></param>
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IToplevelTransitionManager.cs b/Terminal.Gui/ConsoleDrivers/V2/IToplevelTransitionManager.cs
index a3d93d49b9..984c54490d 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IToplevelTransitionManager.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IToplevelTransitionManager.cs
@@ -1,21 +1,20 @@
-#nullable enable
-namespace Terminal.Gui;
+namespace Terminal.Gui;
 
 /// <summary>
-/// Interface for class that handles bespoke behaviours that occur when application
-/// top level changes.
+///     Interface for class that handles bespoke behaviours that occur when application
+///     top level changes.
 /// </summary>
 public interface IToplevelTransitionManager
 {
     /// <summary>
-    /// Raises the <see cref="Toplevel.Ready"/> event on the current top level
-    /// if it has not been raised before now.
+    ///     Raises the <see cref="Toplevel.Ready"/> event on the current top level
+    ///     if it has not been raised before now.
     /// </summary>
     void RaiseReadyEventIfNeeded ();
 
     /// <summary>
-    /// Handles any state change needed when the application top changes e.g.
-    /// setting redraw flags
+    ///     Handles any state change needed when the application top changes e.g.
+    ///     setting redraw flags
     /// </summary>
     void HandleTopMaybeChanging ();
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/IWindowSizeMonitor.cs b/Terminal.Gui/ConsoleDrivers/V2/IWindowSizeMonitor.cs
index ba961cf444..5bfedfaf58 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/IWindowSizeMonitor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/IWindowSizeMonitor.cs
@@ -2,8 +2,8 @@
 namespace Terminal.Gui;
 
 /// <summary>
-/// Interface for classes responsible for reporting the current
-/// size of the terminal window.
+///     Interface for classes responsible for reporting the current
+///     size of the terminal window.
 /// </summary>
 public interface IWindowSizeMonitor
 {
@@ -11,8 +11,8 @@ public interface IWindowSizeMonitor
     event EventHandler<SizeChangedEventArgs>? SizeChanging;
 
     /// <summary>
-    /// Examines the current size of the terminal and raises <see cref="SizeChanging"/> if it is different
-    /// from last inspection.
+    ///     Examines the current size of the terminal and raises <see cref="SizeChanging"/> if it is different
+    ///     from last inspection.
     /// </summary>
     /// <returns></returns>
     bool Poll ();
diff --git a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
index 7ebb9fd1c6..e7b7b8d2cc 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
@@ -5,9 +5,9 @@
 namespace Terminal.Gui;
 
 /// <summary>
-/// Processes the queued input buffer contents - which must be of Type <typeparamref name="T"/>.
-/// Is responsible for <see cref="ProcessQueue"/> and translating into common Terminal.Gui
-/// events and data models.
+///     Processes the queued input buffer contents - which must be of Type <typeparamref name="T"/>.
+///     Is responsible for <see cref="ProcessQueue"/> and translating into common Terminal.Gui
+///     events and data models.
 /// </summary>
 public abstract class InputProcessor<T> : IInputProcessor
 {
@@ -19,14 +19,14 @@ public abstract class InputProcessor<T> : IInputProcessor
     internal AnsiResponseParser<T> Parser { get; } = new ();
 
     /// <summary>
-    /// Class responsible for translating the driver specific native input class <typeparamref name="T"/> e.g.
-    /// <see cref="ConsoleKeyInfo"/> into the Terminal.Gui <see cref="Key"/> class (used for all
-    /// internal library representations of Keys).
+    ///     Class responsible for translating the driver specific native input class <typeparamref name="T"/> e.g.
+    ///     <see cref="ConsoleKeyInfo"/> into the Terminal.Gui <see cref="Key"/> class (used for all
+    ///     internal library representations of Keys).
     /// </summary>
     public IKeyConverter<T> KeyConverter { get; }
 
     /// <summary>
-    /// Input buffer which will be drained from by this class.
+    ///     Input buffer which will be drained from by this class.
     /// </summary>
     public ConcurrentQueue<T> InputBuffer { get; }
 
@@ -48,7 +48,7 @@ public abstract class InputProcessor<T> : IInputProcessor
     /// <param name="a"></param>
     public void OnKeyDown (Key a)
     {
-        Logging.Trace($"{nameof(InputProcessor<T>)} raised {a}");
+        Logging.Trace ($"{nameof (InputProcessor<T>)} raised {a}");
         KeyDown?.Invoke (this, a);
     }
 
@@ -77,22 +77,25 @@ public void OnMouseEvent (MouseEventArgs a)
         // Ensure ScreenPosition is set
         a.ScreenPosition = a.Position;
 
-        foreach (var e in _mouseInterpreter.Process (a))
+        foreach (MouseEventArgs e in _mouseInterpreter.Process (a))
         {
-            Logging.Trace($"Mouse Interpreter raising {e.Flags}");
+            Logging.Trace ($"Mouse Interpreter raising {e.Flags}");
+
             // Pass on
             MouseEvent?.Invoke (this, e);
         }
     }
 
     /// <summary>
-    /// Constructs base instance including wiring all relevant
-    /// parser events and setting <see cref="InputBuffer"/> to
-    /// the provided thread safe input collection.
+    ///     Constructs base instance including wiring all relevant
+    ///     parser events and setting <see cref="InputBuffer"/> to
+    ///     the provided thread safe input collection.
     /// </summary>
     /// <param name="inputBuffer">The collection that will be populated with new input (see <see cref="IConsoleInput{T}"/>)</param>
-    /// <param name="keyConverter">Key converter for translating driver specific
-    /// <typeparamref name="T"/> class into Terminal.Gui <see cref="Key"/>.</param>
+    /// <param name="keyConverter">
+    ///     Key converter for translating driver specific
+    ///     <typeparamref name="T"/> class into Terminal.Gui <see cref="Key"/>.
+    /// </param>
     protected InputProcessor (ConcurrentQueue<T> inputBuffer, IKeyConverter<T> keyConverter)
     {
         InputBuffer = inputBuffer;
@@ -112,7 +115,8 @@ protected InputProcessor (ConcurrentQueue<T> inputBuffer, IKeyConverter<T> keyCo
                                            {
                                                var cur = new string (str.Select (k => k.Item1).ToArray ());
                                                Logging.Logger.LogInformation ($"{nameof (InputProcessor<T>)} ignored unrecognized response '{cur}'");
-                                               AnsiSequenceSwallowed?.Invoke (this,cur);
+                                               AnsiSequenceSwallowed?.Invoke (this, cur);
+
                                                return true;
                                            };
         KeyConverter = keyConverter;
@@ -146,15 +150,15 @@ private IEnumerable<T> ReleaseParserHeldKeysIfStale ()
     }
 
     /// <summary>
-    /// Process the provided single input element <paramref name="input"/>. This method
-    /// is called sequentially for each value read from <see cref="InputBuffer"/>.
+    ///     Process the provided single input element <paramref name="input"/>. This method
+    ///     is called sequentially for each value read from <see cref="InputBuffer"/>.
     /// </summary>
     /// <param name="input"></param>
     protected abstract void Process (T input);
 
     /// <summary>
-    /// Process the provided single input element - short-circuiting the <see cref="Parser"/>
-    /// stage of the processing.
+    ///     Process the provided single input element - short-circuiting the <see cref="Parser"/>
+    ///     stage of the processing.
     /// </summary>
     /// <param name="input"></param>
     protected abstract void ProcessAfterParsing (T input);
diff --git a/Terminal.Gui/ConsoleDrivers/V2/Logging.cs b/Terminal.Gui/ConsoleDrivers/V2/Logging.cs
index dc74692bef..0d367f4a40 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/Logging.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/Logging.cs
@@ -30,35 +30,37 @@ public static class Logging
     internal static readonly Meter Meter = new ("Terminal.Gui");
 
     /// <summary>
-    /// Metric for how long it takes each full iteration of the main loop to occur
+    ///     Metric for how long it takes each full iteration of the main loop to occur
     /// </summary>
-    public static readonly Histogram<int> TotalIterationMetric = Logging.Meter.CreateHistogram<int> ("Iteration (ms)");
+    public static readonly Histogram<int> TotalIterationMetric = Meter.CreateHistogram<int> ("Iteration (ms)");
 
     /// <summary>
-    /// Metric for how long it took to do the 'timeouts and invokes' section of main loop.
+    ///     Metric for how long it took to do the 'timeouts and invokes' section of main loop.
     /// </summary>
-    public static readonly Histogram<int> IterationInvokesAndTimeouts = Logging.Meter.CreateHistogram<int> ("Invokes & Timers (ms)");
+    public static readonly Histogram<int> IterationInvokesAndTimeouts = Meter.CreateHistogram<int> ("Invokes & Timers (ms)");
 
     /// <summary>
-    /// Counter for when we redraw, helps detect situations e.g. where we are repainting entire UI every loop
+    ///     Counter for when we redraw, helps detect situations e.g. where we are repainting entire UI every loop
     /// </summary>
-    public static readonly Counter<int> Redraws = Logging.Meter.CreateCounter<int> ("Redraws");
+    public static readonly Counter<int> Redraws = Meter.CreateCounter<int> ("Redraws");
 
     /// <summary>
-    /// Metric for how long it takes to read all available input from the input stream - at which
-    /// point input loop will sleep.
+    ///     Metric for how long it takes to read all available input from the input stream - at which
+    ///     point input loop will sleep.
     /// </summary>
-    public static readonly Histogram<int> DrainInputStream = Logging.Meter.CreateHistogram<int> ("Drain Input (ms)");
+    public static readonly Histogram<int> DrainInputStream = Meter.CreateHistogram<int> ("Drain Input (ms)");
 
     /// <summary>
-    /// Logs a trace message including the 
+    ///     Logs a trace message including the
     /// </summary>
     /// <param name="message"></param>
     /// <param name="caller"></param>
     /// <param name="filePath"></param>
-    public static void Trace (string message,
-                                 [CallerMemberName] string caller = "",
-                                 [CallerFilePath] string filePath = "")
+    public static void Trace (
+        string message,
+        [CallerMemberName] string caller = "",
+        [CallerFilePath] string filePath = ""
+    )
     {
         string className = Path.GetFileNameWithoutExtension (filePath);
         Logger.LogTrace ($"[{className}] [{caller}] {message}");
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
index eb289f9c2d..8c830aaa36 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoop.cs
@@ -1,7 +1,6 @@
 #nullable enable
 using System.Collections.Concurrent;
 using System.Diagnostics;
-using Microsoft.Extensions.Logging;
 
 namespace Terminal.Gui;
 
@@ -18,16 +17,16 @@ public class MainLoop<T> : IMainLoop<T>
     /// <inheritdoc/>
     public ITimedEvents TimedEvents
     {
-        get => _timedEvents ?? throw new NotInitializedException(nameof(TimedEvents));
+        get => _timedEvents ?? throw new NotInitializedException (nameof (TimedEvents));
         private set => _timedEvents = value;
     }
 
     // TODO: follow above pattern for others too
 
     /// <summary>
-    /// The input events thread-safe collection. This is populated on separate
-    /// thread by a <see cref="IConsoleInput{T}"/>. Is drained as part of each
-    /// <see cref="Iteration"/>
+    ///     The input events thread-safe collection. This is populated on separate
+    ///     thread by a <see cref="IConsoleInput{T}"/>. Is drained as part of each
+    ///     <see cref="Iteration"/>
     /// </summary>
     public ConcurrentQueue<T> InputBuffer
     {
@@ -67,7 +66,7 @@ public IWindowSizeMonitor WindowSizeMonitor
     }
 
     /// <summary>
-    /// Handles raising events and setting required draw status etc when <see cref="Application.Top"/> changes
+    ///     Handles raising events and setting required draw status etc when <see cref="Application.Top"/> changes
     /// </summary>
     public IToplevelTransitionManager ToplevelTransitionManager = new ToplevelTransitionManager ();
 
@@ -78,7 +77,7 @@ public IWindowSizeMonitor WindowSizeMonitor
     public Func<DateTime> Now { get; set; } = () => DateTime.Now;
 
     /// <summary>
-    /// Initializes the class with the provided subcomponents
+    ///     Initializes the class with the provided subcomponents
     /// </summary>
     /// <param name="timedEvents"></param>
     /// <param name="inputBuffer"></param>
@@ -130,6 +129,7 @@ internal void IterationImpl ()
             if (needsDrawOrLayout || sizeChanged)
             {
                 Logging.Redraws.Add (1);
+
                 // TODO: Test only
                 Application.LayoutAndDraw (true);
 
@@ -138,7 +138,7 @@ internal void IterationImpl ()
                 Out.SetCursorVisibility (CursorVisibility.Default);
             }
 
-            this.SetCursor ();
+            SetCursor ();
         }
 
         var swCallbacks = Stopwatch.StartNew ();
@@ -150,7 +150,6 @@ internal void IterationImpl ()
         Logging.IterationInvokesAndTimeouts.Record (swCallbacks.Elapsed.Milliseconds);
     }
 
-    
     private void SetCursor ()
     {
         View? mostFocused = Application.Top.MostFocused;
@@ -180,7 +179,8 @@ private bool AnySubviewsNeedDrawn (View v)
     {
         if (v.NeedsDraw || v.NeedsLayout)
         {
-            Logging.Trace( $"{v.GetType ().Name} triggered redraw (NeedsDraw={v.NeedsDraw} NeedsLayout={v.NeedsLayout}) ");
+            Logging.Trace ($"{v.GetType ().Name} triggered redraw (NeedsDraw={v.NeedsDraw} NeedsLayout={v.NeedsLayout}) ");
+
             return true;
         }
 
@@ -200,4 +200,4 @@ public void Dispose ()
     {
         // TODO release managed resources here
     }
-}
\ No newline at end of file
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
index 881a3b89b5..28bad67ab4 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MainLoopCoordinator.cs
@@ -4,11 +4,11 @@
 namespace Terminal.Gui;
 
 /// <summary>
-///<para>
-/// Handles creating the input loop thread and bootstrapping the
-/// <see cref="MainLoop{T}"/> that handles layout/drawing/events etc.
-/// </para>
-/// <para>This class is designed to be managed by <see cref="ApplicationV2"/></para>
+///     <para>
+///         Handles creating the input loop thread and bootstrapping the
+///         <see cref="MainLoop{T}"/> that handles layout/drawing/events etc.
+///     </para>
+///     <para>This class is designed to be managed by <see cref="ApplicationV2"/></para>
 /// </summary>
 /// <typeparam name="T"></typeparam>
 internal class MainLoopCoordinator<T> : IMainLoopCoordinator
@@ -75,10 +75,10 @@ public async Task StartAsync ()
         BootMainLoop ();
 
         // Wait asynchronously for the semaphore or task failure.
-        var waitForSemaphore = _startupSemaphore.WaitAsync ();
+        Task waitForSemaphore = _startupSemaphore.WaitAsync ();
 
         // Wait for either the semaphore to be released or the input task to crash.
-        var completedTask = await Task.WhenAny (waitForSemaphore, _inputTask).ConfigureAwait (false);
+        Task completedTask = await Task.WhenAny (waitForSemaphore, _inputTask).ConfigureAwait (false);
 
         // Check if the task was the input task and if it has failed.
         if (completedTask == _inputTask)
@@ -88,7 +88,7 @@ public async Task StartAsync ()
                 throw _inputTask.Exception;
             }
 
-            throw new Exception ("Input loop exited during startup instead of entering read loop properly (i.e. and blocking)");
+            throw new ("Input loop exited during startup instead of entering read loop properly (i.e. and blocking)");
         }
 
         Logging.Logger.LogInformation ("Main Loop Coordinator booting complete");
@@ -164,7 +164,7 @@ private void BuildFacadeIfPossible ()
         }
     }
 
-    private bool _stopCalled = false;
+    private bool _stopCalled;
 
     /// <inheritdoc/>
     public void Stop ()
@@ -174,6 +174,7 @@ public void Stop ()
         {
             return;
         }
+
         _stopCalled = true;
 
         _tokenSource.Cancel ();
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseButtonStateEx.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseButtonStateEx.cs
index 78557d2dae..fb1aa27fee 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MouseButtonStateEx.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseButtonStateEx.cs
@@ -10,7 +10,7 @@ internal class MouseButtonStateEx
     private readonly TimeSpan _repeatClickThreshold;
     private readonly int _buttonIdx;
     private int _consecutiveClicks;
-    private Point _lastPosition = new Point ();
+    private Point _lastPosition;
 
     /// <summary>
     ///     When the button entered its current state.
diff --git a/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs b/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
index 6211c68268..f6546302e1 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/MouseInterpreter.cs
@@ -1,7 +1,5 @@
 #nullable enable
 
-using Microsoft.Extensions.Logging;
-
 namespace Terminal.Gui;
 
 internal class MouseInterpreter
@@ -63,7 +61,8 @@ private MouseEventArgs RaiseClick (int button, int numberOfClicks, MouseEventArg
             View = mouseEventArgs.View,
             Position = mouseEventArgs.Position
         };
-        Logging.Trace($"Raising click event:{newClick.Flags} at screen {newClick.ScreenPosition}");
+        Logging.Trace ($"Raising click event:{newClick.Flags} at screen {newClick.ScreenPosition}");
+
         return newClick;
     }
 
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
index c113596bcd..d263c57493 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInput.cs
@@ -3,16 +3,16 @@
 namespace Terminal.Gui;
 
 /// <summary>
-/// Console input implementation that uses native dotnet methods e.g. <see cref="System.Console"/>.
+///     Console input implementation that uses native dotnet methods e.g. <see cref="System.Console"/>.
 /// </summary>
 public class NetInput : ConsoleInput<ConsoleKeyInfo>, INetInput
 {
     private readonly NetWinVTConsole _adjustConsole;
 
     /// <summary>
-    /// Creates a new instance of the class. Implicitly sends
-    /// console mode settings that enable virtual input (mouse
-    /// reporting etc).
+    ///     Creates a new instance of the class. Implicitly sends
+    ///     console mode settings that enable virtual input (mouse
+    ///     reporting etc).
     /// </summary>
     public NetInput ()
     {
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
index 9f6426a75f..d08daca5f4 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetInputProcessor.cs
@@ -1,5 +1,4 @@
 using System.Collections.Concurrent;
-using Microsoft.Extensions.Logging;
 
 namespace Terminal.Gui;
 
@@ -8,18 +7,20 @@ namespace Terminal.Gui;
 /// </summary>
 public class NetInputProcessor : InputProcessor<ConsoleKeyInfo>
 {
-    #pragma warning disable CA2211
+#pragma warning disable CA2211
     /// <summary>
-    /// Set to true to generate code in <see cref="Logging"/> (verbose only) for test cases in NetInputProcessorTests.
-    /// <remarks>This makes the task of capturing user/language/terminal specific keyboard issues easier to
-    /// diagnose. By turning this on and searching logs user can send us exactly the input codes that are released
-    /// to input stream.</remarks>
+    ///     Set to true to generate code in <see cref="Logging"/> (verbose only) for test cases in NetInputProcessorTests.
+    ///     <remarks>
+    ///         This makes the task of capturing user/language/terminal specific keyboard issues easier to
+    ///         diagnose. By turning this on and searching logs user can send us exactly the input codes that are released
+    ///         to input stream.
+    ///     </remarks>
     /// </summary>
     public static bool GenerateTestCasesForKeyPresses = false;
-    #pragma warning enable CA2211
+#pragma warning enable CA2211
 
     /// <inheritdoc/>
-    public NetInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer) : base (inputBuffer, new NetKeyConverter()) { }
+    public NetInputProcessor (ConcurrentQueue<ConsoleKeyInfo> inputBuffer) : base (inputBuffer, new NetKeyConverter ()) { }
 
     /// <inheritdoc/>
     protected override void Process (ConsoleKeyInfo consoleKeyInfo)
@@ -27,7 +28,7 @@ protected override void Process (ConsoleKeyInfo consoleKeyInfo)
         // For building test cases
         if (GenerateTestCasesForKeyPresses)
         {
-            Logging.Trace(FormatConsoleKeyInfoForTestCase (consoleKeyInfo));
+            Logging.Trace (FormatConsoleKeyInfoForTestCase (consoleKeyInfo));
         }
 
         foreach (Tuple<char, ConsoleKeyInfo> released in Parser.ProcessInput (Tuple.Create (consoleKeyInfo.KeyChar, consoleKeyInfo)))
@@ -39,21 +40,20 @@ protected override void Process (ConsoleKeyInfo consoleKeyInfo)
     /// <inheritdoc/>
     protected override void ProcessAfterParsing (ConsoleKeyInfo input)
     {
-        Key key = KeyConverter.ToKey (input);
+        var key = KeyConverter.ToKey (input);
         OnKeyDown (key);
         OnKeyUp (key);
     }
 
-
     /* For building test cases */
     private static string FormatConsoleKeyInfoForTestCase (ConsoleKeyInfo input)
     {
         string charLiteral = input.KeyChar == '\0' ? @"'\0'" : $"'{input.KeyChar}'";
-        string expectedLiteral = $"new Rune('todo')";
+        var expectedLiteral = "new Rune('todo')";
 
-        return $"new ConsoleKeyInfo({charLiteral}, ConsoleKey.{input.Key}, " +
-               $"{input.Modifiers.HasFlag (ConsoleModifiers.Shift).ToString ().ToLower ()}, " +
-               $"{input.Modifiers.HasFlag (ConsoleModifiers.Alt).ToString ().ToLower ()}, " +
-               $"{input.Modifiers.HasFlag (ConsoleModifiers.Control).ToString ().ToLower ()}), {expectedLiteral}";
+        return $"new ConsoleKeyInfo({charLiteral}, ConsoleKey.{input.Key}, "
+               + $"{input.Modifiers.HasFlag (ConsoleModifiers.Shift).ToString ().ToLower ()}, "
+               + $"{input.Modifiers.HasFlag (ConsoleModifiers.Alt).ToString ().ToLower ()}, "
+               + $"{input.Modifiers.HasFlag (ConsoleModifiers.Control).ToString ().ToLower ()}), {expectedLiteral}";
     }
-}
\ No newline at end of file
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetKeyConverter.cs b/Terminal.Gui/ConsoleDrivers/V2/NetKeyConverter.cs
index efac409ebb..b7c17e6153 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetKeyConverter.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetKeyConverter.cs
@@ -1,14 +1,14 @@
 namespace Terminal.Gui;
 
 /// <summary>
-/// <see cref="IKeyConverter{T}"/> capable of converting the
-/// dotnet <see cref="ConsoleKeyInfo"/> class into Terminal.Gui
-/// shared <see cref="Key"/> representation (used by <see cref="View"/>
-/// etc).
+///     <see cref="IKeyConverter{T}"/> capable of converting the
+///     dotnet <see cref="ConsoleKeyInfo"/> class into Terminal.Gui
+///     shared <see cref="Key"/> representation (used by <see cref="View"/>
+///     etc).
 /// </summary>
 internal class NetKeyConverter : IKeyConverter<ConsoleKeyInfo>
 {
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public Key ToKey (ConsoleKeyInfo input)
     {
         ConsoleKeyInfo adjustedInput = EscSeqUtils.MapConsoleKeyInfo (input);
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
index c36f5b0e87..b7a9ea2806 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NetOutput.cs
@@ -3,8 +3,8 @@
 namespace Terminal.Gui;
 
 /// <summary>
-/// Implementation of <see cref="IConsoleOutput"/> that uses native dotnet
-/// methods e.g. <see cref="System.Console"/>
+///     Implementation of <see cref="IConsoleOutput"/> that uses native dotnet
+///     methods e.g. <see cref="System.Console"/>
 /// </summary>
 public class NetOutput : IConsoleOutput
 {
@@ -13,7 +13,7 @@ public class NetOutput : IConsoleOutput
     private CursorVisibility? _cachedCursorVisibility;
 
     /// <summary>
-    /// Creates a new instance of the <see cref="NetOutput"/> class.
+    ///     Creates a new instance of the <see cref="NetOutput"/> class.
     /// </summary>
     public NetOutput ()
     {
@@ -191,11 +191,11 @@ private void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref
         outputWidth = 0;
     }
 
-
     /// <inheritdoc/>
     public void SetCursorPosition (int col, int row) { SetCursorPositionImpl (col, row); }
 
-    private Point _lastCursorPosition = new Point ();
+    private Point _lastCursorPosition;
+
     private bool SetCursorPositionImpl (int col, int row)
     {
         if (_lastCursorPosition.X == col && _lastCursorPosition.Y == row)
@@ -203,7 +203,7 @@ private bool SetCursorPositionImpl (int col, int row)
             return true;
         }
 
-        _lastCursorPosition = new Point (col, row);
+        _lastCursorPosition = new (col, row);
 
         if (_isWinPlatform)
         {
diff --git a/Terminal.Gui/ConsoleDrivers/V2/NotInitializedException.cs b/Terminal.Gui/ConsoleDrivers/V2/NotInitializedException.cs
index 9abc485211..5c63b4071e 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/NotInitializedException.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/NotInitializedException.cs
@@ -1,24 +1,22 @@
 namespace Terminal.Gui;
 
 /// <summary>
-/// Thrown when user code attempts to access a property or perform a method
-/// that is only supported after Initialization e.g. of an <see cref="IMainLoop{T}"/>
+///     Thrown when user code attempts to access a property or perform a method
+///     that is only supported after Initialization e.g. of an <see cref="IMainLoop{T}"/>
 /// </summary>
 public class NotInitializedException : Exception
 {
     /// <summary>
-    /// Creates a new instance of the exception indicating that the class
-    /// <paramref name="memberName"/> cannot be used until owner is initialized.
+    ///     Creates a new instance of the exception indicating that the class
+    ///     <paramref name="memberName"/> cannot be used until owner is initialized.
     /// </summary>
     /// <param name="memberName">Property or method name</param>
-    public NotInitializedException (string memberName):base($"{memberName} cannot be accessed before Initialization")
-    {
-    }
+    public NotInitializedException (string memberName) : base ($"{memberName} cannot be accessed before Initialization") { }
 
     /// <summary>
-    /// Creates a new instance of the exception with the full message/inner exception.
+    ///     Creates a new instance of the exception with the full message/inner exception.
     /// </summary>
     /// <param name="msg"></param>
     /// <param name="innerException"></param>
-    public NotInitializedException (string msg, Exception innerException) :base(msg,innerException){}
+    public NotInitializedException (string msg, Exception innerException) : base (msg, innerException) { }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
index 112c7f9cc2..44d137ff32 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/OutputBuffer.cs
@@ -344,7 +344,7 @@ public void ClearContents ()
             {
                 for (var c = 0; c < Cols; c++)
                 {
-                    Contents [row, c] = new()
+                    Contents [row, c] = new ()
                     {
                         Rune = (Rune)' ',
                         Attribute = new Attribute (Color.White, Color.Black),
@@ -403,7 +403,7 @@ public void FillRect (Rectangle rect, Rune rune)
                         continue;
                     }
 
-                    Contents [r, c] = new()
+                    Contents [r, c] = new ()
                     {
                         Rune = rune != default (Rune) ? rune : (Rune)' ',
                         Attribute = CurrentAttribute, IsDirty = true
diff --git a/Terminal.Gui/ConsoleDrivers/V2/ToplevelTransitionManager.cs b/Terminal.Gui/ConsoleDrivers/V2/ToplevelTransitionManager.cs
index c6fd9c7bc3..49bd86e4b6 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/ToplevelTransitionManager.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/ToplevelTransitionManager.cs
@@ -1,20 +1,20 @@
 #nullable enable
 namespace Terminal.Gui;
 
-
 /// <summary>
-/// Handles bespoke behaviours that occur when application top level changes.
+///     Handles bespoke behaviours that occur when application top level changes.
 /// </summary>
 public class ToplevelTransitionManager : IToplevelTransitionManager
 {
-    private readonly HashSet<Toplevel> _readiedTopLevels = new HashSet<Toplevel> ();
+    private readonly HashSet<Toplevel> _readiedTopLevels = new ();
 
     private View? _lastTop;
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public void RaiseReadyEventIfNeeded ()
     {
-        var top = Application.Top;
+        Toplevel? top = Application.Top;
+
         if (top != null && !_readiedTopLevels.Contains (top))
         {
             top.OnReady ();
@@ -22,10 +22,11 @@ public void RaiseReadyEventIfNeeded ()
         }
     }
 
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public void HandleTopMaybeChanging ()
     {
-        var newTop = Application.Top;
+        Toplevel? newTop = Application.Top;
+
         if (_lastTop != null && _lastTop != newTop && newTop != null)
         {
             newTop.SetNeedsDraw ();
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
index ca516d93d1..da1e81887a 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsInputProcessor.cs
@@ -11,10 +11,10 @@ namespace Terminal.Gui;
 /// </summary>
 internal class WindowsInputProcessor : InputProcessor<InputRecord>
 {
-    private readonly bool[] _lastWasPressed = new bool[4];
+    private readonly bool [] _lastWasPressed = new bool[4];
 
     /// <inheritdoc/>
-    public WindowsInputProcessor (ConcurrentQueue<InputRecord> inputBuffer) : base (inputBuffer, new WindowsKeyConverter()) { }
+    public WindowsInputProcessor (ConcurrentQueue<InputRecord> inputBuffer) : base (inputBuffer, new WindowsKeyConverter ()) { }
 
     /// <inheritdoc/>
     protected override void Process (InputRecord inputEvent)
@@ -72,7 +72,7 @@ protected override void ProcessAfterParsing (InputRecord input)
     {
         var key = KeyConverter.ToKey (input);
 
-        if(key != (Key)0)
+        if (key != (Key)0)
         {
             OnKeyDown (key!);
             OnKeyUp (key!);
@@ -81,9 +81,9 @@ protected override void ProcessAfterParsing (InputRecord input)
 
     public MouseEventArgs ToDriverMouse (MouseEventRecord e)
     {
-        MouseFlags mouseFlags = MouseFlags.ReportMousePosition;
+        var mouseFlags = MouseFlags.ReportMousePosition;
 
-        mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, ButtonState.Button1Pressed,MouseFlags.Button1Pressed, MouseFlags.Button1Released, 0);
+        mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, ButtonState.Button1Pressed, MouseFlags.Button1Pressed, MouseFlags.Button1Released, 0);
         mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, ButtonState.Button2Pressed, MouseFlags.Button2Pressed, MouseFlags.Button2Released, 1);
         mouseFlags = UpdateMouseFlags (mouseFlags, e.ButtonState, ButtonState.Button4Pressed, MouseFlags.Button4Pressed, MouseFlags.Button4Released, 3);
 
@@ -102,7 +102,7 @@ public MouseEventArgs ToDriverMouse (MouseEventRecord e)
             }
         }
 
-        if (e.EventFlags == WindowsConsole.EventFlags.MouseWheeled)
+        if (e.EventFlags == EventFlags.MouseWheeled)
         {
             switch ((int)e.ButtonState)
             {
@@ -128,7 +128,15 @@ public MouseEventArgs ToDriverMouse (MouseEventRecord e)
 
         return result;
     }
-    private MouseFlags UpdateMouseFlags (MouseFlags current, ButtonState newState,ButtonState pressedState, MouseFlags pressedFlag, MouseFlags releasedFlag, int buttonIndex)
+
+    private MouseFlags UpdateMouseFlags (
+        MouseFlags current,
+        ButtonState newState,
+        ButtonState pressedState,
+        MouseFlags pressedFlag,
+        MouseFlags releasedFlag,
+        int buttonIndex
+    )
     {
         if (newState.HasFlag (pressedState))
         {
@@ -143,7 +151,7 @@ private MouseFlags UpdateMouseFlags (MouseFlags current, ButtonState newState,Bu
                 _lastWasPressed [buttonIndex] = false;
             }
         }
+
         return current;
     }
-
-}
\ No newline at end of file
+}
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsKeyConverter.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsKeyConverter.cs
index 535c6d417f..fd092db67c 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsKeyConverter.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsKeyConverter.cs
@@ -3,16 +3,15 @@
 
 namespace Terminal.Gui;
 
-
 /// <summary>
-/// <see cref="IKeyConverter{T}"/> capable of converting the
-/// windows native <see cref="WindowsConsole.InputRecord"/> class
-/// into Terminal.Gui shared <see cref="Key"/> representation
-/// (used by <see cref="View"/> etc).
+///     <see cref="IKeyConverter{T}"/> capable of converting the
+///     windows native <see cref="WindowsConsole.InputRecord"/> class
+///     into Terminal.Gui shared <see cref="Key"/> representation
+///     (used by <see cref="View"/> etc).
 /// </summary>
 internal class WindowsKeyConverter : IKeyConverter<WindowsConsole.InputRecord>
 {
-    /// <inheritdoc />
+    /// <inheritdoc/>
     public Key ToKey (WindowsConsole.InputRecord inputEvent)
     {
         if (inputEvent.KeyEvent.wVirtualKeyCode == (ConsoleKeyMapping.VK)ConsoleKey.Packet)
@@ -23,7 +22,7 @@ public Key ToKey (WindowsConsole.InputRecord inputEvent)
             inputEvent.KeyEvent = WindowsDriver.FromVKPacketToKeyEventRecord (inputEvent.KeyEvent);
         }
 
-        WindowsConsole.ConsoleKeyInfoEx keyInfo = WindowsDriver.ToConsoleKeyInfoEx (inputEvent.KeyEvent);
+        var keyInfo = WindowsDriver.ToConsoleKeyInfoEx (inputEvent.KeyEvent);
 
         //Debug.WriteLine ($"event: KBD: {GetKeyboardLayoutName()} {inputEvent.ToString ()} {keyInfo.ToString (keyInfo)}");
 
@@ -34,6 +33,6 @@ public Key ToKey (WindowsConsole.InputRecord inputEvent)
             return (Key)0;
         }
 
-        return new Key (map);
+        return new (map);
     }
 }
diff --git a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
index 7f8279f876..382b01aa87 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
+++ b/Terminal.Gui/ConsoleDrivers/V2/WindowsOutput.cs
@@ -201,15 +201,15 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord
 
             foreach (ExtendedCharInfo info in charInfoBuffer)
             {
-                ci [i++] = new()
+                ci [i++] = new ()
                 {
-                    Char = new() { UnicodeChar = info.Char },
+                    Char = new () { UnicodeChar = info.Char },
                     Attributes =
                         (ushort)((int)info.Attribute.Foreground.GetClosestNamedColor16 () | ((int)info.Attribute.Background.GetClosestNamedColor16 () << 4))
                 };
             }
 
-            result = WriteConsoleOutput (_screenBuffer, ci, bufferSize, new() { X = window.Left, Y = window.Top }, ref window);
+            result = WriteConsoleOutput (_screenBuffer, ci, bufferSize, new () { X = window.Left, Y = window.Top }, ref window);
         }
         else
         {
@@ -302,23 +302,22 @@ public void SetCursorVisibility (CursorVisibility visibility)
         Write (sb.ToString ());
     }
 
-    private Point _lastCursorPosition = new Point ();
+    private Point _lastCursorPosition;
 
     /// <inheritdoc/>
     public void SetCursorPosition (int col, int row)
     {
-
         if (_lastCursorPosition.X == col && _lastCursorPosition.Y == row)
         {
             return;
         }
 
-        _lastCursorPosition = new Point (col, row);
+        _lastCursorPosition = new (col, row);
 
         SetConsoleCursorPosition (_screenBuffer, new ((short)col, (short)row));
     }
 
-    private bool _isDisposed = false;
+    private bool _isDisposed;
 
     /// <inheritdoc/>
     public void Dispose ()
@@ -336,9 +335,10 @@ public void Dispose ()
             }
             catch (Exception e)
             {
-                Logging.Logger.LogError (e,"Error trying to close screen buffer handle in WindowsOutput via interop method");
+                Logging.Logger.LogError (e, "Error trying to close screen buffer handle in WindowsOutput via interop method");
             }
         }
-        _isDisposed=true;
+
+        _isDisposed = true;
     }
 }

From 4469f1b636ef01ab5adba42f45ff60f38a8e6965 Mon Sep 17 00:00:00 2001
From: tznind <tznind@dundee.ac.uk>
Date: Thu, 27 Feb 2025 20:11:36 +0000
Subject: [PATCH 198/198] Update class diagram

---
 Terminal.Gui/ConsoleDrivers/V2/V2.cd | 89 +++++++++++++++++++++++++---
 1 file changed, 81 insertions(+), 8 deletions(-)

diff --git a/Terminal.Gui/ConsoleDrivers/V2/V2.cd b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
index 579aefd565..f5004db3ce 100644
--- a/Terminal.Gui/ConsoleDrivers/V2/V2.cd
+++ b/Terminal.Gui/ConsoleDrivers/V2/V2.cd
@@ -78,8 +78,8 @@
         <Point X="12.5" Y="5.125" />
         <Point X="12.5" Y="5.792" />
         <Point X="13.031" Y="5.792" />
-        <Point X="13.031" Y="7.596" />
-        <Point X="14" Y="7.596" />
+        <Point X="13.031" Y="7.846" />
+        <Point X="14" Y="7.846" />
       </Path>
     </AssociationLine>
     <AssociationLine Name="AnsiRequestScheduler" Type="Terminal.Gui.AnsiRequestScheduler" ManuallyRouted="true">
@@ -164,7 +164,7 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.NetOutput" Collapsed="true">
-    <Position X="14.75" Y="8.25" Width="1.5" />
+    <Position X="14.75" Y="8.5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AEAAAAAAACAAAAAAAAAAAAAAAAAAQAAAMACAAAEAgAk=</HashCode>
       <FileName>ConsoleDrivers\V2\NetOutput.cs</FileName>
@@ -172,7 +172,7 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.WindowsOutput" Collapsed="true">
-    <Position X="13.25" Y="8.25" Width="1.5" />
+    <Position X="13.25" Y="8.5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AEAAABACACAAhAAAAAAAACCAAAgAQAAIMAAAAAEAgAQ=</HashCode>
       <FileName>ConsoleDrivers\V2\WindowsOutput.cs</FileName>
@@ -216,7 +216,7 @@
     </TypeIdentifier>
   </Class>
   <Class Name="Terminal.Gui.AnsiMouseParser">
-    <Position X="23.5" Y="9.5" Width="1.75" />
+    <Position X="23.5" Y="9.75" Width="1.75" />
     <TypeIdentifier>
       <HashCode>BAAAAAAAAAgAAAAAAAAAAAAAIAAAAAAAQAAAAAAAAAA=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiMouseParser.cs</FileName>
@@ -229,7 +229,7 @@
       <Compartment Name="Fields" Collapsed="true" />
     </Compartments>
     <TypeIdentifier>
-      <HashCode>AQcgAAAAAKBAgFEIBBgAAJEAAjkaQiIAGQADKABDAgQ=</HashCode>
+      <HashCode>AQcgAAAAAKBAgFEIBBgAQJEAAjkaQiIAGQADKABDgAQ=</HashCode>
       <FileName>ConsoleDrivers\V2\ConsoleDriverFacade.cs</FileName>
     </TypeIdentifier>
     <ShowAsAssociation>
@@ -249,6 +249,19 @@
   </Class>
   <Class Name="Terminal.Gui.AnsiResponseParserBase" Collapsed="true">
     <Position X="20.25" Y="9" Width="2" />
+    <AssociationLine Name="_mouseParser" Type="Terminal.Gui.AnsiMouseParser" FixedFromPoint="true" FixedToPoint="true">
+      <Path>
+        <Point X="22.25" Y="9.438" />
+        <Point X="24.375" Y="9.438" />
+        <Point X="24.375" Y="9.75" />
+      </Path>
+    </AssociationLine>
+    <AssociationLine Name="_keyboardParser" Type="Terminal.Gui.AnsiKeyboardParser" FixedFromPoint="true">
+      <Path>
+        <Point X="22.25" Y="9.375" />
+        <Point X="25.5" Y="9.375" />
+      </Path>
+    </AssociationLine>
     <TypeIdentifier>
       <HashCode>UAiASAAAEICQALAAQAAAKAAAoAIAAABAAQIAJiAQASQ=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\AnsiResponseParser.cs</FileName>
@@ -367,11 +380,14 @@
     <Lollipop Position="0.2" />
   </Class>
   <Class Name="Terminal.Gui.AnsiKeyboardParser">
-    <Position X="25.5" Y="9.5" Width="1.75" />
+    <Position X="25.5" Y="9.25" Width="1.75" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAE=</HashCode>
       <FileName>ConsoleDrivers\AnsiResponseParser\Keyboard\AnsiKeyboardParser.cs</FileName>
     </TypeIdentifier>
+    <ShowAsCollectionAssociation>
+      <Field Name="_patterns" />
+    </ShowAsCollectionAssociation>
   </Class>
   <Class Name="Terminal.Gui.ToplevelTransitionManager" Collapsed="true">
     <Position X="9.25" Y="13.75" Width="2.25" />
@@ -381,6 +397,49 @@
     </TypeIdentifier>
     <Lollipop Position="0.2" />
   </Class>
+  <Class Name="Terminal.Gui.Logging" Collapsed="true">
+    <Position X="0.5" Y="5.25" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAIgAAAAAAEQAAAAAAAAABAAgAAAAAAAEAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\Logging.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="Terminal.Gui.WindowSizeMonitor" Collapsed="true" BaseTypeListCollapsed="true">
+    <Position X="13.25" Y="14" Width="1.75" />
+    <TypeIdentifier>
+      <HashCode>AAAAgAAAAAAAAAAEAAAAABAAAAAACAAAAAAAAAAAACA=</HashCode>
+      <FileName>ConsoleDrivers\V2\WindowSizeMonitor.cs</FileName>
+    </TypeIdentifier>
+    <Lollipop Position="0.2" />
+  </Class>
+  <Class Name="Terminal.Gui.AnsiKeyboardParserPattern" Collapsed="true">
+    <Position X="28.5" Y="9.5" Width="2" />
+    <TypeIdentifier>
+      <HashCode>AAACIAAAAAAAAAAAAAAAAAQQAAAAAAAAAAAAAAAACAA=</HashCode>
+      <FileName>ConsoleDrivers\AnsiResponseParser\Keyboard\AnsiKeyboardParserPattern.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="Terminal.Gui.CsiKeyPattern" Collapsed="true">
+    <Position X="25.5" Y="10.75" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAACAAAAAAAAABAAAAAAAAAQAACAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\AnsiResponseParser\Keyboard\CsiKeyPattern.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="Terminal.Gui.EscAsAltPattern" Collapsed="true">
+    <Position X="27.75" Y="10.75" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAACAAAAAAAAAAAAAAAAAAAQAACAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\AnsiResponseParser\Keyboard\EscAsAltPattern.cs</FileName>
+    </TypeIdentifier>
+  </Class>
+  <Class Name="Terminal.Gui.Ss3Pattern" Collapsed="true">
+    <Position X="29.5" Y="10.75" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAACAAAAAAAAAAAAAAAAAAAQAACAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\AnsiResponseParser\Keyboard\Ss3Pattern.cs</FileName>
+    </TypeIdentifier>
+  </Class>
   <Interface Name="Terminal.Gui.IConsoleInput&lt;T&gt;" Collapsed="true">
     <Position X="12.5" Y="1" Width="1.5" />
     <TypeIdentifier>
@@ -396,7 +455,7 @@
     </TypeIdentifier>
   </Interface>
   <Interface Name="Terminal.Gui.IConsoleOutput" Collapsed="true">
-    <Position X="14" Y="7.25" Width="1.5" />
+    <Position X="14" Y="7.5" Width="1.5" />
     <TypeIdentifier>
       <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAMAAAAAEAAAA=</HashCode>
       <FileName>ConsoleDrivers\V2\IConsoleOutput.cs</FileName>
@@ -485,6 +544,20 @@
       <FileName>ConsoleDrivers\V2\IConsoleDriverFacade.cs</FileName>
     </TypeIdentifier>
   </Interface>
+  <Interface Name="Terminal.Gui.INetInput" Collapsed="true">
+    <Position X="14.25" Y="2" Width="1.75" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\INetInput.cs</FileName>
+    </TypeIdentifier>
+  </Interface>
+  <Interface Name="Terminal.Gui.IWindowsInput" Collapsed="true">
+    <Position X="10.75" Y="2" Width="1.5" />
+    <TypeIdentifier>
+      <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+      <FileName>ConsoleDrivers\V2\IWindowsInput.cs</FileName>
+    </TypeIdentifier>
+  </Interface>
   <Enum Name="Terminal.Gui.AnsiResponseParserState">
     <Position X="20.25" Y="7.25" Width="2" />
     <TypeIdentifier>