Преглед изворни кода

Fixes #4125. Remove legacy MainLoop infrastructure (#4343)

* Initial plan

* Phase 1: Update IConsoleDriver.Init() to return void instead of MainLoop

Co-authored-by: tig <[email protected]>

* Phase 2: Remove legacy MainLoop infrastructure

Co-authored-by: tig <[email protected]>

* Complete Phase 1 and Phase 2 - All tests pass

Co-authored-by: tig <[email protected]>

* Update deep dive docs to reflect MainLoop removal

Co-authored-by: tig <[email protected]>

---------

Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: tig <[email protected]>
Co-authored-by: Tig <[email protected]>
Copilot пре 1 месец
родитељ
комит
a0979368cb
31 измењених фајлова са 25 додато и 1533 уклоњено
  1. 0 1
      Examples/UICatalog/Scenarios/Arrangement.cs
  2. 0 1
      Examples/UICatalog/Scenarios/Clipping.cs
  3. 0 2
      Examples/UICatalog/Scenarios/ProgressBarStyles.cs
  4. 0 1
      Examples/UICatalog/Scenarios/Shortcuts.cs
  5. 1 1
      Terminal.Gui/App/Application.Lifecycle.cs
  6. 0 20
      Terminal.Gui/App/Application.Run.cs
  7. 0 3
      Terminal.Gui/App/Application.cs
  8. 0 24
      Terminal.Gui/App/MainLoop/IMainLoopDriver.cs
  9. 0 122
      Terminal.Gui/App/MainLoop/LegacyMainLoopDriver.cs
  10. 2 17
      Terminal.Gui/App/MainLoop/MainLoopSyncContext.cs
  11. 2 2
      Terminal.Gui/Drivers/AnsiHandling/EscSeqUtils/EscSeqUtils.cs
  12. 1 2
      Terminal.Gui/Drivers/ConsoleDriver.cs
  13. 1 1
      Terminal.Gui/Drivers/ConsoleDriverFacade.cs
  14. 1 27
      Terminal.Gui/Drivers/FakeDriver/FakeDriver.cs
  15. 0 38
      Terminal.Gui/Drivers/FakeDriver/FakeMainLoop.cs
  16. 1 2
      Terminal.Gui/Drivers/IConsoleDriver.cs
  17. 0 17
      Tests/UnitTests/Application/ApplicationTests.cs
  18. 0 940
      Tests/UnitTests/Application/MainLoopTests.cs
  19. 0 1
      Tests/UnitTests/Application/RunStateTests.cs
  20. 2 2
      Tests/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs
  21. 0 1
      Tests/UnitTests/View/Draw/AllViewsDrawTests.cs
  22. 0 1
      Tests/UnitTests/View/Layout/LayoutTests.cs
  23. 0 1
      Tests/UnitTests/Views/AllViewsTests.cs
  24. 0 2
      Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs
  25. 0 300
      Tests/UnitTestsParallelizable/ConsoleDrivers/MainLoopDriverTests.cs
  26. 1 1
      Tests/UnitTestsParallelizable/MockConsoleDriver.cs
  27. 0 1
      Tests/UnitTestsParallelizable/TestSetup.cs
  28. 2 0
      docfx/docs/drivers.md
  29. 11 2
      docfx/docs/migratingfromv1.md
  30. BIN
      local_packages/Terminal.Gui.2.0.0.nupkg
  31. BIN
      local_packages/Terminal.Gui.2.0.0.snupkg

+ 0 - 1
Examples/UICatalog/Scenarios/Arrangement.cs

@@ -99,7 +99,6 @@ public class Arrangement : Scenario
 
                              progressBar.Fraction += 0.01f;
 
-                             Application.Wakeup ();
 
                              progressBar.SetNeedsDraw ();
                          };

+ 0 - 1
Examples/UICatalog/Scenarios/Clipping.cs

@@ -100,7 +100,6 @@ public class Clipping : Scenario
                                  {
                                      tiledProgressBar1.Pulse ();
                                      tiledProgressBar2.Pulse ();
-                                     Application.Wakeup ();
                                  };
 
         progressTimer.Start ();

+ 0 - 2
Examples/UICatalog/Scenarios/ProgressBarStyles.cs

@@ -198,7 +198,6 @@ public class ProgressBarStyles : Scenario
                                                                       button.Enabled = true;
                                                                   }
 
-                                                                  Application.Wakeup ();
                                                               },
                                                               null,
                                                               0,
@@ -282,7 +281,6 @@ public class ProgressBarStyles : Scenario
                                      marqueesBlocksPB.Text = marqueesContinuousPB.Text = DateTime.Now.TimeOfDay.ToString ();
                                      marqueesBlocksPB.Pulse ();
                                      marqueesContinuousPB.Pulse ();
-                                     Application.Wakeup ();
                                  },
                                  null,
                                  0,

+ 0 - 1
Examples/UICatalog/Scenarios/Shortcuts.cs

@@ -384,7 +384,6 @@ public class Shortcuts : Scenario
 
                                  pb.Fraction += 0.01f;
 
-                                 Application.Wakeup ();
 
                                  pb.SetNeedsDraw ();
                              }

+ 1 - 1
Terminal.Gui/App/Application.Lifecycle.cs

@@ -131,7 +131,7 @@ public static partial class Application // Lifecycle (Init/Shutdown)
 
         try
         {
-            MainLoop = Driver!.Init ();
+            Driver!.Init ();
             SubscribeDriverEvents ();
         }
         catch (InvalidOperationException ex)

+ 0 - 20
Terminal.Gui/App/Application.Run.cs

@@ -372,12 +372,6 @@ public static partial class Application // Run (Begin -> Run -> Layout/Draw -> E
     /// <param name="action">the action to be invoked on the main processing thread.</param>
     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.
-
-    /// <summary>Wakes up the running application that might be waiting on input.</summary>
-    public static void Wakeup () { MainLoop?.Wakeup (); }
-
     /// <summary>
     ///     Causes any Toplevels that need layout to be laid out. Then draws any Toplevels that need display. Only Views that
     ///     need to be laid out (see <see cref="View.NeedsLayout"/>) will be laid out.
@@ -396,10 +390,6 @@ public static partial class Application // Run (Begin -> Run -> Layout/Draw -> E
     /// <remarks>See also <see cref="Timeout"/></remarks>
     public static event EventHandler<IterationEventArgs>? Iteration;
 
-    /// <summary>The <see cref="MainLoop"/> driver for the application</summary>
-    /// <value>The main loop.</value>
-    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
     ///     cause the application to continue running until Application.RequestStop () is called.
@@ -417,11 +407,6 @@ public static partial class Application // Run (Begin -> Run -> Layout/Draw -> E
 
         for (state.Toplevel.Running = true; state.Toplevel?.Running == true;)
         {
-            if (MainLoop is { })
-            {
-                MainLoop.Running = true;
-            }
-
             if (EndAfterFirstIteration && !firstIteration)
             {
                 return;
@@ -430,11 +415,6 @@ public static partial class Application // Run (Begin -> Run -> Layout/Draw -> E
             firstIteration = RunIteration (ref state, firstIteration);
         }
 
-        if (MainLoop is { })
-        {
-            MainLoop.Running = false;
-        }
-
         // Run one last iteration to consume any outstanding input events from Driver
         // This is important for remaining OnKeyUp events.
         RunIteration (ref state, firstIteration);

+ 0 - 3
Terminal.Gui/App/Application.cs

@@ -216,9 +216,6 @@ public static partial class Application
         Top = null;
         _cachedRunStateToplevel = null;
 
-        // MainLoop stuff
-        MainLoop?.Dispose ();
-        MainLoop = null;
         MainThreadId = -1;
         Iteration = null;
         EndAfterFirstIteration = false;

+ 0 - 24
Terminal.Gui/App/MainLoop/IMainLoopDriver.cs

@@ -1,24 +0,0 @@
-#nullable enable
-namespace Terminal.Gui.App;
-
-/// <summary>Interface to create a platform specific <see cref="MainLoop"/> driver.</summary>
-internal interface IMainLoopDriver
-{
-    /// <summary>Must report whether there are any events pending, or even block waiting for events.</summary>
-    /// <returns><see langword="true"/>, if there were pending events, <see langword="false"/> otherwise.</returns>
-    bool EventsPending ();
-
-    /// <summary>The iteration function.</summary>
-    void Iteration ();
-
-    /// <summary>Initializes the <see cref="MainLoop"/>, gets the calling main loop for the initialization.</summary>
-    /// <remarks>Call <see cref="TearDown"/> to release resources.</remarks>
-    /// <param name="mainLoop">Main loop.</param>
-    void Setup (MainLoop mainLoop);
-
-    /// <summary>Tears down the <see cref="MainLoop"/> driver. Releases resources created in <see cref="Setup"/>.</summary>
-    void TearDown ();
-
-    /// <summary>Wakes up the <see cref="MainLoop"/> that might be waiting on input, must be thread safe.</summary>
-    void Wakeup ();
-}

+ 0 - 122
Terminal.Gui/App/MainLoop/LegacyMainLoopDriver.cs

@@ -1,122 +0,0 @@
-#nullable enable
-//
-// LegacyMainLoopDriver.cs: IMainLoopDriver and MainLoop for legacy v1 driver based applications
-//
-// Authors:
-//   Miguel de Icaza ([email protected])
-//
-
-using System.Collections.ObjectModel;
-
-namespace Terminal.Gui.App;
-
-/// <summary>
-///     The main event loop of legacy v1 driver based applications.
-/// </summary>
-/// <remarks>
-///     <para>
-///         This class is provided for backward compatibility with the legacy FakeDriver implementation.
-///         New code should use the modern <see cref="ApplicationMainLoop{T}"/> architecture instead.
-///     </para>
-///     <para>
-///         Monitoring of file descriptors is only available on Unix, there does not seem to be a way of supporting this
-///         on Windows.
-///     </para>
-/// </remarks>
-[Obsolete ("This class is for legacy FakeDriver compatibility only. Use ApplicationMainLoop<T> for new code.")]
-public class MainLoop : IDisposable
-{
-    /// <summary>
-    /// Gets the class responsible for handling timeouts
-    /// </summary>
-    public ITimedEvents TimedEvents { get; } = new TimedEvents();
-
-    /// <summary>Creates a new MainLoop.</summary>
-    /// <remarks>Use <see cref="Dispose"/> to release resources.</remarks>
-    /// <param name="driver">
-    ///     The <see cref="IConsoleDriver"/> instance (one of the implementations FakeMainLoop, UnixMainLoop,
-    ///     NetMainLoop or WindowsMainLoop).
-    /// </param>
-    internal MainLoop (IMainLoopDriver driver)
-    {
-        MainLoopDriver = driver;
-        driver.Setup (this);
-    }
-
-
-    /// <summary>The current <see cref="IMainLoopDriver"/> in use.</summary>
-    /// <value>The main loop driver.</value>
-    internal IMainLoopDriver? MainLoopDriver { get; private set; }
-
-    /// <summary>Used for unit tests.</summary>
-    internal bool Running { get; set; }
-
-
-    /// <inheritdoc/>
-    public void Dispose ()
-    {
-        GC.SuppressFinalize (this);
-        Stop ();
-        Running = false;
-        MainLoopDriver?.TearDown ();
-        MainLoopDriver = null;
-    }
-
-
-    /// <summary>Determines whether there are pending events to be processed.</summary>
-    /// <remarks>
-    ///     You can use this method if you want to probe if events are pending. Typically used if you need to flush the
-    ///     input queue while still running some of your own code in your main thread.
-    /// </remarks>
-    internal bool EventsPending () { return MainLoopDriver!.EventsPending (); }
-
-
-    /// <summary>Runs the <see cref="MainLoop"/>. Used only for unit tests.</summary>
-    internal void Run ()
-    {
-        bool prev = Running;
-        Running = true;
-
-        while (Running)
-        {
-            EventsPending ();
-            RunIteration ();
-        }
-
-        Running = prev;
-    }
-
-    /// <summary>Runs one iteration of timers and file watches</summary>
-    /// <remarks>
-    ///     Use this to process all pending events (timers handlers and file watches).
-    ///     <code>
-    ///     while (main.EventsPending ()) RunIteration ();
-    ///   </code>
-    /// </remarks>
-    internal void RunIteration ()
-    {
-        RunAnsiScheduler ();
-
-        MainLoopDriver?.Iteration ();
-
-        TimedEvents.RunTimers ();
-    }
-
-    private void RunAnsiScheduler ()
-    {
-        Application.Driver?.GetRequestScheduler ().RunSchedule ();
-    }
-
-    /// <summary>Stops the main loop driver and calls <see cref="IMainLoopDriver.Wakeup"/>. Used only for unit tests.</summary>
-    internal void Stop ()
-    {
-        Running = false;
-        Wakeup ();
-    }
-
-
-    /// <summary>Wakes up the <see cref="MainLoop"/> that might be waiting on input.</summary>
-    internal void Wakeup () { MainLoopDriver?.Wakeup (); }
-
-
-}

+ 2 - 17
Terminal.Gui/App/MainLoop/MainLoopSyncContext.cs

@@ -10,23 +10,8 @@ internal sealed class MainLoopSyncContext : SynchronizationContext
 
     public override void Post (SendOrPostCallback d, object state)
     {
-        // Queue the task
-        if (ApplicationImpl.Instance.IsLegacy)
-        {
-            Application.MainLoop?.TimedEvents.Add (TimeSpan.Zero,
-                                                   () =>
-                                                   {
-                                                       d (state);
-
-                                                       return false;
-                                                   }
-                                                  );
-            Application.MainLoop?.Wakeup ();
-        }
-        else
-        {
-            ApplicationImpl.Instance.Invoke (() => { d (state); });
-        }
+        // Queue the task using the modern architecture
+        ApplicationImpl.Instance.Invoke (() => { d (state); });
     }
 
     //_mainLoop.Driver.Wakeup ();

+ 2 - 2
Terminal.Gui/Drivers/AnsiHandling/EscSeqUtils/EscSeqUtils.cs

@@ -931,7 +931,7 @@ public static class EscSeqUtils
             _isButtonClicked = false;
             _isButtonDoubleClicked = true;
 
-            Application.MainLoop?.TimedEvents.Add (TimeSpan.Zero,
+            ApplicationImpl.Instance.TimedEvents?.Add (TimeSpan.Zero,
                                           () =>
                                           {
                                               Task.Run (async () => await ProcessButtonDoubleClickedAsync ());
@@ -970,7 +970,7 @@ public static class EscSeqUtils
                 mouseFlags.Add (GetButtonClicked (buttonState));
                 _isButtonClicked = true;
 
-                Application.MainLoop?.TimedEvents.Add (TimeSpan.Zero,
+                ApplicationImpl.Instance.TimedEvents?.Add (TimeSpan.Zero,
                                               () =>
                                               {
                                                   Task.Run (async () => await ProcessButtonClickedAsync ());

+ 1 - 2
Terminal.Gui/Drivers/ConsoleDriver.cs

@@ -550,8 +550,7 @@ public abstract class ConsoleDriver : IConsoleDriver
     #region Setup & Teardown
 
     /// <summary>Initializes the driver</summary>
-    /// <returns>Returns an instance of <see cref="MainLoop"/> using the <see cref="IMainLoopDriver"/> for the driver.</returns>
-    public abstract MainLoop Init ();
+    public abstract void Init ();
 
     /// <summary>Ends the execution of the console driver.</summary>
     public abstract void End ();

+ 1 - 1
Terminal.Gui/Drivers/ConsoleDriverFacade.cs

@@ -365,7 +365,7 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
 
     /// <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 void Init () { throw new NotSupportedException (); }
 
     /// <summary>Ends the execution of the console driver.</summary>
     public void End ()

+ 1 - 27
Terminal.Gui/Drivers/FakeDriver/FakeDriver.cs

@@ -91,9 +91,7 @@ public class FakeDriver : ConsoleDriver
         FakeConsole.Clear ();
     }
 
-    private FakeMainLoop? _mainLoopDriver;
-
-    public override MainLoop Init ()
+    public override void Init ()
     {
         FakeConsole.MockKeyPresses.Clear ();
 
@@ -102,12 +100,6 @@ public class FakeDriver : ConsoleDriver
         FakeConsole.Clear ();
         ResizeScreen ();
         CurrentAttribute = new Attribute (Color.White, Color.Black);
-        //ClearContents ();
-
-        _mainLoopDriver = new FakeMainLoop (this);
-        _mainLoopDriver.MockKeyPressed = MockKeyPressedHandler;
-
-        return new MainLoop (_mainLoopDriver);
     }
 
     public override bool UpdateScreen ()
@@ -346,24 +338,6 @@ public class FakeDriver : ConsoleDriver
 
     private CursorVisibility _savedCursorVisibility;
 
-    private void MockKeyPressedHandler (ConsoleKeyInfo consoleKeyInfo)
-    {
-        if (consoleKeyInfo.Key == ConsoleKey.Packet)
-        {
-            consoleKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (consoleKeyInfo);
-        }
-
-        KeyCode map = MapKey (consoleKeyInfo);
-
-        if (IsValidInput (map, out map))
-        {
-            OnKeyDown (new (map));
-            OnKeyUp (new (map));
-        }
-
-        //OnKeyPressed (new KeyEventArgs (map));
-    }
-
     /// <inheritdoc/>
     public override bool GetCursorVisibility (out CursorVisibility visibility)
     {

+ 0 - 38
Terminal.Gui/Drivers/FakeDriver/FakeMainLoop.cs

@@ -1,38 +0,0 @@
-
-namespace Terminal.Gui.Drivers;
-
-internal class FakeMainLoop : IMainLoopDriver
-{
-    public Action<ConsoleKeyInfo> MockKeyPressed;
-
-    public FakeMainLoop (IConsoleDriver consoleDriver = null)
-    {
-        // No implementation needed for FakeMainLoop
-    }
-
-    public void Setup (MainLoop mainLoop)
-    {
-        // No implementation needed for FakeMainLoop
-    }
-
-    public void Wakeup ()
-    {
-        // No implementation needed for FakeMainLoop
-    }
-
-    public bool EventsPending ()
-    {
-        // Always return true for FakeMainLoop
-        return true;
-    }
-
-    public void Iteration ()
-    {
-        if (FakeConsole.MockKeyPresses.Count > 0)
-        {
-            MockKeyPressed?.Invoke (FakeConsole.MockKeyPresses.Pop ());
-        }
-    }
-
-    public void TearDown () { }
-}

+ 1 - 2
Terminal.Gui/Drivers/IConsoleDriver.cs

@@ -214,8 +214,7 @@ public interface IConsoleDriver
     void UpdateCursor ();
 
     /// <summary>Initializes the driver</summary>
-    /// <returns>Returns an instance of <see cref="MainLoop"/> using the <see cref="IMainLoopDriver"/> for the driver.</returns>
-    MainLoop Init ();
+    void Init ();
 
     /// <summary>Ends the execution of the console driver.</summary>
     void End ();

+ 0 - 17
Tests/UnitTests/Application/ApplicationTests.cs

@@ -159,7 +159,6 @@ public class ApplicationTests
         Application.Shutdown ();
 
         Assert.Null (Application.Top);
-        Assert.Null (Application.MainLoop);
         Assert.Null (Application.Driver);
     }
 
@@ -316,7 +315,6 @@ public class ApplicationTests
             // Don't check Application.Force16Colors
             //Assert.False (Application.Force16Colors);
             Assert.Null (Application.Driver);
-            Assert.Null (Application.MainLoop);
             Assert.False (Application.EndAfterFirstIteration);
 
             // Commented out because if CM changed the defaults, those changes should
@@ -472,7 +470,6 @@ public class ApplicationTests
         Application.Shutdown ();
 
         Assert.Null (Application.Top);
-        Assert.Null (Application.MainLoop);
         Assert.Null (Application.Driver);
     }
 
@@ -486,7 +483,6 @@ public class ApplicationTests
         Application.Shutdown ();
 
         Assert.Null (Application.Top);
-        Assert.Null (Application.MainLoop);
         Assert.Null (Application.Driver);
     }
 
@@ -522,14 +518,12 @@ public class ApplicationTests
         Application.End (runstate);
 
         Assert.NotNull (Application.Top);
-        Assert.NotNull (Application.MainLoop);
         Assert.NotNull (Application.Driver);
 
         topLevel.Dispose ();
         Application.Shutdown ();
 
         Assert.Null (Application.Top);
-        Assert.Null (Application.MainLoop);
         Assert.Null (Application.Driver);
     }
 
@@ -676,7 +670,6 @@ public class ApplicationTests
         Application.Shutdown ();
 
         Assert.Null (Application.Top);
-        Assert.Null (Application.MainLoop);
         Assert.Null (Application.Driver);
     }
 
@@ -700,7 +693,6 @@ public class ApplicationTests
         Application.Shutdown ();
 
         Assert.Null (Application.Top);
-        Assert.Null (Application.MainLoop);
         Assert.Null (Application.Driver);
     }
 
@@ -734,7 +726,6 @@ public class ApplicationTests
         Application.Shutdown ();
 
         Assert.Null (Application.Top);
-        Assert.Null (Application.MainLoop);
         Assert.Null (Application.Driver);
     }
 
@@ -752,7 +743,6 @@ public class ApplicationTests
         Application.Shutdown ();
 
         Assert.Null (Application.Top);
-        Assert.Null (Application.MainLoop);
         Assert.Null (Application.Driver);
     }
 
@@ -774,7 +764,6 @@ public class ApplicationTests
         Application.Shutdown ();
 
         Assert.Null (Application.Top);
-        Assert.Null (Application.MainLoop);
         Assert.Null (Application.Driver);
 
         a.After (null);
@@ -793,7 +782,6 @@ public class ApplicationTests
         Application.Shutdown ();
 
         Assert.Null (Application.Top);
-        Assert.Null (Application.MainLoop);
         Assert.Null (Application.Driver);
     }
 
@@ -812,7 +800,6 @@ public class ApplicationTests
         Application.Shutdown ();
 
         Assert.Null (Application.Top);
-        Assert.Null (Application.MainLoop);
         Assert.Null (Application.Driver);
     }
 
@@ -829,7 +816,6 @@ public class ApplicationTests
         Application.Shutdown ();
 
         Assert.Null (Application.Top);
-        Assert.Null (Application.MainLoop);
         Assert.Null (Application.Driver);
     }
 
@@ -849,7 +835,6 @@ public class ApplicationTests
         top.Dispose ();
         Application.Shutdown ();
         Assert.Null (Application.Top);
-        Assert.Null (Application.MainLoop);
         Assert.Null (Application.Driver);
     }
 
@@ -872,7 +857,6 @@ public class ApplicationTests
         top.Dispose ();
         Application.Shutdown ();
         Assert.Null (Application.Top);
-        Assert.Null (Application.MainLoop);
         Assert.Null (Application.Driver);
     }
 
@@ -892,7 +876,6 @@ public class ApplicationTests
         top.Dispose ();
         Application.Shutdown ();
         Assert.Null (Application.Top);
-        Assert.Null (Application.MainLoop);
         Assert.Null (Application.Driver);
     }
 

+ 0 - 940
Tests/UnitTests/Application/MainLoopTests.cs

@@ -1,940 +0,0 @@
-using System.Diagnostics;
-using Xunit.Abstractions;
-
-// Alias Console to MockConsole so we don't accidentally use Console
-
-namespace UnitTests.ApplicationTests;
-
-/// <summary>Tests MainLoop using the FakeMainLoop.</summary>
-public class MainLoopTests
-{
-    private readonly ITestOutputHelper _output;
-
-    public MainLoopTests (ITestOutputHelper output)
-    {
-        _output = output;
-        ConsoleDriver.RunningUnitTests = true;
-    }
-
-    private static Button btn;
-    private static string cancel;
-    private static string clickMe;
-    private static int four;
-    private static int one;
-    private static string pewPew;
-    private static bool taskCompleted;
-
-    // TODO: EventsPending tests
-    // - wait = true
-    // - wait = false
-
-    // TODO: Add IMainLoop tests
-    private static int three;
-    private static int total;
-    private static int two;
-    private static int zero;
-
-    // See Also ConsoleDRivers/MainLoopDriverTests.cs for tests of the MainLoopDriver
-
-    // Idle Handler tests
-    [Fact]
-    public void AddTimeout_Adds_And_Removes ()
-    {
-        var ml = new MainLoop (new FakeMainLoop ());
-
-        Func<bool> fnTrue = () => true;
-        Func<bool> fnFalse = () => false;
-
-        var a = ml.TimedEvents.Add (TimeSpan.Zero, fnTrue);
-        var b = ml.TimedEvents.Add (TimeSpan.Zero, fnFalse);
-
-        Assert.Equal (2, ml.TimedEvents.Timeouts.Count);
-        Assert.Equal (fnTrue, ml.TimedEvents.Timeouts.ElementAt (0).Value.Callback);
-        Assert.NotEqual (fnFalse, ml.TimedEvents.Timeouts.ElementAt (0).Value.Callback);
-
-        Assert.True (ml.TimedEvents.Remove (a));
-        Assert.Single (ml.TimedEvents.Timeouts);
-
-        // 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.TimedEvents.Remove (a));
-
-        Assert.True (ml.TimedEvents.Remove (b));
-
-        // 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.TimedEvents.Remove(b));
-    }
-
-    [Fact]
-    public void AddTimeout_Function_GetsCalled_OnIteration ()
-    {
-        var ml = new MainLoop (new FakeMainLoop ());
-
-        var functionCalled = 0;
-
-        Func<bool> fn = () =>
-                        {
-                            functionCalled++;
-
-                            return true;
-                        };
-
-        ml.TimedEvents.Add (TimeSpan.Zero, fn);
-        ml.RunIteration ();
-        Assert.Equal (1, functionCalled);
-    }
-
-    [Fact]
-    public void AddTimeout_Twice_Returns_False_Called_Twice ()
-    {
-        var ml = new MainLoop (new FakeMainLoop ());
-
-        var functionCalled = 0;
-
-        Func<bool> fn1 = () =>
-                         {
-                             functionCalled++;
-
-                             return false;
-                         };
-
-        // Force stop if 10 iterations
-        var stopCount = 0;
-
-        Func<bool> fnStop = () =>
-                            {
-                                stopCount++;
-
-                                if (stopCount == 10)
-                                {
-                                    ml.Stop ();
-                                }
-
-                                return true;
-                            };
-
-        var a = ml.TimedEvents.Add (TimeSpan.Zero, fnStop);
-        var b = ml.TimedEvents.Add (TimeSpan.Zero, fn1);
-        ml.Run ();
-
-        Assert.True (ml.TimedEvents.Remove(a));
-        Assert.False (ml.TimedEvents.Remove (a));
-
-        // Cannot remove b because it returned false i.e. auto removes itself
-        Assert.False (ml.TimedEvents.Remove (b));
-
-        Assert.Equal (1, functionCalled);
-    }
-
-    [Fact]
-    public void AddTimeoutTwice_Function_CalledTwice ()
-    {
-        var ml = new MainLoop (new FakeMainLoop ());
-
-        var functionCalled = 0;
-
-        Func<bool> fn = () =>
-                        {
-                            functionCalled++;
-
-                            return true;
-                        };
-
-        var a = ml.TimedEvents.Add (TimeSpan.Zero, fn);
-        var b = ml.TimedEvents.Add (TimeSpan.Zero, fn);
-        ml.RunIteration ();
-        Assert.Equal (2, functionCalled);
-        Assert.Equal (2, ml.TimedEvents.Timeouts.Count);
-
-        functionCalled = 0;
-        Assert.True (ml.TimedEvents.Remove (a));
-        Assert.Single (ml.TimedEvents.Timeouts);
-        ml.RunIteration ();
-        Assert.Equal (1, functionCalled);
-
-        functionCalled = 0;
-        Assert.True (ml.TimedEvents.Remove (b));
-        Assert.Empty (ml.TimedEvents.Timeouts);
-        ml.RunIteration ();
-        Assert.Equal (0, functionCalled);
-        Assert.False (ml.TimedEvents.Remove (b));
-    }
-
-    [Fact]
-    public void AddThenRemoveIdle_Function_NotCalled ()
-    {
-        var ml = new MainLoop (new FakeMainLoop ());
-
-        var functionCalled = 0;
-
-        Func<bool> fn = () =>
-                        {
-                            functionCalled++;
-
-                            return true;
-                        };
-
-        var a = ml.TimedEvents.Add (TimeSpan.Zero, fn);
-        Assert.True (ml.TimedEvents.Remove (a));
-        ml.RunIteration ();
-        Assert.Equal (0, functionCalled);
-    }
-
-    // Timeout Handler Tests
-    [Fact]
-    public void AddTimer_Adds_Removes_NoFaults ()
-    {
-        var ml = new MainLoop (new FakeMainLoop ());
-        var ms = 100;
-
-        var callbackCount = 0;
-
-        Func<bool> callback = () =>
-                              {
-                                  callbackCount++;
-
-                                  return true;
-                              };
-
-        object token = ml.TimedEvents.Add (TimeSpan.FromMilliseconds (ms), callback);
-
-        Assert.True (ml.TimedEvents.Remove (token));
-
-        // BUGBUG: This should probably fault?
-        // Must return a boolean.
-        Assert.False (ml.TimedEvents.Remove (token));
-    }
-
-    [Fact]
-    public async Task AddTimer_Duplicate_Keys_Not_Allowed ()
-    {
-        var ml = new MainLoop (new FakeMainLoop ());
-        const int ms = 100;
-        object token1 = null, token2 = null;
-
-        var callbackCount = 0;
-
-        Func<bool> callback = () =>
-                              {
-                                  callbackCount++;
-
-                                  if (callbackCount == 2)
-                                  {
-                                      ml.Stop ();
-                                  }
-
-                                  return true;
-                              };
-
-        var task1 = new Task (() => token1 = ml.TimedEvents.Add (TimeSpan.FromMilliseconds (ms), callback));
-        var task2 = new Task (() => token2 = ml.TimedEvents.Add (TimeSpan.FromMilliseconds (ms), callback));
-        Assert.Null (token1);
-        Assert.Null (token2);
-        task1.Start ();
-        task2.Start ();
-        ml.Run ();
-        Assert.NotNull (token1);
-        Assert.NotNull (token2);
-        await Task.WhenAll (task1, task2);
-        Assert.True (ml.TimedEvents.Remove (token1));
-        Assert.True (ml.TimedEvents.Remove (token2));
-
-        Assert.Equal (2, callbackCount);
-    }
-
-    // Timeout Handler Tests
-    [Fact]
-    public void AddTimer_EventFired ()
-    {
-        var ml = new MainLoop (new FakeMainLoop ());
-        var ms = 100;
-
-        // Use Stopwatch ticks since TimedEvents now uses Stopwatch.GetTimestamp internally
-        long originTicks = Stopwatch.GetTimestamp () * TimeSpan.TicksPerSecond / Stopwatch.Frequency;
-
-        var callbackCount = 0;
-
-        Func<bool> callback = () =>
-                              {
-                                  callbackCount++;
-
-                                  return true;
-                              };
-
-        object sender = null;
-        TimeoutEventArgs args = null;
-
-        ml.TimedEvents.Added += (s, e) =>
-                                       {
-                                           sender = s;
-                                           args = e;
-                                       };
-
-        object token = ml.TimedEvents.Add (TimeSpan.FromMilliseconds (ms), callback);
-
-        Assert.Same (ml.TimedEvents, sender);
-        Assert.NotNull (args.Timeout);
-        Assert.True (args.Ticks - originTicks >= 100 * TimeSpan.TicksPerMillisecond);
-    }
-
-    [Fact]
-    public void AddTimer_In_Parallel_Wont_Throw ()
-    {
-        var ml = new MainLoop (new FakeMainLoop ());
-        const int ms = 100;
-        object token1 = null, token2 = null;
-
-        var callbackCount = 0;
-
-        Func<bool> callback = () =>
-                              {
-                                  callbackCount++;
-
-                                  if (callbackCount == 2)
-                                  {
-                                      ml.Stop ();
-                                  }
-
-                                  return true;
-                              };
-
-        Parallel.Invoke (
-                         () => token1 = ml.TimedEvents.Add (TimeSpan.FromMilliseconds (ms), callback),
-                         () => token2 = ml.TimedEvents.Add (TimeSpan.FromMilliseconds (ms), callback)
-                        );
-        ml.Run ();
-        Assert.NotNull (token1);
-        Assert.NotNull (token2);
-        Assert.True (ml.TimedEvents.Remove (token1));
-        Assert.True (ml.TimedEvents.Remove (token2));
-
-        Assert.Equal (2, callbackCount);
-    }
-
-    [Fact]
-    public void AddTimer_Remove_NotCalled ()
-    {
-        var ml = new MainLoop (new FakeMainLoop ());
-        TimeSpan ms = TimeSpan.FromMilliseconds (50);
-
-        // Force stop if 10 iterations
-        var stopCount = 0;
-
-        Func<bool> fnStop = () =>
-                            {
-                                stopCount++;
-
-                                if (stopCount == 10)
-                                {
-                                    ml.Stop ();
-                                }
-
-                                return true;
-                            };
-        ml.TimedEvents.Add (TimeSpan.Zero, fnStop);
-
-        var callbackCount = 0;
-
-        Func<bool> callback = () =>
-                              {
-                                  callbackCount++;
-
-                                  return true;
-                              };
-
-        object token = ml.TimedEvents.Add (ms, callback);
-        Assert.True (ml.TimedEvents.Remove (token));
-        ml.Run ();
-        Assert.Equal (0, callbackCount);
-    }
-
-    [Fact]
-    public void AddTimer_ReturnFalse_StopsBeingCalled ()
-    {
-        var ml = new MainLoop (new FakeMainLoop ());
-        TimeSpan ms = TimeSpan.FromMilliseconds (50);
-
-        // Force stop if 10 iterations
-        var stopCount = 0;
-
-        Func<bool> fnStop = () =>
-                            {
-                                Thread.Sleep (10); // Sleep to enable timer to fire
-                                stopCount++;
-
-                                if (stopCount == 10)
-                                {
-                                    ml.Stop ();
-                                }
-
-                                return true;
-                            };
-        ml.TimedEvents.Add (TimeSpan.Zero, fnStop);
-
-        var callbackCount = 0;
-
-        Func<bool> callback = () =>
-                              {
-                                  callbackCount++;
-
-                                  return false;
-                              };
-
-        object token = ml.TimedEvents.Add (ms, callback);
-        ml.Run ();
-        Assert.Equal (1, callbackCount);
-        Assert.Equal (10, stopCount);
-        Assert.False (ml.TimedEvents.Remove (token));
-    }
-
-    [Fact]
-    public void AddTimer_Run_Called ()
-    {
-        var ml = new MainLoop (new FakeMainLoop ());
-        var ms = 100;
-
-        var callbackCount = 0;
-
-        Func<bool> callback = () =>
-                              {
-                                  callbackCount++;
-                                  ml.Stop ();
-
-                                  return true;
-                              };
-
-        object token = ml.TimedEvents.Add (TimeSpan.FromMilliseconds (ms), callback);
-        ml.Run ();
-        Assert.True (ml.TimedEvents.Remove (token));
-
-        Assert.Equal (1, callbackCount);
-    }
-
-    [Fact]
-    public void AddTimer_Run_CalledAtApproximatelyRightTime ()
-    {
-        var ml = new MainLoop (new FakeMainLoop ());
-        TimeSpan ms = TimeSpan.FromMilliseconds (50);
-        var watch = new Stopwatch ();
-
-        var callbackCount = 0;
-
-        Func<bool> callback = () =>
-                              {
-                                  watch.Stop ();
-                                  callbackCount++;
-                                  ml.Stop ();
-
-                                  return true;
-                              };
-
-        object token = ml.TimedEvents.Add (ms, callback);
-        watch.Start ();
-        ml.Run ();
-
-        // +/- 100ms should be good enuf
-        // https://github.com/xunit/assert.xunit/pull/25
-        Assert.Equal (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100));
-
-        Assert.True (ml.TimedEvents.Remove (token));
-        Assert.Equal (1, callbackCount);
-    }
-
-    [Fact]
-    public void AddTimer_Run_CalledTwiceApproximatelyRightTime ()
-    {
-        var ml = new MainLoop (new FakeMainLoop ());
-        TimeSpan ms = TimeSpan.FromMilliseconds (50);
-        var watch = new Stopwatch ();
-
-        var callbackCount = 0;
-
-        Func<bool> callback = () =>
-                              {
-                                  callbackCount++;
-
-                                  if (callbackCount == 2)
-                                  {
-                                      watch.Stop ();
-                                      ml.Stop ();
-                                  }
-
-                                  return true;
-                              };
-
-        object token = ml.TimedEvents.Add (ms, callback);
-        watch.Start ();
-        ml.Run ();
-
-        // +/- 100ms should be good enuf
-        // https://github.com/xunit/assert.xunit/pull/25
-        Assert.Equal (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100));
-
-        Assert.True (ml.TimedEvents.Remove (token));
-        Assert.Equal (2, callbackCount);
-    }
-
-    [Fact]
-    public void CheckTimersAndIdleHandlers_NoTimers_Returns_False ()
-    {
-        var ml = new MainLoop (new FakeMainLoop ());
-        bool retVal = ml.TimedEvents.CheckTimers(out int waitTimeOut);
-        Assert.False (retVal);
-        Assert.Equal (-1, waitTimeOut);
-    }
-
-    [Fact]
-    public void CheckTimersAndIdleHandlers_NoTimers_WithIdle_Returns_True ()
-    {
-        var ml = new MainLoop (new FakeMainLoop ());
-        Func<bool> fnTrue = () => true;
-
-        ml.TimedEvents.Add (TimeSpan.Zero, fnTrue);
-        bool retVal = ml.TimedEvents.CheckTimers(out int waitTimeOut);
-        Assert.True (retVal);
-        Assert.Equal (0, waitTimeOut);
-    }
-
-    [Fact]
-    public void CheckTimersAndIdleHandlers_With1Timer_Returns_Timer ()
-    {
-        var ml = new MainLoop (new FakeMainLoop ());
-        TimeSpan ms = TimeSpan.FromMilliseconds (50);
-
-        static bool Callback () { return false; }
-
-        _ = ml.TimedEvents.Add (ms, Callback);
-        bool retVal = ml.TimedEvents.CheckTimers (out int waitTimeOut);
-
-        Assert.True (retVal);
-
-        // It should take < 10ms to execute to here
-        Assert.True (ms.TotalMilliseconds <= waitTimeOut + 10);
-    }
-
-    [Fact]
-    public void CheckTimersAndIdleHandlers_With2Timers_Returns_Timer ()
-    {
-        var ml = new MainLoop (new FakeMainLoop ());
-        TimeSpan ms = TimeSpan.FromMilliseconds (50);
-
-        static bool Callback () { return false; }
-
-        _ = ml.TimedEvents.Add (ms, Callback);
-        _ = ml.TimedEvents.Add (ms, Callback);
-        bool retVal = ml.TimedEvents.CheckTimers (out int waitTimeOut);
-
-        Assert.True (retVal);
-
-        // It should take < 10ms to execute to here
-        Assert.True (ms.TotalMilliseconds <= waitTimeOut + 10);
-    }
-
-    [Fact]
-    public void False_Idle_Stops_It_Being_Called_Again ()
-    {
-        var ml = new MainLoop (new FakeMainLoop ());
-
-        var functionCalled = 0;
-
-        Func<bool> fn1 = () =>
-                         {
-                             functionCalled++;
-
-                             if (functionCalled == 10)
-                             {
-                                 return false;
-                             }
-
-                             return true;
-                         };
-
-        // Force stop if 20 iterations
-        var stopCount = 0;
-
-        Func<bool> fnStop = () =>
-                            {
-                                stopCount++;
-
-                                if (stopCount == 20)
-                                {
-                                    ml.Stop ();
-                                }
-
-                                return true;
-                            };
-
-        var a = ml.TimedEvents.Add (TimeSpan.Zero, fnStop);
-        var b = ml.TimedEvents.Add (TimeSpan.Zero, fn1);
-        ml.Run ();
-        Assert.True (ml.TimedEvents.Remove (a));
-        Assert.False (ml.TimedEvents.Remove (a));
-
-        Assert.Equal (10, functionCalled);
-        Assert.Equal (20, stopCount);
-    }
-
-    [Fact]
-    public void Internal_Tests ()
-    {
-        var testMainloop = new TestMainloop ();
-        var mainloop = new MainLoop (testMainloop);
-        Assert.Empty (mainloop.TimedEvents.Timeouts);
-
-        Assert.NotNull (
-                        new Terminal.Gui.App.Timeout { Span = new (), Callback = () => true }
-                       );
-    }
-
-    [Theory]
-    [MemberData (nameof (TestAddTimeout))]
-    public void Mainloop_Invoke_Or_AddTimeout_Can_Be_Used_For_Events_Or_Actions (
-        Action action,
-        string pclickMe,
-        string pcancel,
-        string ppewPew,
-        int pzero,
-        int pone,
-        int ptwo,
-        int pthree,
-        int pfour
-    )
-    {
-        // TODO: Expand this test to test all drivers
-        Application.Init (null, "fakedriver");
-
-        total = 0;
-        btn = null;
-        clickMe = pclickMe;
-        cancel = pcancel;
-        pewPew = ppewPew;
-        zero = pzero;
-        one = pone;
-        two = ptwo;
-        three = pthree;
-        four = pfour;
-        taskCompleted = false;
-
-        var btnLaunch = new Button { Text = "Open Window" };
-
-        btnLaunch.Accepting += (s, e) => action ();
-
-        var top = new Toplevel ();
-        top.Add (btnLaunch);
-
-        int iterations = -1;
-
-        Application.Iteration += (s, a) =>
-                                 {
-                                     iterations++;
-
-                                     if (iterations == 0)
-                                     {
-                                         Assert.Null (btn);
-                                         Assert.Equal (zero, total);
-                                         Assert.False (btnLaunch.NewKeyDownEvent (Key.Space));
-
-                                         if (btn == null)
-                                         {
-                                             Assert.Null (btn);
-                                             Assert.Equal (zero, total);
-                                         }
-                                         else
-                                         {
-                                             Assert.Equal (clickMe, btn.Text);
-                                             Assert.Equal (four, total);
-                                         }
-                                     }
-                                     else if (iterations == 1)
-                                     {
-                                         Assert.Equal (clickMe, btn.Text);
-                                         Assert.Equal (zero, total);
-                                         Assert.False (btn.NewKeyDownEvent (Key.Space));
-                                         Assert.Equal (cancel, btn.Text);
-                                         Assert.Equal (one, total);
-                                     }
-                                     else if (taskCompleted)
-                                     {
-                                         Application.RequestStop ();
-                                     }
-                                 };
-
-        Application.Run (top);
-        top.Dispose ();
-
-        Assert.True (taskCompleted);
-        Assert.Equal (clickMe, btn.Text);
-        Assert.Equal (four, total);
-
-        Application.Shutdown ();
-    }
-    
-    [Fact]
-    public void RemoveIdle_Function_NotCalled ()
-    {
-        var ml = new MainLoop (new FakeMainLoop ());
-
-        var functionCalled = 0;
-
-        Func<bool> fn = () =>
-                        {
-                            functionCalled++;
-
-                            return true;
-                        };
-
-        Assert.False (ml.TimedEvents.Remove ("flibble"));
-        ml.RunIteration ();
-        Assert.Equal (0, functionCalled);
-    }
-
-    [Fact]
-    public void Run_Runs_Idle_Stop_Stops_Idle ()
-    {
-        var ml = new MainLoop (new FakeMainLoop ());
-
-        var functionCalled = 0;
-
-        Func<bool> fn = () =>
-                        {
-                            functionCalled++;
-
-                            if (functionCalled == 10)
-                            {
-                                ml.Stop ();
-                            }
-
-                            return true;
-                        };
-
-        var a = ml.TimedEvents.Add (TimeSpan.Zero, fn);
-        ml.Run ();
-        Assert.True (ml.TimedEvents.Remove (a));
-
-        Assert.Equal (10, functionCalled);
-    }
-
-    [Theory]
-    [InlineData ("fake")]
-    [InlineData ("windows")]
-    [InlineData ("dotnet")]
-    [InlineData ("unix")]
-    public void Application_Invoke_Run_TimedEvents (string driverName)
-    {
-        // Arrange
-        Application.Init (driverName: driverName);
-        var functionCalled = 0;
-        var stopwatch = new Stopwatch ();
-
-        // Act
-        Application.Invoke (() =>
-                            {
-                                // Stop the stopwatch *after* the function is called.
-                                functionCalled++;
-                                stopwatch.Stop ();
-                                Application.RequestStop ();
-                            });
-
-        // Start timing just before running the application loop.
-        stopwatch.Start ();
-        Application.Run ();
-
-        // Assert
-        Assert.NotNull (Application.Top);
-        Application.Top.Dispose ();
-        Application.Shutdown ();
-        Assert.Equal (1, functionCalled);
-
-        // Output the elapsed time for this test case.
-        // ReSharper disable once Xunit.XunitTestWithConsoleOutput
-        // ReSharper disable once LocalizableElement
-        Console.WriteLine ($"[{driverName}] Duration: {stopwatch.Elapsed.TotalMilliseconds:F2} ms");
-
-        // Output elapsed duration to xUnit's test output
-        _output.WriteLine ($"[{driverName}] Duration: {stopwatch.Elapsed.TotalMilliseconds:F2} ms");
-    }
-
-    [Theory]
-    [InlineData ("fake")]
-    [InlineData ("windows")]
-    [InlineData ("dotnet")]
-    [InlineData ("unix")]
-    public void Application_AddTimeout_Run_TimedEvents (string driverName)
-    {
-        // Arrange
-        Application.Init (driverName: driverName);
-        var functionCalled = 0;
-        var stopwatch = new Stopwatch ();
-
-        // Act
-        bool Function ()
-        {
-            functionCalled++;
-
-            if (functionCalled == 10 && Application.Top is { Running: true })
-            {
-                stopwatch.Stop ();
-                Application.RequestStop ();
-
-                return false;
-            }
-
-            return true;
-        }
-
-        Application.AddTimeout (TimeSpan.FromMilliseconds (1), Function);
-
-        // Start timing just before running the application loop.
-        stopwatch.Start ();
-        Application.Run ();
-
-        // Assert
-        Assert.NotNull (Application.Top);
-        Application.Top.Dispose ();
-        Application.Shutdown ();
-        Assert.Equal (10, functionCalled);
-
-        // Output the elapsed time for this test case.
-        // ReSharper disable once Xunit.XunitTestWithConsoleOutput
-        // ReSharper disable once LocalizableElement
-        Console.WriteLine ($"[{driverName}] Duration: {stopwatch.Elapsed.TotalMilliseconds:F2} ms");
-
-        // Output elapsed duration to xUnit's test output
-        _output.WriteLine ($"[{driverName}] Duration: {stopwatch.Elapsed.TotalMilliseconds:F2} ms");
-    }
-
-    public static IEnumerable<object []> TestAddTimeout
-    {
-        get
-        {
-            // Goes fine
-            Action a1 = StartWindow;
-
-            yield return new object [] { a1, "Click Me", "Cancel", "Pew Pew", 0, 1, 2, 3, 4 };
-
-            // Also goes fine
-            Action a2 = () => Application.Invoke (StartWindow);
-
-            yield return new object [] { a2, "Click Me", "Cancel", "Pew Pew", 0, 1, 2, 3, 4 };
-        }
-    }
-
-    private static async void RunAsyncTest (object sender, EventArgs e)
-    {
-        Assert.Equal (clickMe, btn.Text);
-        Assert.Equal (zero, total);
-
-        btn.Text = "Cancel";
-        Interlocked.Increment (ref total);
-        btn.SetNeedsDraw ();
-
-        await Task.Run (
-                        () =>
-                        {
-                            try
-                            {
-                                Assert.Equal (cancel, btn.Text);
-                                Assert.Equal (one, total);
-
-                                RunSql ();
-                            }
-                            finally
-                            {
-                                SetReadyToRun ();
-                            }
-                        }
-                       )
-                  .ContinueWith (
-                                 async (s, e) =>
-                                 {
-                                     await Task.Delay (1000);
-                                     Assert.Equal (clickMe, btn.Text);
-                                     Assert.Equal (three, total);
-
-                                     Interlocked.Increment (ref total);
-
-                                     Assert.Equal (clickMe, btn.Text);
-                                     Assert.Equal (four, total);
-
-                                     taskCompleted = true;
-                                 },
-                                 TaskScheduler.FromCurrentSynchronizationContext ()
-                                );
-    }
-
-    private static void RunSql ()
-    {
-        Thread.Sleep (100);
-        Assert.Equal (cancel, btn.Text);
-        Assert.Equal (one, total);
-
-        Application.Invoke (
-                            () =>
-                            {
-                                btn.Text = "Pew Pew";
-                                Interlocked.Increment (ref total);
-                                btn.SetNeedsDraw ();
-                            }
-                           );
-    }
-
-    private static void SetReadyToRun ()
-    {
-        Thread.Sleep (100);
-        Assert.Equal (pewPew, btn.Text);
-        Assert.Equal (two, total);
-
-        Application.Invoke (
-                            () =>
-                            {
-                                btn.Text = "Click Me";
-                                Interlocked.Increment (ref total);
-                                btn.SetNeedsDraw ();
-                            }
-                           );
-    }
-
-    private static void StartWindow ()
-    {
-        var startWindow = new Window { Modal = true };
-
-        btn = new() { Text = "Click Me" };
-
-        btn.Accepting += RunAsyncTest;
-
-        var totalbtn = new Button { X = Pos.Right (btn), Text = "total" };
-
-        totalbtn.Accepting += (s, e) => { MessageBox.Query ("Count", $"Count is {total}", "Ok"); };
-
-        startWindow.Add (btn);
-        startWindow.Add (totalbtn);
-
-        Application.Run (startWindow);
-
-        Assert.Equal (clickMe, btn.Text);
-        Assert.Equal (four, total);
-
-        Application.RequestStop ();
-    }
-
-    private class MillisecondTolerance : IEqualityComparer<TimeSpan>
-    {
-        public MillisecondTolerance (int tolerance) { _tolerance = tolerance; }
-        private readonly int _tolerance;
-        public bool Equals (TimeSpan x, TimeSpan y) { return Math.Abs (x.Milliseconds - y.Milliseconds) <= _tolerance; }
-        public int GetHashCode (TimeSpan obj) { return obj.GetHashCode (); }
-    }
-
-    private class TestMainloop : IMainLoopDriver
-    {
-        private MainLoop mainLoop;
-        public bool EventsPending () { throw new NotImplementedException (); }
-        public void Iteration () { throw new NotImplementedException (); }
-        public void TearDown () { throw new NotImplementedException (); }
-        public void Setup (MainLoop mainLoop) { this.mainLoop = mainLoop; }
-        public void Wakeup () { throw new NotImplementedException (); }
-    }
-}

+ 0 - 1
Tests/UnitTests/Application/RunStateTests.cs

@@ -45,7 +45,6 @@ public class RunStateTests
 #endif
 
         Assert.Null (Application.Top);
-    //    Assert.Null (Application.MainLoop);
         Assert.Null (Application.Driver);
     }
 

+ 2 - 2
Tests/UnitTests/ConsoleDrivers/ConsoleDriverTests.cs

@@ -50,8 +50,8 @@ public class ConsoleDriverTests
     public void Init_Inits (Type driverType)
     {
         var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
-        MainLoop ml = driver.Init ();
-        Assert.NotNull (ml);
+        driver.Init ();
+        // Note: MainLoop is no longer returned from Init() as part of legacy MainLoop removal
         Assert.NotNull (driver.Clipboard);
         Console.ForegroundColor = ConsoleColor.Red;
         Assert.Equal (ConsoleColor.Red, Console.ForegroundColor);

+ 0 - 1
Tests/UnitTests/View/Draw/AllViewsDrawTests.cs

@@ -12,7 +12,6 @@ public class AllViewsDrawTests (ITestOutputHelper output) : TestsAllViews
     {
         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);
 

+ 0 - 1
Tests/UnitTests/View/Layout/LayoutTests.cs

@@ -12,7 +12,6 @@ public class LayoutTests (ITestOutputHelper output) : TestsAllViews
     {
 
         // Required for spinner view that wants to register timeouts
-        Application.MainLoop = new MainLoop (new FakeMainLoop (Application.Driver));
 
         var view = (View)CreateInstanceIfNotGeneric (viewType);
 

+ 0 - 1
Tests/UnitTests/Views/AllViewsTests.cs

@@ -14,7 +14,6 @@ public class AllViewsTests (ITestOutputHelper output) : TestsAllViews
     public void AllViews_Center_Properly (Type viewType)
     {
         // Required for spinner view that wants to register timeouts
-        Application.MainLoop = new (new FakeMainLoop (Application.Driver));
 
         var view = CreateInstanceIfNotGeneric (viewType);
 

+ 0 - 2
Tests/UnitTests/Views/Menuv1/MenuBarv1Tests.cs

@@ -623,7 +623,6 @@ public class MenuBarv1Tests (ITestOutputHelper output)
         Application.RaiseMouseEvent (new () { ScreenPosition = new (20, 5), Flags = MouseFlags.Button1Clicked });
 
         // Need to fool MainLoop into thinking it's running
-        Application.MainLoop.Running = true;
         AutoInitShutdownAttribute.RunIteration ();
         Assert.Equal (items [0], menu.Menus [0].Title);
 
@@ -815,7 +814,6 @@ public class MenuBarv1Tests (ITestOutputHelper output)
         Application.RaiseMouseEvent (new () { ScreenPosition = new (20, 5), Flags = MouseFlags.Button1Clicked });
 
         // Need to fool MainLoop into thinking it's running
-        Application.MainLoop.Running = true;
         AutoInitShutdownAttribute.RunIteration ();
         Assert.Equal (items [0], menu.Menus [0].Title);
 

+ 0 - 300
Tests/UnitTestsParallelizable/ConsoleDrivers/MainLoopDriverTests.cs

@@ -1,300 +0,0 @@
-using Xunit.Abstractions;
-
-// Alias Console to MockConsole so we don't accidentally use Console
-
-namespace UnitTests_Parallelizable.DriverTests;
-
-public class MainLoopDriverTests : UnitTests.Parallelizable.ParallelizableBase
-{
-    public MainLoopDriverTests (ITestOutputHelper output) { ConsoleDriver.RunningUnitTests = true; }
-
-    [Theory]
-    [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))]
-    //[InlineData (typeof (DotNetDriver), typeof (NetMainLoop))]
-    //[InlineData (typeof (UnixDriver), typeof (UnixMainLoop))]
-    //[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
-
-    //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
-    public void MainLoop_AddTimeout_ValidIdleHandler_ReturnsToken (Type driverType, Type mainLoopDriverType)
-    {
-        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
-        var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
-        var mainLoop = new MainLoop (mainLoopDriver);
-        var idleHandlerInvoked = false;
-
-        bool IdleHandler ()
-        {
-            idleHandlerInvoked = true;
-
-            return false;
-        }
-
-        var token = mainLoop.TimedEvents.Add(TimeSpan.Zero, IdleHandler);
-
-        Assert.NotNull (token);
-        Assert.False (idleHandlerInvoked); // Idle handler should not be invoked immediately
-        mainLoop.RunIteration (); // Run an iteration to process the idle handler
-        Assert.True (idleHandlerInvoked); // Idle handler should be invoked after processing
-        mainLoop.Dispose ();
-    }
-
-    [Theory]
-    [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))]
-    //[InlineData (typeof (DotNetDriver), typeof (NetMainLoop))]
-    //[InlineData (typeof (UnixDriver), typeof (UnixMainLoop))]
-    //[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
-
-    //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
-    public void MainLoop_AddTimeout_ValidParameters_ReturnsToken (Type driverType, Type mainLoopDriverType)
-    {
-        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
-        var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
-        var mainLoop = new MainLoop (mainLoopDriver);
-        var callbackInvoked = false;
-
-        object token = mainLoop.TimedEvents.Add (
-                                            TimeSpan.FromMilliseconds (100),
-                                            () =>
-                                            {
-                                                callbackInvoked = true;
-
-                                                return false;
-                                            }
-                                           );
-
-        Assert.NotNull (token);
-        mainLoop.RunIteration (); // Run an iteration to process the timeout
-        Assert.False (callbackInvoked); // Callback should not be invoked immediately
-        Thread.Sleep (200); // Wait for the timeout
-        mainLoop.RunIteration (); // Run an iteration to process the timeout
-        Assert.True (callbackInvoked); // Callback should be invoked after the timeout
-        mainLoop.Dispose ();
-    }
-
-    [Theory]
-    [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))]
-    //[InlineData (typeof (DotNetDriver), typeof (NetMainLoop))]
-    //[InlineData (typeof (UnixDriver), typeof (UnixMainLoop))]
-    //[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
-
-    //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
-    public void MainLoop_CheckTimersAndIdleHandlers_IdleHandlersActive_ReturnsTrue (
-        Type driverType,
-        Type mainLoopDriverType
-    )
-    {
-        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
-        var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
-        var mainLoop = new MainLoop (mainLoopDriver);
-
-        mainLoop.TimedEvents.Add (TimeSpan.Zero, () => false);
-        bool result = mainLoop.TimedEvents.CheckTimers (out int waitTimeout);
-
-        Assert.True (result);
-        Assert.Equal (0, waitTimeout);
-        mainLoop.Dispose ();
-    }
-
-    [Theory]
-    [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))]
-    //[InlineData (typeof (DotNetDriver), typeof (NetMainLoop))]
-    //[InlineData (typeof (UnixDriver), typeof (UnixMainLoop))]
-    //[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
-
-    //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
-    public void MainLoop_CheckTimers_NoTimersOrIdleHandlers_ReturnsFalse (
-        Type driverType,
-        Type mainLoopDriverType
-    )
-    {
-        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
-        var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
-        var mainLoop = new MainLoop (mainLoopDriver);
-
-        bool result = mainLoop.TimedEvents.CheckTimers (out int waitTimeout);
-
-        Assert.False (result);
-        Assert.Equal (-1, waitTimeout);
-        mainLoop.Dispose ();
-    }
-
-    [Theory]
-    [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))]
-    //[InlineData (typeof (DotNetDriver), typeof (NetMainLoop))]
-    //[InlineData (typeof (UnixDriver), typeof (UnixMainLoop))]
-    //[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
-
-    //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
-    public void MainLoop_CheckTimersAndIdleHandlers_TimersActive_ReturnsTrue (
-        Type driverType,
-        Type mainLoopDriverType
-    )
-    {
-        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
-        var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
-        var mainLoop = new MainLoop (mainLoopDriver);
-
-        mainLoop.TimedEvents.Add (TimeSpan.FromMilliseconds (100), () => false);
-        bool result = mainLoop.TimedEvents.CheckTimers(out int waitTimeout);
-
-        Assert.True (result);
-        Assert.True (waitTimeout >= 0);
-        mainLoop.Dispose ();
-    }
-
-    [Theory]
-    [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))]
-    //[InlineData (typeof (DotNetDriver), typeof (NetMainLoop))]
-    //[InlineData (typeof (UnixDriver), typeof (UnixMainLoop))]
-    //[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
-
-    //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
-    public void MainLoop_Constructs_Disposes (Type driverType, Type mainLoopDriverType)
-    {
-        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
-        var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
-        var mainLoop = new MainLoop (mainLoopDriver);
-
-        // Check default values
-        Assert.NotNull (mainLoop);
-        Assert.Equal (mainLoopDriver, mainLoop.MainLoopDriver);
-        Assert.Empty (mainLoop.TimedEvents.Timeouts);
-        Assert.False (mainLoop.Running);
-
-        // Clean up
-        mainLoop.Dispose ();
-
-        // 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.TimedEvents.Timeouts);
-        Assert.False (mainLoop.Running);
-    }
-
-    [Theory]
-    [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))]
-    //[InlineData (typeof (DotNetDriver), typeof (NetMainLoop))]
-    //[InlineData (typeof (UnixDriver), typeof (UnixMainLoop))]
-    //[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
-
-    //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
-    public void MainLoop_RemoveIdle_InvalidToken_ReturnsFalse (Type driverType, Type mainLoopDriverType)
-    {
-        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
-        var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
-        var mainLoop = new MainLoop (mainLoopDriver);
-
-        bool result = mainLoop.TimedEvents.Remove("flibble");
-
-        Assert.False (result);
-        mainLoop.Dispose ();
-    }
-
-    [Theory]
-    [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))]
-    //[InlineData (typeof (DotNetDriver), typeof (NetMainLoop))]
-    //[InlineData (typeof (UnixDriver), typeof (UnixMainLoop))]
-    //[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
-
-    //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
-    public void MainLoop_RemoveIdle_ValidToken_ReturnsTrue (Type driverType, Type mainLoopDriverType)
-    {
-        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
-        var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
-        var mainLoop = new MainLoop (mainLoopDriver);
-
-        bool IdleHandler () { return false; }
-
-
-        var token = mainLoop.TimedEvents.Add (TimeSpan.Zero, IdleHandler);
-        bool result = mainLoop.TimedEvents.Remove (token);
-
-        Assert.True (result);
-        mainLoop.Dispose ();
-    }
-
-    [Theory]
-    [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))]
-    //[InlineData (typeof (DotNetDriver), typeof (NetMainLoop))]
-    //[InlineData (typeof (UnixDriver), typeof (UnixMainLoop))]
-    //[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
-
-    //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
-    public void MainLoop_RemoveTimeout_InvalidToken_ReturnsFalse (Type driverType, Type mainLoopDriverType)
-    {
-        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
-        var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
-        var mainLoop = new MainLoop (mainLoopDriver);
-
-        bool result = mainLoop.TimedEvents.Remove (new object ());
-
-        Assert.False (result);
-    }
-
-    [Theory]
-    [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))]
-    //[InlineData (typeof (DotNetDriver), typeof (NetMainLoop))]
-    //[InlineData (typeof (UnixDriver), typeof (UnixMainLoop))]
-    //[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
-
-    //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
-    public void MainLoop_RemoveTimeout_ValidToken_ReturnsTrue (Type driverType, Type mainLoopDriverType)
-    {
-        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
-        var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
-        var mainLoop = new MainLoop (mainLoopDriver);
-
-        object token = mainLoop.TimedEvents.Add (TimeSpan.FromMilliseconds (100), () => false);
-        bool result = mainLoop.TimedEvents.Remove (token);
-
-        Assert.True (result);
-        mainLoop.Dispose ();
-    }
-
-    [Theory]
-    [InlineData (typeof (FakeDriver), typeof (FakeMainLoop))]
-    //[InlineData (typeof (DotNetDriver), typeof (NetMainLoop))]
-    //[InlineData (typeof (UnixDriver), typeof (UnixMainLoop))]
-    //[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
-
-    //[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
-    public void MainLoop_RunIteration_ValidIdleHandler_CallsIdleHandler (Type driverType, Type mainLoopDriverType)
-    {
-        var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
-        var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
-        var mainLoop = new MainLoop (mainLoopDriver);
-        var idleHandlerInvoked = false;
-
-        Func<bool> idleHandler = () =>
-                                 {
-                                     idleHandlerInvoked = true;
-
-                                     return false;
-                                 };
-
-        mainLoop.TimedEvents.Add (TimeSpan.Zero, idleHandler);
-        mainLoop.RunIteration (); // Run an iteration to process the idle handler
-
-        Assert.True (idleHandlerInvoked);
-        mainLoop.Dispose ();
-    }
-
-    //[Theory]
-    //[InlineData (typeof (FakeDriver), typeof (FakeMainLoop))]
-    ////[InlineData (typeof (DotNetDriver), typeof (NetMainLoop))]
-    ////[InlineData (typeof (UnixDriver), typeof (UnixMainLoop))]
-    ////[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
-    //public void MainLoop_Invoke_ValidAction_RunsAction (Type driverType, Type mainLoopDriverType)
-    //{
-    //	var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
-    //	var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver });
-    //	var mainLoop = new MainLoop (mainLoopDriver);
-    //	var actionInvoked = false;
-
-    //	mainLoop.Invoke (() => { actionInvoked = true; });
-    //	mainLoop.RunIteration (); // Run an iteration to process the action.
-
-    //	Assert.True (actionInvoked);
-    //	mainLoop.Dispose ();
-    //}
-}

+ 1 - 1
Tests/UnitTestsParallelizable/MockConsoleDriver.cs

@@ -155,7 +155,7 @@ internal class MockConsoleDriver : IConsoleDriver
     public void UpdateCursor () {}
 
     /// <inheritdoc />
-    public MainLoop Init () { return null!; }
+    public void Init () { }
 
     /// <inheritdoc />
     public void End () {  }

+ 0 - 1
Tests/UnitTestsParallelizable/TestSetup.cs

@@ -47,7 +47,6 @@ public class GlobalTestSetup : IDisposable
         // Don't check Application.Force16Colors
         //Assert.False (Application.Force16Colors);
         Assert.Null (Application.Driver);
-        Assert.Null (Application.MainLoop);
         Assert.False (Application.EndAfterFirstIteration);
         Assert.Equal (Key.Tab.WithShift, Application.PrevTabKey);
         Assert.Equal (Key.Tab, Application.NextTabKey);

+ 2 - 0
docfx/docs/drivers.md

@@ -291,6 +291,8 @@ Application.Init();
 
 Terminal.Gui v1 drivers that implement `IConsoleDriver` but not `IConsoleDriverFacade` are still supported through a legacy compatibility layer. However, they do not benefit from the v2 architecture improvements (multi-threading, component separation, etc.).
 
+**Note**: The legacy `MainLoop` infrastructure (including the `MainLoop` class, `IMainLoopDriver` interface, and `FakeMainLoop`) has been removed in favor of the modern architecture. All drivers now use the `MainLoopCoordinator` and `ApplicationMainLoop` system exclusively.
+
 ## See Also
 
 - @Terminal.Gui.Drivers - API Reference

+ 11 - 2
docfx/docs/migratingfromv1.md

@@ -456,17 +456,26 @@ Additionally, the `Toggle` event was renamed `CheckStateChanging` and made cance
 +cb.AdvanceCheckState ();
 ```
 
-## `MainLoop` is no longer accessible from `Application`
+## `MainLoop` has been removed from `Application`
 
-In v1, you could add timeouts via `Application.MainLoop.AddTimeout` among other things.  In v2, the `MainLoop` object is internal to `Application` and methods previously accessed via `MainLoop` can now be accessed directly via `Application`
+In v1, you could add timeouts via `Application.MainLoop.AddTimeout` and access the `MainLoop` object directly. In v2, the legacy `MainLoop` class has been completely removed as part of the architectural modernization. Timeout functionality and other features previously accessed via `MainLoop` are now available directly through `Application` or `ApplicationImpl`.
 
 ### How to Fix
 
+Replace any `Application.MainLoop` references:
+
 ```diff
 - Application.MainLoop.AddTimeout (TimeSpan time, Func<MainLoop, bool> callback)
 + Application.AddTimeout (TimeSpan time, Func<bool> callback)
 ```
 
+```diff
+- Application.MainLoop.Wakeup ()
++ // No replacement needed - wakeup is handled automatically by the modern architecture
+```
+
+**Note**: The legacy `MainLoop` infrastructure (including `IMainLoopDriver` and `FakeMainLoop`) has been removed. The modern v2 architecture uses `ApplicationImpl`, `MainLoopCoordinator`, and `ApplicationMainLoop` instead.
+
 ## `SendSubViewXXX` renamed and corrected
 
 In v1, the `View` methods to move SubViews within the SubViews list were poorly named and actually operated in reverse of what their names suggested.

BIN
local_packages/Terminal.Gui.2.0.0.nupkg


BIN
local_packages/Terminal.Gui.2.0.0.snupkg