RemoteDesignerEntryPoint.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Net;
  4. using System.Reflection;
  5. using Avalonia.Controls;
  6. using Avalonia.Input;
  7. using Avalonia.Remote.Protocol;
  8. using Avalonia.Remote.Protocol.Designer;
  9. using Avalonia.Remote.Protocol.Viewport;
  10. using Avalonia.Threading;
  11. namespace Avalonia.DesignerSupport.Remote
  12. {
  13. public class RemoteDesignerEntryPoint
  14. {
  15. private static ClientSupportedPixelFormatsMessage s_supportedPixelFormats;
  16. private static ClientViewportAllocatedMessage s_viewportAllocatedMessage;
  17. private static IAvaloniaRemoteTransportConnection s_transport;
  18. class CommandLineArgs
  19. {
  20. public string AppPath { get; set; }
  21. public Uri Transport { get; set; }
  22. public string Method { get; set; } = Methods.AvaloniaRemote;
  23. public string SessionId { get; set; } = Guid.NewGuid().ToString();
  24. }
  25. static class Methods
  26. {
  27. public const string AvaloniaRemote = "avalonia-remote";
  28. public const string Win32 = "win32";
  29. }
  30. static Exception Die(string error)
  31. {
  32. if (error != null)
  33. {
  34. Console.Error.WriteLine(error);
  35. Console.Error.Flush();
  36. }
  37. Environment.Exit(1);
  38. return new Exception("APPEXIT");
  39. }
  40. static void Log(string message) => Console.WriteLine(message);
  41. static Exception PrintUsage()
  42. {
  43. Console.Error.WriteLine("Usage: --transport transport_spec --session-id sid --method method app");
  44. Console.Error.WriteLine();
  45. Console.Error.WriteLine("Example: --transport tcp-bson://127.0.0.1:30243/ --session-id 123 --method avalonia-remote MyApp.exe");
  46. Console.Error.Flush();
  47. return Die(null);
  48. }
  49. static CommandLineArgs ParseCommandLineArgs(string[] args)
  50. {
  51. var rv = new CommandLineArgs();
  52. Action<string> next = null;
  53. try
  54. {
  55. foreach (var arg in args)
  56. {
  57. if (next != null)
  58. {
  59. next(arg);
  60. next = null;
  61. }
  62. else if (arg == "--transport")
  63. next = a => rv.Transport = new Uri(a, UriKind.Absolute);
  64. else if (arg == "--method")
  65. next = a => rv.Method = a;
  66. else if (arg == "--session-id")
  67. next = a => rv.SessionId = a;
  68. else if (rv.AppPath == null)
  69. rv.AppPath = arg;
  70. else
  71. PrintUsage();
  72. }
  73. if (rv.AppPath == null || rv.Transport == null)
  74. PrintUsage();
  75. }
  76. catch
  77. {
  78. PrintUsage();
  79. }
  80. return rv;
  81. }
  82. static IAvaloniaRemoteTransportConnection CreateTransport(Uri transport)
  83. {
  84. if (transport.Scheme == "tcp-bson")
  85. {
  86. return new BsonTcpTransport().Connect(IPAddress.Parse(transport.Host), transport.Port).Result;
  87. }
  88. PrintUsage();
  89. return null;
  90. }
  91. interface IAppInitializer
  92. {
  93. Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, CommandLineArgs args, object obj);
  94. }
  95. class AppInitializer<T> : IAppInitializer where T : AppBuilderBase<T>, new()
  96. {
  97. public Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport,
  98. CommandLineArgs args, object obj)
  99. {
  100. var builder = (AppBuilderBase<T>) obj;
  101. if (args.Method == Methods.AvaloniaRemote)
  102. builder.UseWindowingSubsystem(() => PreviewerWindowingPlatform.Initialize(transport));
  103. if (args.Method == Methods.Win32)
  104. builder.UseWindowingSubsystem("Avalonia.Win32");
  105. builder.SetupWithoutStarting();
  106. return builder.Instance;
  107. }
  108. }
  109. private const string BuilderMethodName = "BuildAvaloniaApp";
  110. class NeverClose : ICloseable
  111. {
  112. public event EventHandler Closed
  113. {
  114. add {}
  115. remove {}
  116. }
  117. }
  118. public static void Main(string[] cmdline)
  119. {
  120. var args = ParseCommandLineArgs(cmdline);
  121. var transport = CreateTransport(args.Transport);
  122. var asm = Assembly.LoadFile(System.IO.Path.GetFullPath(args.AppPath));
  123. var entryPoint = asm.EntryPoint;
  124. if (entryPoint == null)
  125. throw Die($"Assembly {args.AppPath} doesn't have an entry point");
  126. var builderMethod = entryPoint.DeclaringType.GetMethod(BuilderMethodName,
  127. BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
  128. if (builderMethod == null)
  129. throw Die($"{entryPoint.DeclaringType.FullName} doesn't have a method named {BuilderMethodName}");
  130. Design.IsDesignMode = true;
  131. Log($"Obtaining AppBuilder instance from {builderMethod.DeclaringType.FullName}.{builderMethod.Name}");
  132. var appBuilder = builderMethod.Invoke(null, null);
  133. Log($"Initializing application in design mode");
  134. var initializer =(IAppInitializer)Activator.CreateInstance(typeof(AppInitializer<>).MakeGenericType(appBuilder.GetType()));
  135. var app = initializer.GetConfiguredApp(transport, args, appBuilder);
  136. s_transport = transport;
  137. transport.OnMessage += OnTransportMessage;
  138. transport.OnException += (t, e) => Die(e.ToString());
  139. Log("Sending StartDesignerSessionMessage");
  140. transport.Send(new StartDesignerSessionMessage {SessionId = args.SessionId});
  141. app.Run(new NeverClose());
  142. }
  143. private static void RebuildPreFlight()
  144. {
  145. PreviewerWindowingPlatform.PreFlightMessages = new List<object>
  146. {
  147. s_supportedPixelFormats,
  148. s_viewportAllocatedMessage
  149. };
  150. }
  151. private static Window s_currentWindow;
  152. private static void OnTransportMessage(IAvaloniaRemoteTransportConnection transport, object obj) => Dispatcher.UIThread.Post(() =>
  153. {
  154. if (obj is ClientSupportedPixelFormatsMessage formats)
  155. {
  156. s_supportedPixelFormats = formats;
  157. RebuildPreFlight();
  158. }
  159. if (obj is ClientViewportAllocatedMessage viewport)
  160. {
  161. s_viewportAllocatedMessage = viewport;
  162. RebuildPreFlight();
  163. }
  164. if (obj is UpdateXamlMessage xaml)
  165. {
  166. try
  167. {
  168. s_currentWindow?.Close();
  169. }
  170. catch
  171. {
  172. //Ignore
  173. }
  174. s_currentWindow = null;
  175. try
  176. {
  177. var dpi = s_viewportAllocatedMessage != null ? new Vector(s_viewportAllocatedMessage.DpiX, s_viewportAllocatedMessage.DpiY) : new Vector(96, 96);
  178. s_currentWindow = DesignWindowLoader.LoadDesignerWindow(xaml.Xaml, xaml.AssemblyPath, dpi);
  179. s_transport.Send(new UpdateXamlResultMessage(){Handle = s_currentWindow.PlatformImpl?.Handle?.Handle.ToString()});
  180. }
  181. catch (Exception e)
  182. {
  183. s_transport.Send(new UpdateXamlResultMessage
  184. {
  185. Error = e.ToString()
  186. });
  187. }
  188. }
  189. });
  190. }
  191. }