ApplicationImpl.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. #nullable enable
  2. using System.Diagnostics;
  3. using System.Diagnostics.CodeAnalysis;
  4. namespace Terminal.Gui.App;
  5. /// <summary>
  6. /// Original Terminal.Gui implementation of core <see cref="Application"/> methods.
  7. /// </summary>
  8. public class ApplicationImpl : IApplication
  9. {
  10. // Private static readonly Lazy instance of Application
  11. private static Lazy<IApplication> _lazyInstance = new (() => new ApplicationImpl ());
  12. /// <summary>
  13. /// Gets the currently configured backend implementation of <see cref="Application"/> gateway methods.
  14. /// Change to your own implementation by using <see cref="ChangeInstance"/> (before init).
  15. /// </summary>
  16. public static IApplication Instance => _lazyInstance.Value;
  17. /// <inheritdoc/>
  18. public virtual ITimedEvents? TimedEvents => Application.MainLoop?.TimedEvents;
  19. /// <summary>
  20. /// Handles which <see cref="View"/> (if any) has captured the mouse
  21. /// </summary>
  22. public IMouseGrabHandler MouseGrabHandler { get; set; } = new MouseGrabHandler ();
  23. /// <summary>
  24. /// Change the singleton implementation, should not be called except before application
  25. /// startup. This method lets you provide alternative implementations of core static gateway
  26. /// methods of <see cref="Application"/>.
  27. /// </summary>
  28. /// <param name="newApplication"></param>
  29. public static void ChangeInstance (IApplication newApplication)
  30. {
  31. _lazyInstance = new Lazy<IApplication> (newApplication);
  32. }
  33. /// <inheritdoc/>
  34. [RequiresUnreferencedCode ("AOT")]
  35. [RequiresDynamicCode ("AOT")]
  36. public virtual void Init (IConsoleDriver? driver = null, string? driverName = null)
  37. {
  38. Application.InternalInit (driver, string.IsNullOrWhiteSpace (driverName) ? Application.ForceDriver : driverName);
  39. }
  40. /// <summary>
  41. /// Runs the application by creating a <see cref="Toplevel"/> object and calling
  42. /// <see cref="Run(Toplevel, Func{Exception, bool})"/>.
  43. /// </summary>
  44. /// <remarks>
  45. /// <para>Calling <see cref="Init"/> first is not needed as this function will initialize the application.</para>
  46. /// <para>
  47. /// <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has returned) to
  48. /// ensure resources are cleaned up and terminal settings restored.
  49. /// </para>
  50. /// <para>
  51. /// The caller is responsible for disposing the object returned by this method.
  52. /// </para>
  53. /// </remarks>
  54. /// <returns>The created <see cref="Toplevel"/> object. The caller is responsible for disposing this object.</returns>
  55. [RequiresUnreferencedCode ("AOT")]
  56. [RequiresDynamicCode ("AOT")]
  57. public Toplevel Run (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null) { return Run<Toplevel> (errorHandler, driver); }
  58. /// <summary>
  59. /// Runs the application by creating a <see cref="Toplevel"/>-derived object of type <c>T</c> and calling
  60. /// <see cref="Run(Toplevel, Func{Exception, bool})"/>.
  61. /// </summary>
  62. /// <remarks>
  63. /// <para>Calling <see cref="Init"/> first is not needed as this function will initialize the application.</para>
  64. /// <para>
  65. /// <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has returned) to
  66. /// ensure resources are cleaned up and terminal settings restored.
  67. /// </para>
  68. /// <para>
  69. /// The caller is responsible for disposing the object returned by this method.
  70. /// </para>
  71. /// </remarks>
  72. /// <param name="errorHandler"></param>
  73. /// <param name="driver">
  74. /// The <see cref="IConsoleDriver"/> to use. If not specified the default driver for the platform will
  75. /// be used ( <see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, or <see cref="NetDriver"/>). Must be
  76. /// <see langword="null"/> if <see cref="Init"/> has already been called.
  77. /// </param>
  78. /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
  79. [RequiresUnreferencedCode ("AOT")]
  80. [RequiresDynamicCode ("AOT")]
  81. public virtual T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
  82. where T : Toplevel, new()
  83. {
  84. if (!Application.Initialized)
  85. {
  86. // Init() has NOT been called.
  87. Application.InternalInit (driver, Application.ForceDriver, true);
  88. }
  89. if (Instance is ApplicationV2)
  90. {
  91. return Instance.Run<T> (errorHandler, driver);
  92. }
  93. var top = new T ();
  94. Run (top, errorHandler);
  95. return top;
  96. }
  97. /// <summary>Runs the Application using the provided <see cref="Toplevel"/> view.</summary>
  98. /// <remarks>
  99. /// <para>
  100. /// This method is used to start processing events for the main application, but it is also used to run other
  101. /// modal <see cref="View"/>s such as <see cref="Dialog"/> boxes.
  102. /// </para>
  103. /// <para>
  104. /// To make a <see cref="Run(Toplevel,System.Func{System.Exception,bool})"/> stop execution, call
  105. /// <see cref="Application.RequestStop"/>.
  106. /// </para>
  107. /// <para>
  108. /// Calling <see cref="Run(Toplevel,System.Func{System.Exception,bool})"/> is equivalent to calling
  109. /// <see cref="Application.Begin(Toplevel)"/>, followed by <see cref="Application.RunLoop(RunState)"/>, and then calling
  110. /// <see cref="Application.End(RunState)"/>.
  111. /// </para>
  112. /// <para>
  113. /// Alternatively, to have a program control the main loop and process events manually, call
  114. /// <see cref="Application.Begin(Toplevel)"/> to set things up manually and then repeatedly call
  115. /// <see cref="Application.RunLoop(RunState)"/> with the wait parameter set to false. By doing this the
  116. /// <see cref="Application.RunLoop(RunState)"/> method will only process any pending events, timers handlers and then
  117. /// return control immediately.
  118. /// </para>
  119. /// <para>When using <see cref="Run{T}"/> or
  120. /// <see cref="Run(System.Func{System.Exception,bool},IConsoleDriver)"/>
  121. /// <see cref="Init"/> will be called automatically.
  122. /// </para>
  123. /// <para>
  124. /// RELEASE builds only: When <paramref name="errorHandler"/> is <see langword="null"/> any exceptions will be
  125. /// rethrown. Otherwise, if <paramref name="errorHandler"/> will be called. If <paramref name="errorHandler"/>
  126. /// returns <see langword="true"/> the <see cref="Application.RunLoop(RunState)"/> will resume; otherwise this method will
  127. /// exit.
  128. /// </para>
  129. /// </remarks>
  130. /// <param name="view">The <see cref="Toplevel"/> to run as a modal.</param>
  131. /// <param name="errorHandler">
  132. /// RELEASE builds only: Handler for any unhandled exceptions (resumes when returns true,
  133. /// rethrows when null).
  134. /// </param>
  135. public virtual void Run (Toplevel view, Func<Exception, bool>? errorHandler = null)
  136. {
  137. ArgumentNullException.ThrowIfNull (view);
  138. if (Application.Initialized)
  139. {
  140. if (Application.Driver is null)
  141. {
  142. // Disposing before throwing
  143. view.Dispose ();
  144. // This code path should be impossible because Init(null, null) will select the platform default driver
  145. throw new InvalidOperationException (
  146. "Init() completed without a driver being set (this should be impossible); Run<T>() cannot be called."
  147. );
  148. }
  149. }
  150. else
  151. {
  152. // Init() has NOT been called.
  153. throw new InvalidOperationException (
  154. "Init() has not been called. Only Run() or Run<T>() can be used without calling Init()."
  155. );
  156. }
  157. var resume = true;
  158. while (resume)
  159. {
  160. #if !DEBUG
  161. try
  162. {
  163. #endif
  164. resume = false;
  165. RunState runState = Application.Begin (view);
  166. // If EndAfterFirstIteration is true then the user must dispose of the runToken
  167. // by using NotifyStopRunState event.
  168. Application.RunLoop (runState);
  169. if (runState.Toplevel is null)
  170. {
  171. #if DEBUG_IDISPOSABLE
  172. if (View.EnableDebugIDisposableAsserts)
  173. {
  174. Debug.Assert (Application.TopLevels.Count == 0);
  175. }
  176. #endif
  177. runState.Dispose ();
  178. return;
  179. }
  180. if (!Application.EndAfterFirstIteration)
  181. {
  182. Application.End (runState);
  183. }
  184. #if !DEBUG
  185. }
  186. catch (Exception error)
  187. {
  188. Logging.Warning ($"Release Build Exception: {error}");
  189. if (errorHandler is null)
  190. {
  191. throw;
  192. }
  193. resume = errorHandler (error);
  194. }
  195. #endif
  196. }
  197. }
  198. /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
  199. /// <remarks>
  200. /// Shutdown must be called for every call to <see cref="Init"/> or
  201. /// <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> to ensure all resources are cleaned
  202. /// up (Disposed)
  203. /// and terminal settings are restored.
  204. /// </remarks>
  205. public virtual void Shutdown ()
  206. {
  207. // TODO: Throw an exception if Init hasn't been called.
  208. bool wasInitialized = Application.Initialized;
  209. Application.ResetState ();
  210. ConfigurationManager.PrintJsonErrors ();
  211. if (wasInitialized)
  212. {
  213. bool init = Application.Initialized;
  214. Application.OnInitializedChanged (this, new (in init));
  215. }
  216. _lazyInstance = new (() => new ApplicationImpl ());
  217. }
  218. /// <inheritdoc />
  219. public virtual void RequestStop (Toplevel? top)
  220. {
  221. top ??= Application.Top;
  222. if (!top!.Running)
  223. {
  224. return;
  225. }
  226. var ev = new ToplevelClosingEventArgs (top);
  227. top.OnClosing (ev);
  228. if (ev.Cancel)
  229. {
  230. return;
  231. }
  232. top.Running = false;
  233. Application.OnNotifyStopRunState (top);
  234. }
  235. /// <inheritdoc />
  236. public virtual void Invoke (Action action)
  237. {
  238. // If we are already on the main UI thread
  239. if (Application.MainThreadId == Thread.CurrentThread.ManagedThreadId)
  240. {
  241. action ();
  242. WakeupMainLoop ();
  243. return;
  244. }
  245. if (Application.MainLoop == null)
  246. {
  247. Logging.Warning ("Ignored Invoke because MainLoop is not initialized yet");
  248. return;
  249. }
  250. Application.AddTimeout (TimeSpan.Zero,
  251. () =>
  252. {
  253. action ();
  254. return false;
  255. }
  256. );
  257. WakeupMainLoop ();
  258. void WakeupMainLoop ()
  259. {
  260. // Ensure the action is executed in the main loop
  261. // Wakeup mainloop if it's waiting for events
  262. Application.MainLoop?.Wakeup ();
  263. }
  264. }
  265. /// <inheritdoc />
  266. public bool IsLegacy { get; protected set; } = true;
  267. /// <inheritdoc />
  268. public virtual object AddTimeout (TimeSpan time, Func<bool> callback)
  269. {
  270. if (Application.MainLoop is null)
  271. {
  272. throw new NotInitializedException ("Cannot add timeout before main loop is initialized", null);
  273. }
  274. return Application.MainLoop.TimedEvents.Add (time, callback);
  275. }
  276. /// <inheritdoc />
  277. public virtual bool RemoveTimeout (object token)
  278. {
  279. return Application.MainLoop?.TimedEvents.Remove (token) ?? false;
  280. }
  281. /// <inheritdoc />
  282. public virtual void LayoutAndDraw (bool forceDraw)
  283. {
  284. Application.LayoutAndDrawImpl (forceDraw);
  285. }
  286. }