ApplicationImpl.Driver.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. using System.Collections.Concurrent;
  2. namespace Terminal.Gui.App;
  3. internal partial class ApplicationImpl
  4. {
  5. /// <inheritdoc/>
  6. public IDriver? Driver { get; set; }
  7. /// <inheritdoc/>
  8. public string ForceDriver { get; set; } = string.Empty;
  9. /// <summary>
  10. /// Creates the appropriate <see cref="IDriver"/> based on platform and driverName.
  11. /// </summary>
  12. /// <param name="driverName"></param>
  13. /// <returns></returns>
  14. /// <exception cref="Exception"></exception>
  15. /// <exception cref="InvalidOperationException"></exception>
  16. private void CreateDriver (string? driverName)
  17. {
  18. PlatformID p = Environment.OSVersion.Platform;
  19. // Check component factory type first - this takes precedence over driverName
  20. bool factoryIsWindows = _componentFactory is IComponentFactory<WindowsConsole.InputRecord>;
  21. bool factoryIsDotNet = _componentFactory is IComponentFactory<ConsoleKeyInfo>;
  22. bool factoryIsUnix = _componentFactory is IComponentFactory<char>;
  23. bool factoryIsFake = _componentFactory is IComponentFactory<ConsoleKeyInfo>;
  24. // Then check driverName
  25. bool nameIsWindows = driverName?.Contains ("windows", StringComparison.OrdinalIgnoreCase) ?? false;
  26. bool nameIsDotNet = driverName?.Contains ("dotnet", StringComparison.OrdinalIgnoreCase) ?? false;
  27. bool nameIsUnix = driverName?.Contains ("unix", StringComparison.OrdinalIgnoreCase) ?? false;
  28. bool nameIsFake = driverName?.Contains ("fake", StringComparison.OrdinalIgnoreCase) ?? false;
  29. // Decide which driver to use - component factory type takes priority
  30. if (factoryIsFake || (!factoryIsWindows && !factoryIsDotNet && !factoryIsUnix && nameIsFake))
  31. {
  32. Coordinator = CreateSubcomponents (() => new FakeComponentFactory ());
  33. _driverName = "fake";
  34. }
  35. else if (factoryIsWindows || (!factoryIsDotNet && !factoryIsUnix && nameIsWindows))
  36. {
  37. Coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
  38. _driverName = "windows";
  39. }
  40. else if (factoryIsDotNet || (!factoryIsWindows && !factoryIsUnix && nameIsDotNet))
  41. {
  42. Coordinator = CreateSubcomponents (() => new NetComponentFactory ());
  43. _driverName = "dotnet";
  44. }
  45. else if (factoryIsUnix || (!factoryIsWindows && !factoryIsDotNet && nameIsUnix))
  46. {
  47. Coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
  48. _driverName = "unix";
  49. }
  50. else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows)
  51. {
  52. Coordinator = CreateSubcomponents (() => new WindowsComponentFactory ());
  53. _driverName = "windows";
  54. }
  55. else if (p == PlatformID.Unix)
  56. {
  57. Coordinator = CreateSubcomponents (() => new UnixComponentFactory ());
  58. _driverName = "unix";
  59. }
  60. else
  61. {
  62. Logging.Information($"Falling back to dotnet driver.");
  63. Coordinator = CreateSubcomponents (() => new NetComponentFactory ());
  64. _driverName = "dotnet";
  65. }
  66. Logging.Trace ($"Created Subcomponents: {Coordinator}");
  67. Coordinator.StartInputTaskAsync (this).Wait ();
  68. if (Driver == null)
  69. {
  70. throw new ("Driver was null even after booting MainLoopCoordinator");
  71. }
  72. Driver.Force16Colors = Terminal.Gui.Drivers.Driver.Force16Colors;
  73. }
  74. private readonly IComponentFactory? _componentFactory;
  75. /// <summary>
  76. /// INTERNAL: Gets or sets the main loop coordinator that orchestrates the application's event processing,
  77. /// input handling, and rendering pipeline.
  78. /// </summary>
  79. /// <remarks>
  80. /// <para>
  81. /// The <see cref="IMainLoopCoordinator"/> is the central component responsible for:
  82. /// <list type="bullet">
  83. /// <item>Managing the platform-specific input thread that reads from the console</item>
  84. /// <item>Coordinating the main application loop via <see cref="IMainLoopCoordinator.RunIteration"/></item>
  85. /// <item>Processing queued input events and translating them to Terminal.Gui events</item>
  86. /// <item>Managing the <see cref="ApplicationMainLoop{TInputRecord}"/> that handles rendering</item>
  87. /// <item>Executing scheduled timeouts and callbacks via <see cref="ITimedEvents"/></item>
  88. /// </list>
  89. /// </para>
  90. /// <para>
  91. /// The coordinator is created in <see cref="CreateDriver"/> based on the selected driver
  92. /// (Windows, Unix, .NET, or Fake) and is started by calling
  93. /// <see cref="IMainLoopCoordinator.StartInputTaskAsync"/>.
  94. /// </para>
  95. /// </remarks>
  96. internal IMainLoopCoordinator? Coordinator { get; private set; }
  97. /// <summary>
  98. /// INTERNAL: Creates a <see cref="MainLoopCoordinator{TInputRecord}"/> with the appropriate component factory
  99. /// for the specified input record type.
  100. /// </summary>
  101. /// <typeparam name="TInputRecord">
  102. /// Platform-specific input type: <see cref="ConsoleKeyInfo"/> (.NET/Fake),
  103. /// <see cref="WindowsConsole.InputRecord"/> (Windows), or <see cref="char"/> (Unix).
  104. /// </typeparam>
  105. /// <param name="fallbackFactory">
  106. /// Factory function to create the component factory if <see cref="_componentFactory"/>
  107. /// is not of type <see cref="IComponentFactory{TInputRecord}"/>.
  108. /// </param>
  109. /// <returns>
  110. /// A <see cref="MainLoopCoordinator{TInputRecord}"/> configured with the input queue,
  111. /// main loop, timed events, and selected component factory.
  112. /// </returns>
  113. private IMainLoopCoordinator CreateSubcomponents<TInputRecord> (Func<IComponentFactory<TInputRecord>> fallbackFactory) where TInputRecord : struct
  114. {
  115. ConcurrentQueue<TInputRecord> inputQueue = new ();
  116. ApplicationMainLoop<TInputRecord> loop = new ();
  117. IComponentFactory<TInputRecord> cf;
  118. if (_componentFactory is IComponentFactory<TInputRecord> typedFactory)
  119. {
  120. cf = typedFactory;
  121. }
  122. else
  123. {
  124. cf = fallbackFactory ();
  125. }
  126. return new MainLoopCoordinator<TInputRecord> (_timedEvents, inputQueue, loop, cf);
  127. }
  128. internal void SubscribeDriverEvents ()
  129. {
  130. if (Driver is null)
  131. {
  132. Logging.Error($"Driver is null");
  133. return;
  134. }
  135. Driver.SizeChanged += Driver_SizeChanged;
  136. Driver.KeyDown += Driver_KeyDown;
  137. Driver.KeyUp += Driver_KeyUp;
  138. Driver.MouseEvent += Driver_MouseEvent;
  139. }
  140. internal void UnsubscribeDriverEvents ()
  141. {
  142. if (Driver is null)
  143. {
  144. Logging.Error ($"Driver is null");
  145. return;
  146. }
  147. Driver.SizeChanged -= Driver_SizeChanged;
  148. Driver.KeyDown -= Driver_KeyDown;
  149. Driver.KeyUp -= Driver_KeyUp;
  150. Driver.MouseEvent -= Driver_MouseEvent;
  151. }
  152. private void Driver_KeyDown (object? sender, Key e) { Keyboard?.RaiseKeyDownEvent (e); }
  153. private void Driver_KeyUp (object? sender, Key e) { Keyboard?.RaiseKeyUpEvent (e); }
  154. private void Driver_MouseEvent (object? sender, MouseEventArgs e) { Mouse?.RaiseMouseEvent (e); }
  155. }