RemoteDesignerEntryPoint.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Net;
  4. using System.Reflection;
  5. using System.Threading;
  6. using System.Xml;
  7. using Avalonia.Controls;
  8. using Avalonia.DesignerSupport.Remote.HtmlTransport;
  9. using Avalonia.Input;
  10. using Avalonia.Remote.Protocol;
  11. using Avalonia.Remote.Protocol.Designer;
  12. using Avalonia.Remote.Protocol.Viewport;
  13. using Avalonia.Threading;
  14. namespace Avalonia.DesignerSupport.Remote
  15. {
  16. public class RemoteDesignerEntryPoint
  17. {
  18. private static ClientSupportedPixelFormatsMessage s_supportedPixelFormats;
  19. private static ClientViewportAllocatedMessage s_viewportAllocatedMessage;
  20. private static ClientRenderInfoMessage s_renderInfoMessage;
  21. private static IAvaloniaRemoteTransportConnection s_transport;
  22. class CommandLineArgs
  23. {
  24. public string AppPath { get; set; }
  25. public Uri Transport { get; set; }
  26. public Uri HtmlMethodListenUri { get; set; }
  27. public string Method { get; set; } = Methods.AvaloniaRemote;
  28. public string SessionId { get; set; } = Guid.NewGuid().ToString();
  29. }
  30. internal static class Methods
  31. {
  32. public const string AvaloniaRemote = "avalonia-remote";
  33. public const string Win32 = "win32";
  34. public const string Html = "html";
  35. }
  36. static Exception Die(string error)
  37. {
  38. if (error != null)
  39. {
  40. Console.Error.WriteLine(error);
  41. Console.Error.Flush();
  42. }
  43. Environment.Exit(1);
  44. return new Exception("APPEXIT");
  45. }
  46. static void Log(string message) => Console.WriteLine(message);
  47. static Exception PrintUsage()
  48. {
  49. Console.Error.WriteLine("Usage: --transport transport_spec --session-id sid --method method app");
  50. Console.Error.WriteLine();
  51. Console.Error.WriteLine("--transport: transport used for communication with the IDE");
  52. Console.Error.WriteLine(" 'tcp-bson' (e. g. 'tcp-bson://127.0.0.1:30243/') - TCP-based transport with BSON serialization of messages defined in Avalonia.Remote.Protocol");
  53. Console.Error.WriteLine(" 'file' (e. g. 'file://C://my/file.xaml' - pseudo-transport that triggers XAML updates on file changes, useful as a standalone previewer tool, always uses http preview method");
  54. Console.Error.WriteLine();
  55. Console.Error.WriteLine("--session-id: session id to be sent to IDE process");
  56. Console.Error.WriteLine();
  57. Console.Error.WriteLine("--method: the way the XAML is displayed");
  58. Console.Error.WriteLine(" 'avalonia-remote' - binary image is sent via transport connection in FrameMessage");
  59. Console.Error.WriteLine(" 'win32' - XAML is displayed in win32 window (handle could be obtained from UpdateXamlResultMessage), IDE is responsible to use user32!SetParent");
  60. Console.Error.WriteLine(" 'html' - Previewer starts an HTML server and displays XAML previewer as a web page");
  61. Console.Error.WriteLine();
  62. Console.Error.WriteLine("--html-url - endpoint for HTML method to listen on, e. g. http://127.0.0.1:8081");
  63. Console.Error.WriteLine();
  64. Console.Error.WriteLine("Example: --transport tcp-bson://127.0.0.1:30243/ --session-id 123 --method avalonia-remote MyApp.exe");
  65. Console.Error.Flush();
  66. return Die(null);
  67. }
  68. static CommandLineArgs ParseCommandLineArgs(string[] args)
  69. {
  70. var rv = new CommandLineArgs();
  71. Action<string> next = null;
  72. try
  73. {
  74. foreach (var arg in args)
  75. {
  76. if (next != null)
  77. {
  78. next(arg);
  79. next = null;
  80. }
  81. else if (arg == "--transport")
  82. next = a => rv.Transport = new Uri(a, UriKind.Absolute);
  83. else if (arg == "--method")
  84. next = a => rv.Method = a;
  85. else if (arg == "--html-url")
  86. next = a => rv.HtmlMethodListenUri = new Uri(a, UriKind.Absolute);
  87. else if (arg == "--session-id")
  88. next = a => rv.SessionId = a;
  89. else if (rv.AppPath == null)
  90. rv.AppPath = arg;
  91. else
  92. PrintUsage();
  93. }
  94. if (rv.AppPath == null || rv.Transport == null)
  95. PrintUsage();
  96. }
  97. catch
  98. {
  99. PrintUsage();
  100. }
  101. if (next != null)
  102. PrintUsage();
  103. return rv;
  104. }
  105. static IAvaloniaRemoteTransportConnection CreateTransport(CommandLineArgs args)
  106. {
  107. var transport = args.Transport;
  108. if (transport.Scheme == "tcp-bson")
  109. {
  110. return new BsonTcpTransport().Connect(IPAddress.Parse(transport.Host), transport.Port).Result;
  111. }
  112. if (transport.Scheme == "file")
  113. {
  114. return new FileWatcherTransport(transport, args.AppPath);
  115. }
  116. PrintUsage();
  117. return null;
  118. }
  119. interface IAppInitializer
  120. {
  121. IAvaloniaRemoteTransportConnection ConfigureApp(IAvaloniaRemoteTransportConnection transport, CommandLineArgs args, object obj);
  122. }
  123. class AppInitializer<T> : IAppInitializer where T : AppBuilderBase<T>, new()
  124. {
  125. public IAvaloniaRemoteTransportConnection ConfigureApp(IAvaloniaRemoteTransportConnection transport,
  126. CommandLineArgs args, object obj)
  127. {
  128. var builder = (AppBuilderBase<T>)obj;
  129. if (args.Method == Methods.AvaloniaRemote)
  130. builder.UseWindowingSubsystem(() => PreviewerWindowingPlatform.Initialize(transport));
  131. if (args.Method == Methods.Html)
  132. {
  133. transport = new HtmlWebSocketTransport(transport,
  134. args.HtmlMethodListenUri ?? new Uri("http://localhost:5000"));
  135. builder.UseWindowingSubsystem(() =>
  136. PreviewerWindowingPlatform.Initialize(transport));
  137. }
  138. if (args.Method == Methods.Win32)
  139. builder.UseWindowingSubsystem("Avalonia.Win32");
  140. builder.SetupWithoutStarting();
  141. return transport;
  142. }
  143. }
  144. private const string BuilderMethodName = "BuildAvaloniaApp";
  145. public static void Main(string[] cmdline)
  146. {
  147. var args = ParseCommandLineArgs(cmdline);
  148. var transport = CreateTransport(args);
  149. if (transport is ITransportWithEnforcedMethod enforcedMethod)
  150. args.Method = enforcedMethod.PreviewerMethod;
  151. var asm = Assembly.LoadFile(System.IO.Path.GetFullPath(args.AppPath));
  152. var entryPoint = asm.EntryPoint;
  153. if (entryPoint == null)
  154. throw Die($"Assembly {args.AppPath} doesn't have an entry point");
  155. var builderMethod = entryPoint.DeclaringType.GetMethod(BuilderMethodName,
  156. BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Array.Empty<Type>(), null);
  157. if (builderMethod == null)
  158. throw Die($"{entryPoint.DeclaringType.FullName} doesn't have a method named {BuilderMethodName}");
  159. Design.IsDesignMode = true;
  160. Log($"Obtaining AppBuilder instance from {builderMethod.DeclaringType.FullName}.{builderMethod.Name}");
  161. var appBuilder = builderMethod.Invoke(null, null);
  162. Log($"Initializing application in design mode");
  163. var initializer =(IAppInitializer)Activator.CreateInstance(typeof(AppInitializer<>).MakeGenericType(appBuilder.GetType()));
  164. transport = initializer.ConfigureApp(transport, args, appBuilder);
  165. s_transport = transport;
  166. transport.OnMessage += OnTransportMessage;
  167. transport.OnException += (t, e) => Die(e.ToString());
  168. transport.Start();
  169. Log("Sending StartDesignerSessionMessage");
  170. transport.Send(new StartDesignerSessionMessage {SessionId = args.SessionId});
  171. Dispatcher.UIThread.MainLoop(CancellationToken.None);
  172. }
  173. private static void RebuildPreFlight()
  174. {
  175. PreviewerWindowingPlatform.PreFlightMessages = new List<object>
  176. {
  177. s_supportedPixelFormats,
  178. s_viewportAllocatedMessage,
  179. s_renderInfoMessage
  180. };
  181. }
  182. private static Window s_currentWindow;
  183. private static void OnTransportMessage(IAvaloniaRemoteTransportConnection transport, object obj) => Dispatcher.UIThread.Post(() =>
  184. {
  185. if (obj is ClientSupportedPixelFormatsMessage formats)
  186. {
  187. s_supportedPixelFormats = formats;
  188. RebuildPreFlight();
  189. }
  190. if (obj is ClientRenderInfoMessage renderInfo)
  191. {
  192. s_renderInfoMessage = renderInfo;
  193. RebuildPreFlight();
  194. }
  195. if (obj is ClientViewportAllocatedMessage viewport)
  196. {
  197. s_viewportAllocatedMessage = viewport;
  198. RebuildPreFlight();
  199. }
  200. if (obj is UpdateXamlMessage xaml)
  201. {
  202. try
  203. {
  204. s_currentWindow?.Close();
  205. }
  206. catch
  207. {
  208. //Ignore
  209. }
  210. s_currentWindow = null;
  211. try
  212. {
  213. s_currentWindow = DesignWindowLoader.LoadDesignerWindow(xaml.Xaml, xaml.AssemblyPath, xaml.XamlFileProjectPath);
  214. s_transport.Send(new UpdateXamlResultMessage(){Handle = s_currentWindow.PlatformImpl?.Handle?.Handle.ToString()});
  215. }
  216. catch (Exception e)
  217. {
  218. s_transport.Send(new UpdateXamlResultMessage
  219. {
  220. Error = e.ToString(),
  221. Exception = new ExceptionDetails(e),
  222. });
  223. }
  224. }
  225. });
  226. }
  227. }