public static void Run <TStartup>(string windowTitle, string hostHtmlPath, bool fullscreen = false, int x = 0, int y = 0, int width = 800, int height = 600) { DesktopSynchronizationContext.UnhandledException += (sender, exception) => { UnhandledException(exception); }; photinoWindow = new PhotinoWindow(windowTitle, options => { var contentRootAbsolute = Path.GetDirectoryName(Path.GetFullPath(hostHtmlPath)); options.SchemeHandlers.Add(BlazorAppScheme, (string url, out string contentType) => { // TODO: Only intercept for the hostname 'app' and passthrough for others // TODO: Prevent directory traversal? var appFile = Path.Combine(contentRootAbsolute, new Uri(url).AbsolutePath.Substring(1)); if (appFile == contentRootAbsolute) { appFile = hostHtmlPath; } contentType = GetContentType(appFile); return(File.Exists(appFile) ? File.OpenRead(appFile) : null); }); // framework:// is resolved as embedded resources options.SchemeHandlers.Add("framework", (string url, out string contentType) => { contentType = GetContentType(url); return(SupplyFrameworkFile(url)); }); }, fullscreen, x, y, width, height); CancellationTokenSource appLifetimeCts = new CancellationTokenSource(); Task.Factory.StartNew(async() => { try { var ipc = new IPC(photinoWindow); await RunAsync <TStartup>(ipc, appLifetimeCts.Token); } catch (Exception ex) { UnhandledException(ex); throw; } }); try { photinoWindow.NavigateToUrl(BlazorAppScheme + "://app/"); photinoWindow.WaitForExit(); } finally { appLifetimeCts.Cancel(); } }