using System.Diagnostics; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Threading; using LanMountainDesktop.Launcher.Models; using LanMountainDesktop.Launcher.Resources; using LanMountainDesktop.Launcher.Views; using LanMountainDesktop.Shared.Contracts.Launcher; using LanMountainDesktop.Shared.IPC; using LanMountainDesktop.Shared.IPC.Abstractions.Services; namespace LanMountainDesktop.Launcher.Shell; internal static class LauncherGuiCoordinator { public static async Task RunAsync( IClassicDesktopStyleApplicationLifetime desktop, CommandContext context, SplashWindow splashWindow) { LauncherResult result; SplashWindow? currentSplashWindow = splashWindow; var appRoot = Commands.ResolveAppRoot(context); var dataLocationResolver = new DataLocationResolver(appRoot); var startupAttemptRegistry = new StartupAttemptRegistry(); var coordinatorPipeName = LauncherCoordinatorIpcServer.CreatePipeName(); var successPolicy = LauncherOrchestrator.ResolveSuccessPolicyKey(context); if (!startupAttemptRegistry.TryReserveCoordinator( context.LaunchSource, successPolicy, coordinatorPipeName, out var reservedAttempt, out var activeCoordinatorAttempt)) { result = await AttachToExistingCoordinatorAsync( context, currentSplashWindow, activeCoordinatorAttempt).ConfigureAwait(false); Logger.Info($"Secondary launcher completed. Success={result.Success}; Code='{result.Code}'."); await WriteLauncherResultAsync(context, result).ConfigureAwait(false); Environment.ExitCode = result.Success ? 0 : 1; await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(Environment.ExitCode), DispatcherPriority.Background); return; } using var coordinatorServer = new LauncherCoordinatorIpcServer( coordinatorPipeName, BuildCoordinatorStatusFromAttempt(reservedAttempt), HandleCoordinatorRequestAsync, startupAttemptRegistry.UpdateOwnedCoordinatorHeartbeat); coordinatorServer.Start(); while (true) { try { Logger.Info( $"Coordinator start. Command='{context.Command}'; AppRoot='{appRoot}'; " + $"IsDebugMode={context.IsDebugMode}; LaunchSource='{context.LaunchSource}'; " + $"ResultPath='{context.GetOption("result") ?? ""}'."); var orchestrator = LauncherCompositionRoot.CreateOrchestrator( context, appRoot, startupAttemptRegistry, coordinatorServer); result = await orchestrator.RunAsync(currentSplashWindow).ConfigureAwait(false); } catch (Exception ex) { Logger.Error("Coordinator threw an unhandled exception.", ex); result = new LauncherResult { Success = false, Stage = "launch", Code = "exception", Message = $"Launcher failed: {ex.Message}", ErrorMessage = ex.ToString() }; } if (result.Success || result.Code == "host_not_found" || (!string.Equals(result.Stage, "launch", StringComparison.OrdinalIgnoreCase) && !string.Equals(result.Stage, "launchHost", StringComparison.OrdinalIgnoreCase))) { break; } var failureAction = await ShowFailureWindowAsync(result).ConfigureAwait(false); if (failureAction == ErrorWindowResult.Exit) { break; } if (failureAction == ErrorWindowResult.ActivateExisting && await TryActivateExistingInstanceAsync().ConfigureAwait(false)) { result = new LauncherResult { Success = true, Stage = "launch", Code = "activation_requested", Message = "Launcher activated the existing desktop instance.", Details = result.Details }; break; } currentSplashWindow = CreateSplashWindow(); currentSplashWindow.Show(); } Logger.Info($"Coordinator completed. Success={result.Success}; Stage='{result.Stage}'; Code='{result.Code}'."); await WriteLauncherResultAsync(context, result).ConfigureAwait(false); Environment.ExitCode = result.Success ? 0 : 1; if (result.Success) { var hostPid = ResolveManagedHostPid(result, startupAttemptRegistry.GetOwnedAttempt()?.HostPid ?? 0); var airAppRuntimeBridge = new AirAppRuntimeBridge(appRoot, dataLocationResolver.ResolveDataRoot()); await airAppRuntimeBridge.AttachHostAsync(hostPid).ConfigureAwait(false); await WaitForHostProcessToExitAsync(hostPid).ConfigureAwait(false); } await Dispatcher.UIThread.InvokeAsync(() => desktop.Shutdown(Environment.ExitCode), DispatcherPriority.Background); } private static SplashWindow CreateSplashWindow() { var window = new SplashWindow(); TrySetSplashVersionInfo(window, LauncherRuntimeContext.Current); return window; } private static void TrySetSplashVersionInfo(SplashWindow window, CommandContext context) { try { var appRoot = Commands.ResolveAppRoot(context); var versionInfo = new DeploymentLocator(appRoot).GetVersionInfo(); window.SetVersionInfo(versionInfo.Version, versionInfo.Codename); } catch (Exception ex) { Logger.Warn($"Failed to set splash version info before coordinator start: {ex.Message}"); } } private static int ResolveManagedHostPid(LauncherResult result, int fallbackHostPid) { if (result.Details.TryGetValue("hostPid", out var hostPidText) && int.TryParse(hostPidText, out var hostPid)) { return hostPid; } if (result.Details.TryGetValue("existingHostPid", out var existingHostPidText) && int.TryParse(existingHostPidText, out var existingHostPid)) { return existingHostPid; } return fallbackHostPid; } private static async Task WaitForHostProcessToExitAsync(int hostPid) { Logger.Info($"Launcher entering host background lifetime. HostPid={hostPid}."); while (TryGetLiveProcess(hostPid)) { await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false); } Logger.Info("Launcher host background lifetime completed; host process is gone."); } private static async Task AttachToExistingCoordinatorAsync( CommandContext context, SplashWindow? splashWindow, StartupAttemptRecord? activeCoordinatorAttempt) { var reporter = splashWindow as ISplashStageReporter; reporter?.Report("activation", Strings.Preview_ActivationConnecting); if (activeCoordinatorAttempt is not null && !string.IsNullOrWhiteSpace(activeCoordinatorAttempt.CoordinatorPipeName)) { var command = string.Equals(context.LaunchSource, "restart", StringComparison.OrdinalIgnoreCase) ? LauncherCoordinatorCommands.Attach : LauncherCoordinatorCommands.ActivateDesktop; var request = new LauncherCoordinatorRequest { Command = command, LaunchSource = context.LaunchSource, SuccessPolicy = LauncherOrchestrator.ResolveSuccessPolicyKey(context) }; var response = await new LauncherCoordinatorIpcClient() .SendAsync(activeCoordinatorAttempt.CoordinatorPipeName, request, TimeSpan.FromSeconds(2)) .ConfigureAwait(false); if (response is not null) { reporter?.Report("activation", response.Message); await DismissSplashIfNeededAsync(splashWindow).ConfigureAwait(false); var success = response.Accepted || IsRecoverableActivationFailure(response.ActivationResult, response.Status); return new LauncherResult { Success = success, Stage = "launch", Code = success && !response.Accepted ? "attached_to_launcher_coordinator" : response.Code, Message = success && !response.Accepted ? "Attached to the active Launcher coordinator; desktop startup is still in progress." : response.Message, Details = BuildCoordinatorResultDetails(response.Status, response.ActivationResult) }; } } var activation = await TryActivateExistingInstanceWithStatusAsync(TimeSpan.FromSeconds(2)).ConfigureAwait(false); if (activation is not null) { reporter?.Report("activation", activation.Message); await DismissSplashIfNeededAsync(splashWindow).ConfigureAwait(false); var success = activation.Accepted || IsRecoverableActivationFailure(activation, null); return new LauncherResult { Success = success, Stage = "launch", Code = activation.Accepted ? "existing_host_activated" : success ? "existing_host_startup_pending" : "existing_host_activation_failed", Message = success && !activation.Accepted ? "Existing desktop process is still starting; Launcher attached without starting another process." : activation.Message, Details = BuildCoordinatorResultDetails(null, activation) }; } await DismissSplashIfNeededAsync(splashWindow).ConfigureAwait(false); return new LauncherResult { Success = false, Stage = "launch", Code = "launcher_coordinator_unavailable", Message = "Another Launcher is coordinating startup, but it did not respond in time.", Details = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["activeCoordinatorPid"] = activeCoordinatorAttempt?.CoordinatorPid.ToString() ?? string.Empty, ["activeCoordinatorPipeName"] = activeCoordinatorAttempt?.CoordinatorPipeName ?? string.Empty, ["activeAttemptId"] = activeCoordinatorAttempt?.AttemptId ?? string.Empty, ["activeHostPid"] = activeCoordinatorAttempt?.HostPid.ToString() ?? string.Empty } }; } private static async Task HandleCoordinatorRequestAsync( LauncherCoordinatorRequest request, LauncherCoordinatorStatus status) { if (string.Equals(request.Command, LauncherCoordinatorCommands.ActivateDesktop, StringComparison.OrdinalIgnoreCase)) { var activation = await TryActivateExistingInstanceWithStatusAsync(TimeSpan.FromSeconds(2)).ConfigureAwait(false); if (activation is not null) { if (!activation.Accepted && IsRecoverableActivationFailure(activation, status)) { return new LauncherCoordinatorResponse { Accepted = true, Code = "attached_to_launcher_coordinator", Message = "Attached to the active Launcher coordinator; desktop startup is still in progress.", Status = status, ActivationResult = activation }; } return new LauncherCoordinatorResponse { Accepted = activation.Accepted, Code = activation.Accepted ? "existing_host_activated" : "existing_host_activation_failed", Message = activation.Message, Status = status, ActivationResult = activation }; } return new LauncherCoordinatorResponse { Accepted = true, Code = "attached_to_launcher_coordinator", Message = "Attached to the active Launcher coordinator; desktop startup is still in progress.", Status = status }; } return new LauncherCoordinatorResponse { Accepted = true, Code = "attached_to_launcher_coordinator", Message = "Attached to the active Launcher coordinator.", Status = status }; } private static LauncherCoordinatorStatus BuildCoordinatorStatusFromAttempt(StartupAttemptRecord attempt) { return new LauncherCoordinatorStatus { AttemptId = attempt.AttemptId, CoordinatorPid = Environment.ProcessId, HostPid = attempt.HostPid, HostProcessAlive = TryGetLiveProcess(attempt.HostPid), LaunchSource = attempt.LaunchSource, SuccessPolicy = attempt.SuccessPolicy, LastObservedStage = attempt.LastObservedStage, LastObservedMessage = attempt.LastObservedMessage, PublicIpcConnected = attempt.PublicIpcConnected || attempt.IpcConnected, State = attempt.State.ToString(), SoftTimeoutShown = attempt.State is StartupAttemptState.SoftTimeout or StartupAttemptState.DetachedWaiting, Completed = attempt.State is StartupAttemptState.Succeeded or StartupAttemptState.Failed, Succeeded = attempt.State == StartupAttemptState.Succeeded, UpdatedAtUtc = attempt.UpdatedAtUtc }; } private static bool IsRecoverableActivationFailure( PublicShellActivationResult? activation, LauncherCoordinatorStatus? status) { if (activation is { Accepted: true }) { return false; } if (status is { Completed: false, HostProcessAlive: true }) { return true; } var shellStatus = activation?.Status; if (shellStatus is null || !shellStatus.PublicIpcReady) { return false; } return !shellStatus.MainWindowOpened || !shellStatus.DesktopVisible || string.Equals(activation?.Code, "shell_not_ready", StringComparison.OrdinalIgnoreCase) || string.Equals(activation?.Code, "startup_pending", StringComparison.OrdinalIgnoreCase); } private static Dictionary BuildCoordinatorResultDetails( LauncherCoordinatorStatus? status, PublicShellActivationResult? activation) { return new Dictionary(StringComparer.OrdinalIgnoreCase) { ["coordinatorPid"] = status?.CoordinatorPid.ToString() ?? string.Empty, ["coordinatorAttemptId"] = status?.AttemptId ?? string.Empty, ["hostPid"] = status?.HostPid.ToString() ?? activation?.Status.ProcessId.ToString() ?? string.Empty, ["hostProcessAlive"] = status?.HostProcessAlive.ToString() ?? string.Empty, ["publicIpcConnected"] = (status?.PublicIpcConnected ?? activation is not null).ToString(), ["startupStage"] = status?.LastObservedStage.ToString() ?? string.Empty, ["startupState"] = status?.State ?? string.Empty, ["activationAccepted"] = activation?.Accepted.ToString() ?? string.Empty, ["shellState"] = activation?.Status.ShellState ?? status?.ShellStatus?.ShellState ?? string.Empty, ["trayState"] = activation?.Status.Tray.State ?? status?.ShellStatus?.Tray.State ?? string.Empty, ["taskbarUsable"] = activation?.Status.Taskbar.IsUsable.ToString() ?? status?.ShellStatus?.Taskbar.IsUsable.ToString() ?? string.Empty }; } private static async Task DismissSplashIfNeededAsync(SplashWindow? splashWindow) { if (splashWindow is null) { return; } try { await splashWindow.DismissAsync().ConfigureAwait(false); } catch (Exception ex) { Logger.Warn($"Failed to dismiss splash after coordinator attach: {ex.Message}"); } } private static async Task WriteLauncherResultAsync(CommandContext context, LauncherResult result) { var resultPath = context.GetOption("result"); if (string.IsNullOrWhiteSpace(resultPath)) { return; } try { await Commands.WriteResultIfNeededAsync(resultPath, result).ConfigureAwait(false); Logger.Info($"Launcher result written to '{Path.GetFullPath(resultPath)}'."); } catch (Exception ex) { Logger.Error($"Failed to write launcher result to '{resultPath}'.", ex); } } private static async Task ShowFailureWindowAsync(LauncherResult result) { ErrorWindow? errorWindow = null; var hostProcessAlive = result.Details.TryGetValue("hostProcessAlive", out var hostProcessAliveText) && bool.TryParse(hostProcessAliveText, out var hostProcessAliveValue) && hostProcessAliveValue; var hostPid = result.Details.TryGetValue("hostPid", out var hostPidText) && int.TryParse(hostPidText, out var parsedPid) ? parsedPid : (int?)null; await Dispatcher.UIThread.InvokeAsync(() => { try { errorWindow = new ErrorWindow(); if (hostProcessAlive) { errorWindow.ConfigureForRunningHostFailure(hostPid); } else { errorWindow.ConfigureForGenericFailure(allowRetry: true); } errorWindow.SetErrorMessage( $"Failed to start LanMountainDesktop.\n\nStage: {result.Stage}\nCode: {result.Code}\n\n{result.Message}"); errorWindow.Show(); } catch (Exception ex) { Logger.Error("Failed to show launcher failure window.", ex); } }); if (errorWindow is null) { return ErrorWindowResult.Exit; } try { return await errorWindow.WaitForChoiceAsync().ConfigureAwait(false); } catch (Exception ex) { Logger.Error("Failure window closed unexpectedly.", ex); return ErrorWindowResult.Exit; } } private static async Task TryActivateExistingInstanceAsync() { var activation = await TryActivateExistingInstanceWithStatusAsync(TimeSpan.FromSeconds(5)).ConfigureAwait(false); return activation?.Accepted == true; } private static async Task TryActivateExistingInstanceWithStatusAsync(TimeSpan timeout) { try { using var ipcClient = new LanMountainDesktopIpcClient(); var connectTask = ipcClient.ConnectAsync(); var completedTask = await Task.WhenAny(connectTask, Task.Delay(timeout)).ConfigureAwait(false); if (completedTask != connectTask) { return null; } await connectTask.ConfigureAwait(false); if (!ipcClient.IsConnected) { return null; } var shellProxy = ipcClient.CreateProxy(); var activationTask = shellProxy.ActivateMainWindowWithStatusAsync(); completedTask = await Task.WhenAny(activationTask, Task.Delay(timeout)).ConfigureAwait(false); if (completedTask != activationTask) { return null; } return await activationTask.ConfigureAwait(false); } catch (Exception ex) { Logger.Warn($"Failed to activate the existing desktop instance: {ex.Message}"); return null; } } private static bool TryGetLiveProcess(int processId) { if (processId <= 0) { return false; } try { using var process = Process.GetProcessById(processId); return !process.HasExited; } catch { return false; } } }