ApplicationImpl.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. #nullable enable
  2. using System.Collections.Concurrent;
  3. using System.Diagnostics;
  4. using System.Diagnostics.CodeAnalysis;
  5. using System.Globalization;
  6. using Microsoft.Extensions.Logging;
  7. using Terminal.Gui.Drivers;
  8. namespace Terminal.Gui.App;
  9. /// <summary>
  10. /// Implementation of core <see cref="Application"/> methods using the modern
  11. /// main loop architecture with component factories for different platforms.
  12. /// </summary>
  13. public class ApplicationImpl : IApplication
  14. {
  15. private readonly IComponentFactory? _componentFactory;
  16. private IMainLoopCoordinator? _coordinator;
  17. private string? _driverName;
  18. private readonly ITimedEvents _timedEvents = new TimedEvents ();
  19. private IConsoleDriver? _driver;
  20. private bool _initialized;
  21. private ApplicationPopover? _popover;
  22. private ApplicationNavigation? _navigation;
  23. private Toplevel? _top;
  24. private readonly ConcurrentStack<Toplevel> _topLevels = new ();
  25. private int _mainThreadId = -1;
  26. private bool _force16Colors;
  27. private string _forceDriver = string.Empty;
  28. private readonly List<SixelToRender> _sixel = new ();
  29. private readonly object _lockScreen = new ();
  30. private Rectangle? _screen;
  31. private bool _clearScreenNextIteration;
  32. private ushort _maximumIterationsPerSecond = Application.DefaultMaximumIterationsPerSecond;
  33. private List<CultureInfo>? _supportedCultures;
  34. // When `End ()` is called, it is possible `RunState.Toplevel` is a different object than `Top`.
  35. // This variable is set in `End` in this case so that `Begin` correctly sets `Top`.
  36. internal Toplevel? _cachedRunStateToplevel;
  37. // Private static readonly Lazy instance of Application
  38. private static Lazy<IApplication> _lazyInstance = new (() => new ApplicationImpl ());
  39. /// <summary>
  40. /// Gets the currently configured backend implementation of <see cref="Application"/> gateway methods.
  41. /// Change to your own implementation by using <see cref="ChangeInstance"/> (before init).
  42. /// </summary>
  43. public static IApplication Instance => _lazyInstance.Value;
  44. /// <inheritdoc/>
  45. public ITimedEvents? TimedEvents => _timedEvents;
  46. internal IMainLoopCoordinator? Coordinator => _coordinator;
  47. private IMouse? _mouse;
  48. /// <summary>
  49. /// Handles mouse event state and processing.
  50. /// </summary>
  51. public IMouse Mouse
  52. {
  53. get
  54. {
  55. if (_mouse is null)
  56. {
  57. _mouse = new MouseImpl { Application = this };
  58. }
  59. return _mouse;
  60. }
  61. set => _mouse = value ?? throw new ArgumentNullException (nameof (value));
  62. }
  63. /// <summary>
  64. /// Handles which <see cref="View"/> (if any) has captured the mouse
  65. /// </summary>
  66. public IMouseGrabHandler MouseGrabHandler { get; set; } = new MouseGrabHandler ();
  67. private IKeyboard? _keyboard;
  68. /// <summary>
  69. /// Handles keyboard input and key bindings at the Application level
  70. /// </summary>
  71. public IKeyboard Keyboard
  72. {
  73. get
  74. {
  75. if (_keyboard is null)
  76. {
  77. _keyboard = new KeyboardImpl { Application = this };
  78. }
  79. return _keyboard;
  80. }
  81. set => _keyboard = value ?? throw new ArgumentNullException (nameof (value));
  82. }
  83. /// <inheritdoc/>
  84. public IConsoleDriver? Driver
  85. {
  86. get => _driver;
  87. set => _driver = value;
  88. }
  89. /// <inheritdoc/>
  90. public bool Initialized
  91. {
  92. get => _initialized;
  93. set => _initialized = value;
  94. }
  95. /// <inheritdoc/>
  96. public bool Force16Colors
  97. {
  98. get => _force16Colors;
  99. set => _force16Colors = value;
  100. }
  101. /// <inheritdoc/>
  102. public string ForceDriver
  103. {
  104. get => _forceDriver;
  105. set => _forceDriver = value;
  106. }
  107. /// <inheritdoc/>
  108. public List<SixelToRender> Sixel => _sixel;
  109. /// <inheritdoc/>
  110. public Rectangle Screen
  111. {
  112. get
  113. {
  114. lock (_lockScreen)
  115. {
  116. if (_screen == null)
  117. {
  118. _screen = Driver?.Screen ?? new (new (0, 0), new (2048, 2048));
  119. }
  120. return _screen.Value;
  121. }
  122. }
  123. set
  124. {
  125. if (value is {} && (value.X != 0 || value.Y != 0))
  126. {
  127. throw new NotImplementedException ($"Screen locations other than 0, 0 are not yet supported");
  128. }
  129. lock (_lockScreen)
  130. {
  131. _screen = value;
  132. }
  133. }
  134. }
  135. /// <inheritdoc/>
  136. public bool ClearScreenNextIteration
  137. {
  138. get => _clearScreenNextIteration;
  139. set => _clearScreenNextIteration = value;
  140. }
  141. /// <inheritdoc/>
  142. public ApplicationPopover? Popover
  143. {
  144. get => _popover;
  145. set => _popover = value;
  146. }
  147. /// <inheritdoc/>
  148. public ApplicationNavigation? Navigation
  149. {
  150. get => _navigation;
  151. set => _navigation = value;
  152. }
  153. /// <inheritdoc/>
  154. public Toplevel? Top
  155. {
  156. get => _top;
  157. set => _top = value;
  158. }
  159. /// <inheritdoc/>
  160. public ConcurrentStack<Toplevel> TopLevels => _topLevels;
  161. /// <inheritdoc/>
  162. public ushort MaximumIterationsPerSecond
  163. {
  164. get => _maximumIterationsPerSecond;
  165. set => _maximumIterationsPerSecond = value;
  166. }
  167. /// <inheritdoc/>
  168. public List<CultureInfo>? SupportedCultures
  169. {
  170. get
  171. {
  172. if (_supportedCultures is null)
  173. {
  174. _supportedCultures = Application.GetSupportedCultures ();
  175. }
  176. return _supportedCultures;
  177. }
  178. }
  179. /// <summary>
  180. /// Internal helper to raise InitializedChanged event. Used by legacy Init path and modern Init path.
  181. /// </summary>
  182. internal void RaiseInitializedChanged (bool initialized)
  183. {
  184. Application.OnInitializedChanged (this, new (initialized));
  185. }
  186. /// <summary>
  187. /// Gets or sets the main thread ID for the application.
  188. /// </summary>
  189. internal int MainThreadId
  190. {
  191. get => _mainThreadId;
  192. set => _mainThreadId = value;
  193. }
  194. /// <inheritdoc/>
  195. public void RequestStop () => RequestStop (null);
  196. /// <summary>
  197. /// Creates a new instance of the Application backend.
  198. /// </summary>
  199. public ApplicationImpl ()
  200. {
  201. }
  202. internal ApplicationImpl (IComponentFactory componentFactory)
  203. {
  204. _componentFactory = componentFactory;
  205. }
  206. /// <summary>
  207. /// Change the singleton implementation, should not be called except before application
  208. /// startup. This method lets you provide alternative implementations of core static gateway
  209. /// methods of <see cref="Application"/>.
  210. /// </summary>
  211. /// <param name="newApplication"></param>
  212. public static void ChangeInstance (IApplication newApplication)
  213. {
  214. _lazyInstance = new Lazy<IApplication> (newApplication);
  215. }
  216. /// <inheritdoc/>
  217. [RequiresUnreferencedCode ("AOT")]
  218. [RequiresDynamicCode ("AOT")]
  219. public void Init (IConsoleDriver? driver = null, string? driverName = null)
  220. {
  221. if (_initialized)
  222. {
  223. Logging.Logger.LogError ("Init called multiple times without shutdown, aborting.");
  224. throw new InvalidOperationException ("Init called multiple times without Shutdown");
  225. }
  226. if (!string.IsNullOrWhiteSpace (driverName))
  227. {
  228. _driverName = driverName;
  229. }
  230. if (string.IsNullOrWhiteSpace (_driverName))
  231. {
  232. _driverName = Application.ForceDriver;
  233. }
  234. Debug.Assert(_navigation is null);
  235. _navigation = new ();
  236. Debug.Assert (_popover is null);
  237. _popover = new ();
  238. // Preserve existing keyboard settings if they exist
  239. bool hasExistingKeyboard = _keyboard is not null;
  240. Key existingQuitKey = _keyboard?.QuitKey ?? Key.Esc;
  241. Key existingArrangeKey = _keyboard?.ArrangeKey ?? Key.F5.WithCtrl;
  242. Key existingNextTabKey = _keyboard?.NextTabKey ?? Key.Tab;
  243. Key existingPrevTabKey = _keyboard?.PrevTabKey ?? Key.Tab.WithShift;
  244. Key existingNextTabGroupKey = _keyboard?.NextTabGroupKey ?? Key.F6;
  245. Key existingPrevTabGroupKey = _keyboard?.PrevTabGroupKey ?? Key.F6.WithShift;
  246. // Reset keyboard to ensure fresh state with default bindings
  247. _keyboard = new KeyboardImpl { Application = this };
  248. // Restore previously set keys if they existed and were different from defaults
  249. if (hasExistingKeyboard)
  250. {
  251. _keyboard.QuitKey = existingQuitKey;
  252. _keyboard.ArrangeKey = existingArrangeKey;
  253. _keyboard.NextTabKey = existingNextTabKey;
  254. _keyboard.PrevTabKey = existingPrevTabKey;
  255. _keyboard.NextTabGroupKey = existingNextTabGroupKey;
  256. _keyboard.PrevTabGroupKey = existingPrevTabGroupKey;
  257. }
  258. CreateDriver (driverName ?? _driverName);
  259. _initialized = true;
  260. Application.OnInitializedChanged (this, new (true));
  261. Application.SubscribeDriverEvents ();
  262. SynchronizationContext.SetSynchronizationContext (new ());
  263. _mainThreadId = Thread.CurrentThread.ManagedThreadId;
  264. }
  265. private void CreateDriver (string? driverName)
  266. {
  267. // When running unit tests, always use FakeDriver unless explicitly specified
  268. if (ConsoleDriver.RunningUnitTests &&
  269. string.IsNullOrEmpty (driverName) &&
  270. _componentFactory is null)
  271. {
  272. Logging.Logger.LogDebug ("Unit test safeguard: forcing FakeDriver (RunningUnitTests=true, driverName=null, componentFactory=null)");
  273. _coordinator = CreateSubcomponents (() => new FakeComponentFactory ());
  274. _coordinator.StartAsync ().Wait ();
  275. if (_driver == null)
  276. {
  277. throw new ("Driver was null even after booting MainLoopCoordinator");
  278. }
  279. return;
  280. }
  281. PlatformID p = Environment.OSVersion.Platform;
  282. // Check component factory type first - this takes precedence over driverName
  283. bool factoryIsWindows = _componentFactory is IComponentFactory<WindowsConsole.InputRecord>;
  284. bool factoryIsDotNet = _componentFactory is IComponentFactory<ConsoleKeyInfo>;
  285. bool factoryIsUnix = _componentFactory is IComponentFactory<char>;
  286. bool factoryIsFake = _componentFactory is IComponentFactory<ConsoleKeyInfo>;
  287. // Then check driverName
  288. bool nameIsWindows = driverName?.Contains ("win", StringComparison.OrdinalIgnoreCase) ?? false;
  289. bool nameIsDotNet = (driverName?.Contains ("dotnet", StringComparison.OrdinalIgnoreCase) ?? false);
  290. bool nameIsUnix = driverName?.Contains ("unix", StringComparison.OrdinalIgnoreCase) ?? false;
  291. bool nameIsFake = driverName?.Contains ("fake", StringComparison.OrdinalIgnoreCase) ?? false;
  292. // Decide which driver to use - component factory type takes priority
  293. if (factoryIsFake || (!factoryIsWindows && !factoryIsDotNet && !factoryIsUnix && nameIsFake))
  294. {
  295. _coordinator = CreateSubcomponents (() => new FakeComponentFactory ());
  296. }
  297. else if (factoryIsWindows || (!factoryIsDotNet && !factoryIsUnix && nameIsWindows))
  298. {
  299. _coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
  300. }
  301. else if (factoryIsDotNet || (!factoryIsWindows && !factoryIsUnix && nameIsDotNet))
  302. {
  303. _coordinator = CreateSubcomponents (() => new NetComponentFactory ());
  304. }
  305. else if (factoryIsUnix || (!factoryIsWindows && !factoryIsDotNet && nameIsUnix))
  306. {
  307. _coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
  308. }
  309. else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
  310. {
  311. _coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
  312. }
  313. else
  314. {
  315. _coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
  316. }
  317. _coordinator.StartAsync ().Wait ();
  318. if (_driver == null)
  319. {
  320. throw new ("Driver was null even after booting MainLoopCoordinator");
  321. }
  322. }
  323. private IMainLoopCoordinator CreateSubcomponents<T> (Func<IComponentFactory<T>> fallbackFactory)
  324. {
  325. ConcurrentQueue<T> inputBuffer = new ();
  326. ApplicationMainLoop<T> loop = new ();
  327. IComponentFactory<T> cf;
  328. if (_componentFactory is IComponentFactory<T> typedFactory)
  329. {
  330. cf = typedFactory;
  331. }
  332. else
  333. {
  334. cf = fallbackFactory ();
  335. }
  336. return new MainLoopCoordinator<T> (_timedEvents, inputBuffer, loop, cf);
  337. }
  338. /// <summary>
  339. /// Runs the application by creating a <see cref="Toplevel"/> object and calling
  340. /// <see cref="Run(Toplevel, Func{Exception, bool})"/>.
  341. /// </summary>
  342. /// <returns>The created <see cref="Toplevel"/> object. The caller is responsible for disposing this object.</returns>
  343. [RequiresUnreferencedCode ("AOT")]
  344. [RequiresDynamicCode ("AOT")]
  345. public Toplevel Run (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null) { return Run<Toplevel> (errorHandler, driver); }
  346. /// <summary>
  347. /// Runs the application by creating a <see cref="Toplevel"/>-derived object of type <c>T</c> and calling
  348. /// <see cref="Run(Toplevel, Func{Exception, bool})"/>.
  349. /// </summary>
  350. /// <param name="errorHandler"></param>
  351. /// <param name="driver">
  352. /// The <see cref="IConsoleDriver"/> to use. If not specified the default driver for the platform will
  353. /// be used. Must be <see langword="null"/> if <see cref="Init"/> has already been called.
  354. /// </param>
  355. /// <returns>The created T object. The caller is responsible for disposing this object.</returns>
  356. [RequiresUnreferencedCode ("AOT")]
  357. [RequiresDynamicCode ("AOT")]
  358. public T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
  359. where T : Toplevel, new()
  360. {
  361. if (!_initialized)
  362. {
  363. // Init() has NOT been called. Auto-initialize as per interface contract.
  364. Init (driver, null);
  365. }
  366. T top = new ();
  367. Run (top, errorHandler);
  368. return top;
  369. }
  370. /// <summary>Runs the Application using the provided <see cref="Toplevel"/> view.</summary>
  371. /// <param name="view">The <see cref="Toplevel"/> to run as a modal.</param>
  372. /// <param name="errorHandler">Handler for any unhandled exceptions.</param>
  373. public void Run (Toplevel view, Func<Exception, bool>? errorHandler = null)
  374. {
  375. Logging.Information ($"Run '{view}'");
  376. ArgumentNullException.ThrowIfNull (view);
  377. if (!_initialized)
  378. {
  379. throw new NotInitializedException (nameof (Run));
  380. }
  381. if (_driver == null)
  382. {
  383. throw new InvalidOperationException ("Driver was inexplicably null when trying to Run view");
  384. }
  385. _top = view;
  386. RunState rs = Application.Begin (view);
  387. _top.Running = true;
  388. while (_topLevels.TryPeek (out Toplevel? found) && found == view && view.Running)
  389. {
  390. if (_coordinator is null)
  391. {
  392. throw new ($"{nameof (IMainLoopCoordinator)} inexplicably became null during Run");
  393. }
  394. _coordinator.RunIteration ();
  395. }
  396. Logging.Information ($"Run - Calling End");
  397. Application.End (rs);
  398. }
  399. /// <summary>Shutdown an application initialized with <see cref="Init"/>.</summary>
  400. public void Shutdown ()
  401. {
  402. _coordinator?.Stop ();
  403. bool wasInitialized = _initialized;
  404. // Reset Screen before calling ResetState to avoid circular reference
  405. ResetScreen ();
  406. // Call ResetState FIRST so it can properly dispose Popover and other resources
  407. // that are accessed via Application.* static properties that now delegate to instance fields
  408. ResetState ();
  409. ConfigurationManager.PrintJsonErrors ();
  410. // Clear instance fields after ResetState has disposed everything
  411. _driver = null;
  412. _mouse = null;
  413. _keyboard = null;
  414. _initialized = false;
  415. _navigation = null;
  416. _popover = null;
  417. _top = null;
  418. _topLevels.Clear ();
  419. _mainThreadId = -1;
  420. _screen = null;
  421. _clearScreenNextIteration = false;
  422. _sixel.Clear ();
  423. // Don't reset ForceDriver and Force16Colors; they need to be set before Init is called
  424. if (wasInitialized)
  425. {
  426. bool init = _initialized; // Will be false after clearing fields above
  427. Application.OnInitializedChanged (this, new (in init));
  428. }
  429. _lazyInstance = new (() => new ApplicationImpl ());
  430. }
  431. /// <inheritdoc />
  432. public void RequestStop (Toplevel? top)
  433. {
  434. Logging.Logger.LogInformation ($"RequestStop '{(top is {} ? top : "null")}'");
  435. top ??= _top;
  436. if (top == null)
  437. {
  438. return;
  439. }
  440. ToplevelClosingEventArgs ev = new (top);
  441. top.OnClosing (ev);
  442. if (ev.Cancel)
  443. {
  444. return;
  445. }
  446. top.Running = false;
  447. }
  448. /// <inheritdoc />
  449. public void Invoke (Action action)
  450. {
  451. // If we are already on the main UI thread
  452. if (Application.Top is { Running: true } && _mainThreadId == Thread.CurrentThread.ManagedThreadId)
  453. {
  454. action ();
  455. return;
  456. }
  457. _timedEvents.Add (TimeSpan.Zero,
  458. () =>
  459. {
  460. action ();
  461. return false;
  462. }
  463. );
  464. }
  465. /// <inheritdoc />
  466. public bool IsLegacy => false;
  467. /// <inheritdoc />
  468. public object AddTimeout (TimeSpan time, Func<bool> callback) { return _timedEvents.Add (time, callback); }
  469. /// <inheritdoc />
  470. public bool RemoveTimeout (object token) { return _timedEvents.Remove (token); }
  471. /// <inheritdoc />
  472. public void LayoutAndDraw (bool forceRedraw = false)
  473. {
  474. List<View> tops = [.. _topLevels];
  475. if (_popover?.GetActivePopover () as View is { Visible: true } visiblePopover)
  476. {
  477. visiblePopover.SetNeedsDraw ();
  478. visiblePopover.SetNeedsLayout ();
  479. tops.Insert (0, visiblePopover);
  480. }
  481. bool neededLayout = View.Layout (tops.ToArray ().Reverse (), Screen.Size);
  482. if (ClearScreenNextIteration)
  483. {
  484. forceRedraw = true;
  485. ClearScreenNextIteration = false;
  486. }
  487. if (forceRedraw)
  488. {
  489. _driver?.ClearContents ();
  490. }
  491. View.SetClipToScreen ();
  492. View.Draw (tops, neededLayout || forceRedraw);
  493. View.SetClipToScreen ();
  494. _driver?.Refresh ();
  495. }
  496. /// <inheritdoc />
  497. public void ResetState (bool ignoreDisposed = false)
  498. {
  499. // Shutdown is the bookend for Init. As such it needs to clean up all resources
  500. // Init created. Apps that do any threading will need to code defensively for this.
  501. // e.g. see Issue #537
  502. foreach (Toplevel? t in _topLevels)
  503. {
  504. t!.Running = false;
  505. }
  506. if (_popover?.GetActivePopover () is View popover)
  507. {
  508. // This forcefully closes the popover; invoking Command.Quit would be more graceful
  509. // but since this is shutdown, doing this is ok.
  510. popover.Visible = false;
  511. }
  512. _popover?.Dispose ();
  513. _popover = null;
  514. _topLevels.Clear ();
  515. #if DEBUG_IDISPOSABLE
  516. // Don't dispose the Top. It's up to caller dispose it
  517. if (View.EnableDebugIDisposableAsserts && !ignoreDisposed && _top is { })
  518. {
  519. Debug.Assert (_top.WasDisposed, $"Title = {_top.Title}, Id = {_top.Id}");
  520. // If End wasn't called _cachedRunStateToplevel may be null
  521. if (_cachedRunStateToplevel is { })
  522. {
  523. Debug.Assert (_cachedRunStateToplevel.WasDisposed);
  524. Debug.Assert (_cachedRunStateToplevel == _top);
  525. }
  526. }
  527. #endif
  528. _top = null;
  529. _cachedRunStateToplevel = null;
  530. _mainThreadId = -1;
  531. // These static properties need to be reset
  532. Application.EndAfterFirstIteration = false;
  533. Application.ClearScreenNextIteration = false;
  534. Application.ClearForceFakeConsole ();
  535. // Driver stuff
  536. if (_driver is { })
  537. {
  538. Application.UnsubscribeDriverEvents ();
  539. _driver?.End ();
  540. _driver = null;
  541. }
  542. // Reset Screen to null so it will be recalculated on next access
  543. ResetScreen ();
  544. // Run State stuff - these are static events on Application class
  545. Application.ClearRunStateEvents ();
  546. // Mouse and Keyboard will be lazy-initialized in ApplicationImpl on next access
  547. _initialized = false;
  548. // Mouse
  549. // Do not clear _lastMousePosition; Popovers require it to stay set with
  550. // last mouse pos.
  551. //_lastMousePosition = null;
  552. Application.CachedViewsUnderMouse.Clear ();
  553. Application.ResetMouseState ();
  554. // Keyboard events and bindings are now managed by the Keyboard instance
  555. Application.ClearSizeChangingEvent ();
  556. _navigation = null;
  557. // Reset SupportedCultures so it's re-cached on next access
  558. _supportedCultures = null;
  559. // Reset synchronization context to allow the user to run async/await,
  560. // as the main loop has been ended, the synchronization context from
  561. // gui.cs does no longer process any callbacks. See #1084 for more details:
  562. // (https://github.com/gui-cs/Terminal.Gui/issues/1084).
  563. SynchronizationContext.SetSynchronizationContext (null);
  564. }
  565. /// <summary>
  566. /// Resets the Screen field to null so it will be recalculated on next access.
  567. /// </summary>
  568. internal void ResetScreen ()
  569. {
  570. lock (_lockScreen)
  571. {
  572. _screen = null;
  573. }
  574. }
  575. }