mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-22 09:14:25 +08:00
feat.尝试弄了AOT的启动器。
This commit is contained in:
@@ -34,15 +34,15 @@ internal sealed class LauncherFlowCoordinator
|
||||
_oobeSteps = [new WelcomeOobeStep(_oobeStateService)];
|
||||
}
|
||||
|
||||
public async Task<LauncherResult> RunAsync()
|
||||
public async Task<LauncherResult> RunAsync(SplashWindow? existingSplashWindow = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 清理待删除的旧版本
|
||||
_deploymentLocator.CleanupDestroyedDeployments();
|
||||
|
||||
// 显示 Splash 窗口
|
||||
var splashWindow = await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
// 使用传入的 Splash 窗口或创建新的
|
||||
var splashWindow = existingSplashWindow ?? await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
var window = new SplashWindow();
|
||||
window.Show();
|
||||
@@ -51,12 +51,29 @@ internal sealed class LauncherFlowCoordinator
|
||||
|
||||
var reporter = (ISplashStageReporter)splashWindow;
|
||||
|
||||
// 跟踪主程序是否已就绪,就绪后自动关闭 Splash 窗口
|
||||
var hostReadyTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
// 启动 IPC 服务端监听主程序进度
|
||||
using var ipcServer = new LauncherIpcServer(msg =>
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
reporter.Report(msg.Stage.ToString().ToLower(), msg.Message ?? "");
|
||||
try
|
||||
{
|
||||
reporter.Report(msg.Stage.ToString().ToLower(), msg.Message ?? "");
|
||||
|
||||
// 主程序报告就绪后,关闭 Splash 窗口
|
||||
if (msg.Stage == StartupStage.Ready && splashWindow.IsVisible && splashWindow.IsLoaded)
|
||||
{
|
||||
splashWindow.Close();
|
||||
hostReadyTcs.TrySetResult();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[LauncherFlowCoordinator] Error in IPC callback: {ex.Message}");
|
||||
}
|
||||
});
|
||||
});
|
||||
ipcServer.Start();
|
||||
@@ -94,14 +111,53 @@ internal sealed class LauncherFlowCoordinator
|
||||
|
||||
// 启动主程序
|
||||
reporter.Report("launch", "正在启动...");
|
||||
var hostResult = await LaunchHostWithIpcAsync();
|
||||
var (hostResult, hostProcess) = await LaunchHostWithIpcAsync(splashWindow);
|
||||
if (!hostResult.Success)
|
||||
{
|
||||
return hostResult;
|
||||
}
|
||||
|
||||
// 等待主程序就绪或超时
|
||||
await Task.Delay(TimeSpan.FromSeconds(30));
|
||||
// 等待主程序进程退出。Launcher 作为后台守护进程保持运行,
|
||||
// 维持 IPC 管道服务端供主程序报告启动进度。
|
||||
if (hostProcess is not null)
|
||||
{
|
||||
// 等待主程序就绪或进程退出(取先发生者)
|
||||
// 如果主程序在 60 秒内未报告 Ready,也关闭 Splash 窗口作为超时保护
|
||||
var readyOrTimeout = Task.WhenAny(
|
||||
hostReadyTcs.Task,
|
||||
Task.Delay(TimeSpan.FromSeconds(60)));
|
||||
|
||||
var processExitTask = hostProcess.WaitForExitAsync();
|
||||
|
||||
// 先等待就绪/超时,然后等待进程退出
|
||||
await readyOrTimeout;
|
||||
|
||||
// 如果 Splash 窗口仍然打开(超时情况),关闭它
|
||||
if (splashWindow.IsVisible)
|
||||
{
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (splashWindow.IsVisible && splashWindow.IsLoaded)
|
||||
{
|
||||
splashWindow.Close();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[LauncherFlowCoordinator] Error closing splash window on timeout: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await processExitTask;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果无法获取进程引用,退回到有限等待
|
||||
await Task.Delay(TimeSpan.FromSeconds(30));
|
||||
}
|
||||
|
||||
return new LauncherResult
|
||||
{
|
||||
@@ -113,7 +169,22 @@ internal sealed class LauncherFlowCoordinator
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Dispatcher.UIThread.InvokeAsync(() => splashWindow.Close());
|
||||
// Splash 窗口可能已由 IPC Ready 回调关闭,这里做安全清理
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (splashWindow.IsVisible && splashWindow.IsLoaded)
|
||||
{
|
||||
splashWindow.Close();
|
||||
Console.WriteLine("[LauncherFlowCoordinator] Splash window closed in finally block");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[LauncherFlowCoordinator] Error closing splash window in finally: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -124,12 +195,12 @@ internal sealed class LauncherFlowCoordinator
|
||||
Stage = "launch",
|
||||
Code = "exception",
|
||||
Message = ex.Message,
|
||||
ErrorMessage = ex.Message
|
||||
ErrorMessage = ex.ToString()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<LauncherResult> LaunchHostWithIpcAsync(string? customHostPath = null)
|
||||
private async Task<(LauncherResult Result, Process? Process)> LaunchHostWithIpcAsync(SplashWindow? splashWindow = null, string? customHostPath = null)
|
||||
{
|
||||
// 优先使用自定义路径(调试模式选择的路径)
|
||||
var hostPath = customHostPath ?? _deploymentLocator.ResolveHostExecutablePath();
|
||||
@@ -145,19 +216,19 @@ internal sealed class LauncherFlowCoordinator
|
||||
// 用户选择重试,如果有选择路径则使用,否则重新尝试
|
||||
if (!string.IsNullOrWhiteSpace(selectedPath))
|
||||
{
|
||||
return await LaunchHostWithIpcAsync(selectedPath);
|
||||
return await LaunchHostWithIpcAsync(splashWindow, selectedPath);
|
||||
}
|
||||
return await LaunchHostWithIpcAsync();
|
||||
return await LaunchHostWithIpcAsync(splashWindow);
|
||||
}
|
||||
|
||||
// 用户选择退出
|
||||
return new LauncherResult
|
||||
return (new LauncherResult
|
||||
{
|
||||
Success = false,
|
||||
Stage = "launchHost",
|
||||
Code = "host_not_found",
|
||||
Message = "LanMountainDesktop host executable not found."
|
||||
};
|
||||
}, null);
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
@@ -168,24 +239,40 @@ internal sealed class LauncherFlowCoordinator
|
||||
var processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = hostPath,
|
||||
UseShellExecute = true,
|
||||
UseShellExecute = false,
|
||||
WorkingDirectory = Path.GetDirectoryName(hostPath) ?? _deploymentLocator.GetAppRoot()
|
||||
};
|
||||
|
||||
// 转发命令行参数给主程序(排除 Launcher 自己的命令和选项)
|
||||
foreach (var arg in _context.RawArgs)
|
||||
{
|
||||
// 跳过 Launcher 自己的命令和选项,只传递用户原始参数
|
||||
if (arg == _context.Command || arg == _context.SubCommand || arg.StartsWith("--"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
processStartInfo.ArgumentList.Add(arg);
|
||||
}
|
||||
|
||||
// 传递环境变量供 IPC 使用
|
||||
processStartInfo.EnvironmentVariables[LauncherIpcConstants.LauncherPidEnvVar] =
|
||||
Environment.ProcessId.ToString();
|
||||
processStartInfo.EnvironmentVariables[LauncherIpcConstants.PackageRootEnvVar] =
|
||||
_deploymentLocator.GetAppRoot();
|
||||
|
||||
// 传递版本信息
|
||||
var versionInfo = _deploymentLocator.GetVersionInfo();
|
||||
processStartInfo.EnvironmentVariables[LauncherIpcConstants.VersionEnvVar] = versionInfo.Version;
|
||||
processStartInfo.EnvironmentVariables[LauncherIpcConstants.CodenameEnvVar] = versionInfo.Codename;
|
||||
|
||||
Process.Start(processStartInfo);
|
||||
return new LauncherResult
|
||||
var hostProcess = Process.Start(processStartInfo);
|
||||
return (new LauncherResult
|
||||
{
|
||||
Success = true,
|
||||
Stage = "launchHost",
|
||||
Code = "ok",
|
||||
Message = "Host launched."
|
||||
};
|
||||
}, hostProcess);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -193,19 +280,65 @@ internal sealed class LauncherFlowCoordinator
|
||||
/// </summary>
|
||||
private async Task<(ErrorWindowResult Result, string? CustomPath)> ShowHostNotFoundErrorAsync()
|
||||
{
|
||||
return await Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
ErrorWindow? errorWindow = null;
|
||||
|
||||
// 在 UI 线程创建并显示错误窗口
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
var errorWindow = new ErrorWindow();
|
||||
errorWindow.SetErrorMessage("找不到阑山桌面应用程序。");
|
||||
errorWindow.Show();
|
||||
|
||||
var result = await errorWindow.WaitForChoiceAsync();
|
||||
var customPath = errorWindow.GetCustomHostPath();
|
||||
|
||||
await Dispatcher.UIThread.InvokeAsync(() => errorWindow.Close());
|
||||
|
||||
return (result, customPath);
|
||||
try
|
||||
{
|
||||
errorWindow = new ErrorWindow();
|
||||
errorWindow.SetErrorMessage("找不到阑山桌面应用程序。");
|
||||
errorWindow.Show();
|
||||
Console.WriteLine("[LauncherFlowCoordinator] ErrorWindow shown for host not found");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[LauncherFlowCoordinator] Failed to show ErrorWindow: {ex.Message}");
|
||||
}
|
||||
});
|
||||
|
||||
if (errorWindow is null)
|
||||
{
|
||||
Console.Error.WriteLine("[LauncherFlowCoordinator] ErrorWindow is null, cannot wait for choice");
|
||||
return (ErrorWindowResult.Exit, null);
|
||||
}
|
||||
|
||||
// 等待用户选择
|
||||
ErrorWindowResult result;
|
||||
string? customPath;
|
||||
|
||||
try
|
||||
{
|
||||
result = await errorWindow.WaitForChoiceAsync();
|
||||
customPath = errorWindow.GetCustomHostPath();
|
||||
Console.WriteLine($"[LauncherFlowCoordinator] ErrorWindow result: {result}, customPath: {customPath != null}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[LauncherFlowCoordinator] Error waiting for choice: {ex.Message}");
|
||||
result = ErrorWindowResult.Exit;
|
||||
customPath = null;
|
||||
}
|
||||
|
||||
// 安全关闭错误窗口
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (errorWindow.IsVisible && errorWindow.IsLoaded)
|
||||
{
|
||||
errorWindow.Close();
|
||||
Console.WriteLine("[LauncherFlowCoordinator] ErrorWindow closed successfully");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[LauncherFlowCoordinator] Error closing ErrorWindow: {ex.Message}");
|
||||
}
|
||||
});
|
||||
|
||||
return (result, customPath);
|
||||
}
|
||||
|
||||
private static void EnsureExecutable(string path)
|
||||
@@ -237,22 +370,72 @@ internal sealed class LauncherFlowCoordinator
|
||||
|
||||
public async Task RunAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var window = await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
var oobeWindow = new OobeWindow();
|
||||
oobeWindow.Show();
|
||||
return oobeWindow;
|
||||
});
|
||||
|
||||
OobeWindow? window = null;
|
||||
|
||||
try
|
||||
{
|
||||
using var _ = cancellationToken.Register(() => window.Close());
|
||||
window = await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var oobeWindow = new OobeWindow();
|
||||
oobeWindow.Show();
|
||||
Console.WriteLine("[WelcomeOobeStep] OOBE window shown");
|
||||
return oobeWindow;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[WelcomeOobeStep] Failed to show OOBE window: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
if (window is null)
|
||||
{
|
||||
Console.Error.WriteLine("[WelcomeOobeStep] OOBE window is null, skipping OOBE");
|
||||
_stateService.MarkCompleted();
|
||||
return;
|
||||
}
|
||||
|
||||
using var _ = cancellationToken.Register(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (window.IsVisible && window.IsLoaded)
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[WelcomeOobeStep] Error closing OOBE window on cancel: {ex.Message}");
|
||||
}
|
||||
});
|
||||
|
||||
await window.WaitForEnterAsync().ConfigureAwait(false);
|
||||
Console.WriteLine("[WelcomeOobeStep] OOBE completed by user");
|
||||
_stateService.MarkCompleted();
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Dispatcher.UIThread.InvokeAsync(() => window.Close());
|
||||
if (window is not null)
|
||||
{
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (window.IsVisible && window.IsLoaded)
|
||||
{
|
||||
window.Close();
|
||||
Console.WriteLine("[WelcomeOobeStep] OOBE window closed in finally");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[WelcomeOobeStep] Error closing OOBE window in finally: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user