#nullable enable using System.Collections.Concurrent; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; using Terminal.Gui.Drivers; namespace Terminal.Gui.App; /// /// Implementation of core methods using the modern /// main loop architecture with component factories for different platforms. /// public class ApplicationImpl : IApplication { private readonly IComponentFactory? _componentFactory; private IMainLoopCoordinator? _coordinator; private string? _driverName; private readonly ITimedEvents _timedEvents = new TimedEvents (); // Private static readonly Lazy instance of Application private static Lazy _lazyInstance = new (() => new ApplicationImpl ()); /// /// Gets the currently configured backend implementation of gateway methods. /// Change to your own implementation by using (before init). /// public static IApplication Instance => _lazyInstance.Value; /// public ITimedEvents? TimedEvents => _timedEvents; internal IMainLoopCoordinator? Coordinator => _coordinator; /// /// Handles which (if any) has captured the mouse /// public IMouseGrabHandler MouseGrabHandler { get; set; } = new MouseGrabHandler (); /// /// Creates a new instance of the Application backend. /// public ApplicationImpl () { } internal ApplicationImpl (IComponentFactory componentFactory) { _componentFactory = componentFactory; } /// /// Change the singleton implementation, should not be called except before application /// startup. This method lets you provide alternative implementations of core static gateway /// methods of . /// /// public static void ChangeInstance (IApplication newApplication) { _lazyInstance = new Lazy (newApplication); } /// [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] public void Init (IConsoleDriver? driver = null, string? driverName = null) { if (Application.Initialized) { Logging.Logger.LogError ("Init called multiple times without shutdown, aborting."); throw new InvalidOperationException ("Init called multiple times without Shutdown"); } if (!string.IsNullOrWhiteSpace (driverName)) { _driverName = driverName; } if (string.IsNullOrWhiteSpace (_driverName)) { _driverName = Application.ForceDriver; } Debug.Assert(Application.Navigation is null); Application.Navigation = new (); Debug.Assert (Application.Popover is null); Application.Popover = new (); Application.AddKeyBindings (); CreateDriver (driverName ?? _driverName); Application.Initialized = true; Application.OnInitializedChanged (this, new (true)); Application.SubscribeDriverEvents (); SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ()); Application.MainThreadId = Thread.CurrentThread.ManagedThreadId; } private void CreateDriver (string? driverName) { PlatformID p = Environment.OSVersion.Platform; // Check component factory type first - this takes precedence over driverName bool factoryIsWindows = _componentFactory is IComponentFactory; bool factoryIsDotNet = _componentFactory is IComponentFactory; bool factoryIsUnix = _componentFactory is IComponentFactory; bool factoryIsFake = _componentFactory is IComponentFactory; // Then check driverName bool nameIsWindows = driverName?.Contains ("win", StringComparison.OrdinalIgnoreCase) ?? false; bool nameIsDotNet = (driverName?.Contains ("dotnet", StringComparison.OrdinalIgnoreCase) ?? false); bool nameIsUnix = driverName?.Contains ("unix", StringComparison.OrdinalIgnoreCase) ?? false; bool nameIsFake = driverName?.Contains ("fake", StringComparison.OrdinalIgnoreCase) ?? false; // Decide which driver to use - component factory type takes priority if (factoryIsFake || (!factoryIsWindows && !factoryIsDotNet && !factoryIsUnix && nameIsFake)) { _coordinator = CreateSubcomponents (() => new FakeComponentFactory ()); } else if (factoryIsWindows || (!factoryIsDotNet && !factoryIsUnix && nameIsWindows)) { _coordinator = CreateSubcomponents (() => new WindowsComponentFactory ()); } else if (factoryIsDotNet || (!factoryIsWindows && !factoryIsUnix && nameIsDotNet)) { _coordinator = CreateSubcomponents (() => new NetComponentFactory ()); } else if (factoryIsUnix || (!factoryIsWindows && !factoryIsDotNet && nameIsUnix)) { _coordinator = CreateSubcomponents (() => new UnixComponentFactory ()); } else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) { _coordinator = CreateSubcomponents (() => new WindowsComponentFactory ()); } else { _coordinator = CreateSubcomponents (() => new UnixComponentFactory ()); } _coordinator.StartAsync ().Wait (); if (Application.Driver == null) { throw new ("Application.Driver was null even after booting MainLoopCoordinator"); } } private IMainLoopCoordinator CreateSubcomponents (Func> fallbackFactory) { ConcurrentQueue inputBuffer = new (); ApplicationMainLoop loop = new (); IComponentFactory cf; if (_componentFactory is IComponentFactory typedFactory) { cf = typedFactory; } else { cf = fallbackFactory (); } return new MainLoopCoordinator (_timedEvents, inputBuffer, loop, cf); } /// /// Runs the application by creating a object and calling /// . /// /// The created object. The caller is responsible for disposing this object. [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] public Toplevel Run (Func? errorHandler = null, IConsoleDriver? driver = null) { return Run (errorHandler, driver); } /// /// Runs the application by creating a -derived object of type T and calling /// . /// /// /// /// The to use. If not specified the default driver for the platform will /// be used. Must be if has already been called. /// /// The created T object. The caller is responsible for disposing this object. [RequiresUnreferencedCode ("AOT")] [RequiresDynamicCode ("AOT")] public T Run (Func? errorHandler = null, IConsoleDriver? driver = null) where T : Toplevel, new() { if (!Application.Initialized) { // Init() has NOT been called. Auto-initialize as per interface contract. Init (driver, null); } var top = new T (); Run (top, errorHandler); return top; } /// Runs the Application using the provided view. /// The to run as a modal. /// Handler for any unhandled exceptions. public void Run (Toplevel view, Func? errorHandler = null) { Logging.Information ($"Run '{view}'"); ArgumentNullException.ThrowIfNull (view); if (!Application.Initialized) { throw new NotInitializedException (nameof (Run)); } if (Application.Driver == null) { throw new InvalidOperationException ("Driver was inexplicably null when trying to Run view"); } Application.Top = view; RunState rs = Application.Begin (view); Application.Top.Running = true; while (Application.TopLevels.TryPeek (out Toplevel? found) && found == view && view.Running) { if (_coordinator is null) { throw new ($"{nameof (IMainLoopCoordinator)} inexplicably became null during Run"); } _coordinator.RunIteration (); } Logging.Information ($"Run - Calling End"); Application.End (rs); } /// Shutdown an application initialized with . public void Shutdown () { _coordinator?.Stop (); bool wasInitialized = Application.Initialized; Application.ResetState (); ConfigurationManager.PrintJsonErrors (); if (wasInitialized) { bool init = Application.Initialized; Application.OnInitializedChanged (this, new (in init)); } Application.Driver = null; _lazyInstance = new (() => new ApplicationImpl ()); } /// public void RequestStop (Toplevel? top) { Logging.Logger.LogInformation ($"RequestStop '{(top is {} ? top : "null")}'"); top ??= Application.Top; if (top == null) { return; } var ev = new ToplevelClosingEventArgs (top); top.OnClosing (ev); if (ev.Cancel) { return; } top.Running = false; } /// public void Invoke (Action action) { // If we are already on the main UI thread if (Application.MainThreadId == Thread.CurrentThread.ManagedThreadId) { action (); return; } _timedEvents.Add (TimeSpan.Zero, () => { action (); return false; } ); } /// public bool IsLegacy => false; /// public object AddTimeout (TimeSpan time, Func callback) { return _timedEvents.Add (time, callback); } /// public bool RemoveTimeout (object token) { return _timedEvents.Remove (token); } /// public void LayoutAndDraw (bool forceDraw) { Application.Top?.SetNeedsDraw(); Application.Top?.SetNeedsLayout (); } }