refactor(launcher): reorganize into responsibility folders

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
lincube
2026-05-28 10:43:30 +08:00
parent 1ee6e68f33
commit b219f109ec
57 changed files with 92 additions and 197 deletions

View File

@@ -1,4 +1,4 @@
namespace LanMountainDesktop.Launcher.Services.AirApp; namespace LanMountainDesktop.Launcher.AirApp;
internal sealed class AirAppHostLocator internal sealed class AirAppHostLocator
{ {

View File

@@ -1,4 +1,4 @@
namespace LanMountainDesktop.Launcher.Services.AirApp; namespace LanMountainDesktop.Launcher.AirApp;
internal static class AirAppInstanceKey internal static class AirAppInstanceKey
{ {

View File

@@ -1,7 +1,5 @@
using System.Diagnostics; using System.Diagnostics;
using LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.AirApp;
namespace LanMountainDesktop.Launcher.Services.AirApp;
internal interface IAirAppProcessStarter internal interface IAirAppProcessStarter
{ {
@@ -60,7 +58,7 @@ internal sealed class AirAppProcessStarter : IAirAppProcessStarter
AddArgument(startInfo, "--source-placement-id", sourcePlacementId.Trim()); AddArgument(startInfo, "--source-placement-id", sourcePlacementId.Trim());
} }
LanMountainDesktop.Launcher.Services.Logger.Info( Logger.Info(
$"Starting AirAppHost. AppId='{appId}'; InstanceKey='{instanceKey}'; HostPath='{hostPath}'; DataRoot='{dataRoot ?? string.Empty}'."); $"Starting AirAppHost. AppId='{appId}'; InstanceKey='{instanceKey}'; HostPath='{hostPath}'; DataRoot='{dataRoot ?? string.Empty}'.");
var process = Process.Start(startInfo); var process = Process.Start(startInfo);
if (process is not null) if (process is not null)
@@ -70,12 +68,12 @@ internal sealed class AirAppProcessStarter : IAirAppProcessStarter
{ {
try try
{ {
LanMountainDesktop.Launcher.Services.Logger.Info( Logger.Info(
$"AirAppHost exited. AppId='{appId}'; InstanceKey='{instanceKey}'; ProcessId={process.Id}; ExitCode={process.ExitCode}."); $"AirAppHost exited. AppId='{appId}'; InstanceKey='{instanceKey}'; ProcessId={process.Id}; ExitCode={process.ExitCode}.");
} }
catch (Exception ex) catch (Exception ex)
{ {
LanMountainDesktop.Launcher.Services.Logger.Warn($"Failed to log AirAppHost exit: {ex.Message}"); Logger.Warn($"Failed to log AirAppHost exit: {ex.Message}");
} }
}; };
} }

View File

@@ -1,7 +1,7 @@
using LanMountainDesktop.Shared.IPC; using LanMountainDesktop.Shared.IPC;
using LanMountainDesktop.Shared.IPC.Abstractions.Services; using LanMountainDesktop.Shared.IPC.Abstractions.Services;
namespace LanMountainDesktop.Launcher.Services.AirApp; namespace LanMountainDesktop.Launcher.AirApp;
internal sealed class LauncherAirAppLifecycleIpcHost : IDisposable internal sealed class LauncherAirAppLifecycleIpcHost : IDisposable
{ {

View File

@@ -2,7 +2,7 @@ using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using LanMountainDesktop.Shared.IPC.Abstractions.Services; using LanMountainDesktop.Shared.IPC.Abstractions.Services;
namespace LanMountainDesktop.Launcher.Services.AirApp; namespace LanMountainDesktop.Launcher.AirApp;
internal sealed class LauncherAirAppLifecycleService : IAirAppLifecycleService internal sealed class LauncherAirAppLifecycleService : IAirAppLifecycleService
{ {

View File

@@ -7,8 +7,6 @@ using Avalonia.Threading;
using LanMountainDesktop.Launcher.Models; using LanMountainDesktop.Launcher.Models;
using LanMountainDesktop.Launcher.Resources; using LanMountainDesktop.Launcher.Resources;
using LanMountainDesktop.Launcher.Services; using LanMountainDesktop.Launcher.Services;
using LanMountainDesktop.Launcher.Services.AirApp;
using LanMountainDesktop.Launcher.Services.Ipc;
using LanMountainDesktop.Launcher.Views; using LanMountainDesktop.Launcher.Views;
using LanMountainDesktop.Shared.Contracts.Launcher; using LanMountainDesktop.Shared.Contracts.Launcher;
using LanMountainDesktop.Shared.IPC; using LanMountainDesktop.Shared.IPC;
@@ -70,12 +68,12 @@ public partial class App : Application
return; return;
} }
// 调试模式:只显示 DevDebugWindow不走正常启动流程 // è°?è¯?模å¼<C3A5>ï¼?å<>ªæ?¾ç¤º DevDebugWindowï¼?ä¸<C3A4>走正常å<C2B8>¯å?¨æµ<C3A6>ç¨?
// 避免启动主程序后 Launcher 自动退出,导致开发者无法预览 UI // é<EFBFBD>¿å?<3F>å<EFBFBD>¯å?¨ä¸»ç¨?åº<C3A5>å<EFBFBD>? Launcher è?ªå?¨é??å?ºï¼?导è?´å¼?å<>?è??æ? æ³?é¢?è§? UI
if (context.IsDebugMode && !context.IsPreviewCommand && if (context.IsDebugMode && !context.IsPreviewCommand &&
!string.Equals(context.Command, "apply-update", StringComparison.OrdinalIgnoreCase)) !string.Equals(context.Command, "apply-update", StringComparison.OrdinalIgnoreCase))
{ {
Logger.Info("Debug mode active showing DevDebugWindow instead of normal launch flow."); Logger.Info("Debug mode active â?? showing DevDebugWindow instead of normal launch flow.");
var devDebugWindow = new DevDebugWindow(); var devDebugWindow = new DevDebugWindow();
devDebugWindow.Show(); devDebugWindow.Show();
base.OnFrameworkInitializationCompleted(); base.OnFrameworkInitializationCompleted();

View File

@@ -1,7 +1,7 @@
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using LanMountainDesktop.Launcher.Models; using LanMountainDesktop.Launcher.Models;
using LanMountainDesktop.Launcher.Services; using LanMountainDesktop.Launcher.Plugins;
using LanMountainDesktop.Shared.Contracts.Launcher; using LanMountainDesktop.Shared.Contracts.Launcher;
using LanMountainDesktop.Shared.IPC.Abstractions.Services; using LanMountainDesktop.Shared.IPC.Abstractions.Services;
@@ -30,15 +30,10 @@ namespace LanMountainDesktop.Launcher;
[JsonSerializable(typeof(PublicTaskbarStatus))] [JsonSerializable(typeof(PublicTaskbarStatus))]
[JsonSerializable(typeof(PublicShellActivationResult))] [JsonSerializable(typeof(PublicShellActivationResult))]
[JsonSerializable(typeof(LauncherResult))] [JsonSerializable(typeof(LauncherResult))]
[JsonSerializable(typeof(HostDiscoveryConfig))]
[JsonSerializable(typeof(PluginManifest))] [JsonSerializable(typeof(PluginManifest))]
[JsonSerializable(typeof(PendingUpgrade))]
[JsonSerializable(typeof(List<PendingUpgrade>))] [JsonSerializable(typeof(List<PendingUpgrade>))]
[JsonSerializable(typeof(OobeStateFile))] [JsonSerializable(typeof(OobeStateFile))]
[JsonSerializable(typeof(DataLocationConfig))] [JsonSerializable(typeof(DataLocationConfig))]
[JsonSerializable(typeof(GitHubRelease))]
[JsonSerializable(typeof(GitHubAsset))]
[JsonSerializable(typeof(List<GitHubRelease>))]
[JsonSerializable(typeof(StartupAttemptRecord))] [JsonSerializable(typeof(StartupAttemptRecord))]
[JsonSerializable(typeof(PrivacyConfig))] [JsonSerializable(typeof(PrivacyConfig))]
[JsonSerializable(typeof(PrivacyAgreementState))] [JsonSerializable(typeof(PrivacyAgreementState))]

View File

@@ -3,7 +3,7 @@ using System.Text.Json;
using LanMountainDesktop.Launcher.Models; using LanMountainDesktop.Launcher.Models;
using LanMountainDesktop.Shared.Contracts.Launcher; using LanMountainDesktop.Shared.Contracts.Launcher;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Deployment;
internal sealed class DeploymentLocator internal sealed class DeploymentLocator
{ {

View File

@@ -1,4 +1,4 @@
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Deployment;
/// <summary> /// <summary>
/// 主程序发现选项 /// 主程序发现选项

View File

@@ -1,6 +1,6 @@
using LanMountainDesktop.Shared.Contracts.Launcher; using LanMountainDesktop.Shared.Contracts.Launcher;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Deployment;
internal sealed record HostLaunchPlan( internal sealed record HostLaunchPlan(
string HostPath, string HostPath,

View File

@@ -1,4 +1,4 @@
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Deployment;
internal sealed class HostResolutionResult internal sealed class HostResolutionResult
{ {

View File

@@ -1,7 +1,7 @@
using System.Diagnostics; using System.Diagnostics;
using Microsoft.Win32; using Microsoft.Win32;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Deployment;
/// <summary> /// <summary>
/// 老版本检测器 - 检测 0.8.x 及更早的单应用模式安装 /// 老版本检测器 - 检测 0.8.x 及更早的单应用模式安装

View File

@@ -0,0 +1,9 @@
global using LanMountainDesktop.Launcher.AirApp;
global using LanMountainDesktop.Launcher.Deployment;
global using LanMountainDesktop.Launcher.Infrastructure;
global using LanMountainDesktop.Launcher.Ipc;
global using LanMountainDesktop.Launcher.Oobe;
global using LanMountainDesktop.Launcher.Plugins;
global using LanMountainDesktop.Launcher.Startup;
global using LanMountainDesktop.Launcher.Update;
global using LanMountainDesktop.Launcher.Services;

View File

@@ -2,7 +2,7 @@ using System.Text;
using System.Text.Json; using System.Text.Json;
using LanMountainDesktop.Launcher.Models; using LanMountainDesktop.Launcher.Models;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Infrastructure;
internal static class Commands internal static class Commands
{ {
@@ -171,16 +171,12 @@ internal static class Commands
? launcherDir ? launcherDir
: AppContext.BaseDirectory); : AppContext.BaseDirectory);
// 发布版结构Launcher 和 app-* 目录在同一目录
// 检查当前目录是否有 app-* 子目录(发布版)
var appDirs = Directory.GetDirectories(baseDir, "app-*", SearchOption.TopDirectoryOnly); var appDirs = Directory.GetDirectories(baseDir, "app-*", SearchOption.TopDirectoryOnly);
if (appDirs.Length > 0) if (appDirs.Length > 0)
{ {
// 找到 app-* 目录,说明是发布版结构
return baseDir; return baseDir;
} }
// 开发环境:检查父目录是否有主程序
var parent = Path.GetFullPath(Path.Combine(baseDir, "..")); var parent = Path.GetFullPath(Path.Combine(baseDir, ".."));
var parentHost = OperatingSystem.IsWindows() var parentHost = OperatingSystem.IsWindows()
? Path.Combine(parent, "LanMountainDesktop.exe") ? Path.Combine(parent, "LanMountainDesktop.exe")
@@ -190,7 +186,6 @@ internal static class Commands
return parent; return parent;
} }
// 默认返回 baseDir
return baseDir; return baseDir;
} }
} }

View File

@@ -1,7 +1,7 @@
using System.Text.Json; using System.Text.Json;
using LanMountainDesktop.Launcher.Models; using LanMountainDesktop.Launcher.Models;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Infrastructure;
/// <summary> /// <summary>
/// 解析应用数据目录位置。 /// 解析应用数据目录位置。

View File

@@ -1,7 +1,7 @@
using Avalonia.Threading; using Avalonia.Threading;
using LanMountainDesktop.Launcher.Views; using LanMountainDesktop.Launcher.Views;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Infrastructure;
internal sealed class DeferredSplashStageReporter : ISplashStageReporter internal sealed class DeferredSplashStageReporter : ISplashStageReporter
{ {

View File

@@ -2,7 +2,7 @@ using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Microsoft.Win32; using Microsoft.Win32;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Infrastructure;
internal enum DotNetRuntimeArchitecture internal enum DotNetRuntimeArchitecture
{ {

View File

@@ -1,4 +1,4 @@
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Infrastructure;
internal interface ISplashStageReporter internal interface ISplashStageReporter
{ {

View File

@@ -1,7 +1,7 @@
using System.Globalization; using System.Globalization;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Infrastructure;
internal static class LanguagePreferenceService internal static class LanguagePreferenceService
{ {

View File

@@ -1,6 +1,6 @@
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Infrastructure;
/// <summary> /// <summary>
/// 启动器背景图片服务 /// 启动器背景图片服务

View File

@@ -1,4 +1,4 @@
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Infrastructure;
internal sealed record LauncherDebugSettings(bool DevModeEnabled, string? CustomHostPath); internal sealed record LauncherDebugSettings(bool DevModeEnabled, string? CustomHostPath);

View File

@@ -1,7 +1,7 @@
using System.Security.Principal; using System.Security.Principal;
using LanMountainDesktop.Launcher.Models; using LanMountainDesktop.Launcher.Models;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Infrastructure;
internal static class LauncherExecutionContext internal static class LauncherExecutionContext
{ {

View File

@@ -1,6 +1,6 @@
using System.Text; using System.Text;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Infrastructure;
/// <summary> /// <summary>
/// 简单的日志记录器 - 同时输出到控制台和文件 /// 简单的日志记录器 - 同时输出到控制台和文件

View File

@@ -2,7 +2,7 @@ using Avalonia;
using Avalonia.Styling; using Avalonia.Styling;
using FluentAvalonia.Styling; using FluentAvalonia.Styling;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Infrastructure;
/// <summary> /// <summary>
/// 主题服务,管理启动器的主题设置 /// 主题服务,管理启动器的主题设置

View File

@@ -3,7 +3,7 @@ using System.Text;
using System.Text.Json; using System.Text.Json;
using LanMountainDesktop.Launcher.Models; using LanMountainDesktop.Launcher.Models;
namespace LanMountainDesktop.Launcher.Services.Ipc; namespace LanMountainDesktop.Launcher.Ipc;
internal sealed class LauncherCoordinatorIpcClient internal sealed class LauncherCoordinatorIpcClient
{ {

View File

@@ -4,7 +4,7 @@ using System.Text.Json;
using System.IO.Pipes; using System.IO.Pipes;
using LanMountainDesktop.Launcher.Models; using LanMountainDesktop.Launcher.Models;
namespace LanMountainDesktop.Launcher.Services.Ipc; namespace LanMountainDesktop.Launcher.Ipc;
internal sealed class LauncherCoordinatorIpcServer : IDisposable internal sealed class LauncherCoordinatorIpcServer : IDisposable
{ {

View File

@@ -2,10 +2,9 @@ using System.Buffers;
using System.IO.Pipes; using System.IO.Pipes;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using LanMountainDesktop.Launcher.Services;
using LanMountainDesktop.Shared.Contracts.Update; using LanMountainDesktop.Shared.Contracts.Update;
namespace LanMountainDesktop.Launcher.Services.Ipc; namespace LanMountainDesktop.Launcher.Ipc;
internal sealed class LauncherUpdateProgressIpcServer : IUpdateProgressReporter, IDisposable internal sealed class LauncherUpdateProgressIpcServer : IUpdateProgressReporter, IDisposable
{ {

View File

@@ -18,6 +18,7 @@
<ItemGroup> <ItemGroup>
<!-- 只引用 Shared.ContractsIPC 协议) --> <!-- 只引用 Shared.ContractsIPC 协议) -->
<ProjectReference Include="..\LanMountainDesktop.Shared.Contracts\LanMountainDesktop.Shared.Contracts.csproj" /> <ProjectReference Include="..\LanMountainDesktop.Shared.Contracts\LanMountainDesktop.Shared.Contracts.csproj" />
<ProjectReference Include="..\LanMountainDesktop.PluginPackaging\LanMountainDesktop.PluginPackaging.csproj" />
<ProjectReference Include="..\LanMountainDesktop.Shared.IPC\LanMountainDesktop.Shared.IPC.csproj" /> <ProjectReference Include="..\LanMountainDesktop.Shared.IPC\LanMountainDesktop.Shared.IPC.csproj" />
</ItemGroup> </ItemGroup>

View File

@@ -2,7 +2,7 @@ using Avalonia.Threading;
using LanMountainDesktop.Launcher.Models; using LanMountainDesktop.Launcher.Models;
using LanMountainDesktop.Launcher.Views; using LanMountainDesktop.Launcher.Views;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Oobe;
internal sealed class DataLocationOobeStep : IOobeStep internal sealed class DataLocationOobeStep : IOobeStep
{ {

View File

@@ -2,7 +2,7 @@ using System.Text.Json;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using LanMountainDesktop.Shared.Contracts.Launcher; using LanMountainDesktop.Shared.Contracts.Launcher;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Oobe;
/// <summary> /// <summary>
/// 在 OOBE 中向 Host 的 settings.json 写入启动与展示相关字段,属性名与 Host /// 在 OOBE 中向 Host 的 settings.json 写入启动与展示相关字段,属性名与 Host

View File

@@ -1,4 +1,4 @@
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Oobe;
internal interface IOobeStep internal interface IOobeStep
{ {

View File

@@ -1,7 +1,7 @@
using System; using System;
using Microsoft.Win32; using Microsoft.Win32;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Oobe;
/// <summary> /// <summary>
/// 将当前 Windows 用户登录时自启动项指向<strong>本 Launcher 进程</strong>(与正式入口一致)。 /// 将当前 Windows 用户登录时自启动项指向<strong>本 Launcher 进程</strong>(与正式入口一致)。

View File

@@ -1,7 +1,7 @@
using System.Text.Json; using System.Text.Json;
using LanMountainDesktop.Launcher.Models; using LanMountainDesktop.Launcher.Models;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Oobe;
internal sealed class OobeStateService internal sealed class OobeStateService
{ {

View File

@@ -3,7 +3,7 @@ using System.Text;
using System.Text.Json; using System.Text.Json;
using LanMountainDesktop.Launcher.Models; using LanMountainDesktop.Launcher.Models;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Oobe;
/// <summary> /// <summary>
/// 隐私协议同意状态管理服务(带防篡改保护) /// 隐私协议同意状态管理服务(带防篡改保护)

View File

@@ -1,7 +1,7 @@
using Avalonia.Threading; using Avalonia.Threading;
using LanMountainDesktop.Launcher.Views; using LanMountainDesktop.Launcher.Views;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Oobe;
internal sealed class WelcomeOobeStep : IOobeStep internal sealed class WelcomeOobeStep : IOobeStep
{ {

View File

@@ -2,7 +2,7 @@ using System.IO.Compression;
using System.Text.Json; using System.Text.Json;
using LanMountainDesktop.Launcher.Models; using LanMountainDesktop.Launcher.Models;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Plugins;
/// <summary> /// <summary>
/// 插件安装服务 - 简化版,不依赖 PluginSdk /// 插件安装服务 - 简化版,不依赖 PluginSdk
@@ -290,7 +290,7 @@ internal sealed class PluginInstallerService
/// <summary> /// <summary>
/// 简化的插件清单模型 /// 简化的插件清单模型
/// </summary> /// </summary>
public class PluginManifest internal class PluginManifest
{ {
public string Id { get; set; } = ""; public string Id { get; set; } = "";
public string Name { get; set; } = ""; public string Name { get; set; } = "";

View File

@@ -1,7 +1,7 @@
using System.Text.Json; using System.Text.Json;
using LanMountainDesktop.Launcher.Models; using LanMountainDesktop.Launcher.Models;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Plugins;
internal sealed class PluginUpgradeQueueService internal sealed class PluginUpgradeQueueService
{ {
@@ -29,7 +29,7 @@ internal sealed class PluginUpgradeQueueService
} }
var text = File.ReadAllText(pendingPath); var text = File.ReadAllText(pendingPath);
var pending = JsonSerializer.Deserialize(text, AppJsonContext.Default.ListPendingUpgrade) ?? []; var pending = JsonSerializer.Deserialize<List<PendingUpgrade>>(text, AppJsonContext.Default.Options) ?? [];
var failures = new List<string>(); var failures = new List<string>();
var succeeded = new List<PendingUpgrade>(); var succeeded = new List<PendingUpgrade>();
@@ -63,7 +63,7 @@ internal sealed class PluginUpgradeQueueService
} }
else else
{ {
File.WriteAllText(pendingPath, JsonSerializer.Serialize(remaining, AppJsonContext.Default.ListPendingUpgrade)); File.WriteAllText(pendingPath, JsonSerializer.Serialize(remaining, AppJsonContext.Default.Options));
} }
return new LauncherResult return new LauncherResult

View File

@@ -1,7 +1,5 @@
using Avalonia; using Avalonia;
using LanMountainDesktop.Launcher.Models; using LanMountainDesktop.Launcher.Models;
using LanMountainDesktop.Launcher.Services;
namespace LanMountainDesktop.Launcher; namespace LanMountainDesktop.Launcher;
public static class Program public static class Program

View File

@@ -2,7 +2,6 @@ using System.Diagnostics;
using Avalonia.Threading; using Avalonia.Threading;
using LanMountainDesktop.Launcher.Models; using LanMountainDesktop.Launcher.Models;
using LanMountainDesktop.Launcher.Resources; using LanMountainDesktop.Launcher.Resources;
using LanMountainDesktop.Launcher.Services.Ipc;
using LanMountainDesktop.Launcher.Startup; using LanMountainDesktop.Launcher.Startup;
using LanMountainDesktop.Launcher.Views; using LanMountainDesktop.Launcher.Views;
using LanMountainDesktop.Shared.Contracts.Launcher; using LanMountainDesktop.Shared.Contracts.Launcher;
@@ -185,115 +184,18 @@ internal sealed partial class LauncherFlowCoordinator
} }
} }
private static async Task<PublicShellStatus?> TryGetPublicShellStatusAsync(
LanMountainDesktopIpcClient ipcClient) =>
await HostStartupMonitor.TryGetPublicShellStatusAsync(ipcClient).ConfigureAwait(false);
private static async Task<StartupSuccessState?> TryRecoverActivationThroughExistingHostAsync( private static async Task<StartupSuccessState?> TryRecoverActivationThroughExistingHostAsync(
LanMountainDesktopIpcClient ipcClient, LanMountainDesktopIpcClient ipcClient,
StartupSuccessTracker startupSuccessTracker, StartupSuccessTracker startupSuccessTracker,
TimeSpan timeout) TimeSpan timeout) =>
{ await HostStartupMonitor.TryRecoverActivationThroughExistingHostAsync(
var activation = await TryActivateExistingHostWithStatusAsync(ipcClient, timeout).ConfigureAwait(false); ipcClient,
if (activation is null) startupSuccessTracker,
{ timeout).ConfigureAwait(false);
return null;
}
if (startupSuccessTracker.TryResolve(activation.Status, out var shellSuccess))
{
return shellSuccess;
}
if (activation.Accepted)
{
return startupSuccessTracker.BuildRecoverySuccessState();
}
return HostActivationPolicy.IsRecoverableActivationFailure(activation)
? new StartupSuccessState(
StartupStage.Ready,
"startup_pending",
activation.Message)
: null;
}
private static async Task<PublicShellStatus?> TryGetPublicShellStatusAsync(
LanMountainDesktopIpcClient ipcClient)
{
try
{
var shellProxy = ipcClient.CreateProxy<IPublicShellControlService>();
return await shellProxy.GetShellStatusAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.Warn($"Failed to query public shell status: {ex.Message}");
return null;
}
}
private static async Task<StartupSuccessState?> TryRecoverWithPublicActivationAsync(
LanMountainDesktopIpcClient ipcClient,
Process hostProcess,
Task<StartupSuccessState> successTask,
StartupSuccessTracker startupSuccessTracker)
{
try
{
var shellProxy = ipcClient.CreateProxy<IPublicShellControlService>();
var activation = await shellProxy.ActivateMainWindowWithStatusAsync().ConfigureAwait(false);
StartupDiagnostics.TraceShellStatus("recovery_activation", activation.Status);
if (startupSuccessTracker.TryResolve(activation.Status, out var shellSuccess))
{
return shellSuccess;
}
var completedTask = await Task.WhenAny(successTask, Task.Delay(TimeSpan.FromSeconds(5))).ConfigureAwait(false);
if (completedTask == successTask)
{
return await successTask.ConfigureAwait(false);
}
if (!hostProcess.HasExited && (activation.Accepted || HostActivationPolicy.IsRecoverableActivationFailure(activation)))
{
return startupSuccessTracker.BuildRecoverySuccessState();
}
}
catch (Exception ex)
{
Logger.Warn($"Public activation recovery failed: {ex.Message}");
}
return null;
}
private static LoadingStateMessage BuildDelayedLoadingState(
LoadingStateMessage loadingState,
string summaryMessage,
string detailMessage,
DateTimeOffset startedAtUtc)
{
var delayedItems = loadingState.ActiveItems
.Where(item => !string.Equals(item.Id, "launcher-soft-timeout", StringComparison.OrdinalIgnoreCase))
.ToList();
delayedItems.Insert(0, new LoadingItem
{
Id = "launcher-soft-timeout",
Type = LoadingItemType.System,
Name = "Startup still in progress",
Description = detailMessage,
State = LoadingState.Delayed,
ProgressPercent = Math.Max(loadingState.OverallProgressPercent, 1),
Message = detailMessage,
StartTime = startedAtUtc
});
return loadingState with
{
ActiveItems = delayedItems,
Message = summaryMessage,
Timestamp = DateTimeOffset.UtcNow,
TotalCount = Math.Max(loadingState.TotalCount, delayedItems.Count)
};
}
private static Dictionary<string, string> BuildAttemptDetails( private static Dictionary<string, string> BuildAttemptDetails(
StartupAttemptRecord? trackedAttempt, StartupAttemptRecord? trackedAttempt,

View File

@@ -2,7 +2,6 @@ using System.Diagnostics;
using Avalonia.Threading; using Avalonia.Threading;
using LanMountainDesktop.Launcher.Models; using LanMountainDesktop.Launcher.Models;
using LanMountainDesktop.Launcher.Resources; using LanMountainDesktop.Launcher.Resources;
using LanMountainDesktop.Launcher.Services.Ipc;
using LanMountainDesktop.Launcher.Views; using LanMountainDesktop.Launcher.Views;
using LanMountainDesktop.Shared.Contracts.Launcher; using LanMountainDesktop.Shared.Contracts.Launcher;
using LanMountainDesktop.Shared.IPC; using LanMountainDesktop.Shared.IPC;

View File

@@ -2,7 +2,6 @@ using System.Diagnostics;
using Avalonia.Threading; using Avalonia.Threading;
using LanMountainDesktop.Launcher.Models; using LanMountainDesktop.Launcher.Models;
using LanMountainDesktop.Launcher.Resources; using LanMountainDesktop.Launcher.Resources;
using LanMountainDesktop.Launcher.Services.Ipc;
using LanMountainDesktop.Launcher.Startup; using LanMountainDesktop.Launcher.Startup;
using LanMountainDesktop.Launcher.Views; using LanMountainDesktop.Launcher.Views;
using LanMountainDesktop.Shared.Contracts.Launcher; using LanMountainDesktop.Shared.Contracts.Launcher;
@@ -340,7 +339,7 @@ internal sealed partial class LauncherFlowCoordinator
{ {
softTimeoutShown = true; softTimeoutShown = true;
reporter.Report("delayed", SoftTimeoutStatusMessage); reporter.Report("delayed", SoftTimeoutStatusMessage);
loadingState = BuildDelayedLoadingState( loadingState = HostStartupMonitor.BuildDelayedLoadingState(
loadingState, loadingState,
SoftTimeoutStatusMessage, SoftTimeoutStatusMessage,
SoftTimeoutDetailsMessage, SoftTimeoutDetailsMessage,

View File

@@ -1,7 +1,6 @@
using System.Diagnostics; using System.Diagnostics;
using LanMountainDesktop.Launcher.Models; using LanMountainDesktop.Launcher.Models;
using LanMountainDesktop.Launcher.Resources; using LanMountainDesktop.Launcher.Resources;
using LanMountainDesktop.Launcher.Services;
using LanMountainDesktop.Launcher.Views; using LanMountainDesktop.Launcher.Views;
using LanMountainDesktop.Shared.Contracts.Launcher; using LanMountainDesktop.Shared.Contracts.Launcher;
using LanMountainDesktop.Shared.IPC; using LanMountainDesktop.Shared.IPC;

View File

@@ -1,4 +1,3 @@
using LanMountainDesktop.Launcher.Services;
using LanMountainDesktop.Shared.IPC; using LanMountainDesktop.Shared.IPC;
namespace LanMountainDesktop.Launcher.Startup; namespace LanMountainDesktop.Launcher.Startup;

View File

@@ -5,7 +5,7 @@ using System.Text.Json;
using LanMountainDesktop.Launcher.Models; using LanMountainDesktop.Launcher.Models;
using LanMountainDesktop.Shared.Contracts.Launcher; using LanMountainDesktop.Shared.Contracts.Launcher;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Startup;
internal sealed class StartupAttemptRegistry internal sealed class StartupAttemptRegistry
{ {

View File

@@ -1,5 +1,4 @@
using System.Text.Json; using System.Text.Json;
using LanMountainDesktop.Launcher.Services;
using LanMountainDesktop.Shared.Contracts.Launcher; using LanMountainDesktop.Shared.Contracts.Launcher;
using LanMountainDesktop.Shared.IPC.Abstractions.Services; using LanMountainDesktop.Shared.IPC.Abstractions.Services;

View File

@@ -1,4 +1,3 @@
using LanMountainDesktop.Launcher.Services;
using LanMountainDesktop.Shared.Contracts.Launcher; using LanMountainDesktop.Shared.Contracts.Launcher;
using LanMountainDesktop.Shared.IPC.Abstractions.Services; using LanMountainDesktop.Shared.IPC.Abstractions.Services;

View File

@@ -1,6 +1,6 @@
using LanMountainDesktop.Shared.Contracts.Update; using LanMountainDesktop.Shared.Contracts.Update;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Update;
public interface IUpdateProgressReporter public interface IUpdateProgressReporter
{ {

View File

@@ -1,6 +1,6 @@
using LanMountainDesktop.Shared.Contracts.Update; using LanMountainDesktop.Shared.Contracts.Update;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Update;
internal sealed class NullUpdateProgressReporter : IUpdateProgressReporter internal sealed class NullUpdateProgressReporter : IUpdateProgressReporter
{ {

View File

@@ -4,7 +4,7 @@ using System.Text.Json;
using LanMountainDesktop.Launcher.Models; using LanMountainDesktop.Launcher.Models;
using ContractsUpdate = LanMountainDesktop.Shared.Contracts.Update; using ContractsUpdate = LanMountainDesktop.Shared.Contracts.Update;
namespace LanMountainDesktop.Launcher.Services; namespace LanMountainDesktop.Launcher.Update;
internal sealed class UpdateEngineService internal sealed class UpdateEngineService
{ {

View File

@@ -28,7 +28,7 @@ public partial class OobeWindow : Window
private bool _migrateExistingData; private bool _migrateExistingData;
// 主题选择 // 主题选择
private Services.ThemeMode _selectedThemeMode = Services.ThemeMode.Light; private ThemeMode _selectedThemeMode = ThemeMode.Light;
private string _selectedAccentColor = "#0078D4"; private string _selectedAccentColor = "#0078D4";
private MonetSource _selectedMonetSource = MonetSource.Wallpaper; private MonetSource _selectedMonetSource = MonetSource.Wallpaper;
@@ -82,19 +82,19 @@ public partial class OobeWindow : Window
// 浅色/深色模式选择 // 浅色/深色模式选择
if (this.FindControl<Border>("LightModeOption") is { } lightModeOption) if (this.FindControl<Border>("LightModeOption") is { } lightModeOption)
{ {
lightModeOption.PointerPressed += (s, e) => SelectThemeMode(Services.ThemeMode.Light); lightModeOption.PointerPressed += (s, e) => SelectThemeMode(ThemeMode.Light);
} }
if (this.FindControl<Border>("DarkModeOption") is { } darkModeOption) if (this.FindControl<Border>("DarkModeOption") is { } darkModeOption)
{ {
darkModeOption.PointerPressed += (s, e) => SelectThemeMode(Services.ThemeMode.Dark); darkModeOption.PointerPressed += (s, e) => SelectThemeMode(ThemeMode.Dark);
} }
if (this.FindControl<RadioButton>("LightModeRadio") is { } lightModeRadio) if (this.FindControl<RadioButton>("LightModeRadio") is { } lightModeRadio)
{ {
lightModeRadio.IsCheckedChanged += (s, e) => lightModeRadio.IsCheckedChanged += (s, e) =>
{ {
if (lightModeRadio.IsChecked == true) SelectThemeMode(Services.ThemeMode.Light); if (lightModeRadio.IsChecked == true) SelectThemeMode(ThemeMode.Light);
}; };
} }
@@ -102,7 +102,7 @@ public partial class OobeWindow : Window
{ {
darkModeRadio.IsCheckedChanged += (s, e) => darkModeRadio.IsCheckedChanged += (s, e) =>
{ {
if (darkModeRadio.IsChecked == true) SelectThemeMode(Services.ThemeMode.Dark); if (darkModeRadio.IsChecked == true) SelectThemeMode(ThemeMode.Dark);
}; };
} }
@@ -812,7 +812,7 @@ public partial class OobeWindow : Window
} }
// 主题选择方法 // 主题选择方法
private void SelectThemeMode(Services.ThemeMode mode) private void SelectThemeMode(ThemeMode mode)
{ {
_selectedThemeMode = mode; _selectedThemeMode = mode;
@@ -821,30 +821,30 @@ public partial class OobeWindow : Window
if (this.FindControl<RadioButton>("LightModeRadio") is { } lightModeRadio) if (this.FindControl<RadioButton>("LightModeRadio") is { } lightModeRadio)
{ {
lightModeRadio.IsChecked = mode == Services.ThemeMode.Light; lightModeRadio.IsChecked = mode == ThemeMode.Light;
} }
if (this.FindControl<RadioButton>("DarkModeRadio") is { } darkModeRadio) if (this.FindControl<RadioButton>("DarkModeRadio") is { } darkModeRadio)
{ {
darkModeRadio.IsChecked = mode == Services.ThemeMode.Dark; darkModeRadio.IsChecked = mode == ThemeMode.Dark;
} }
if (this.FindControl<Border>("LightModeOption") is { } lightModeOption) if (this.FindControl<Border>("LightModeOption") is { } lightModeOption)
{ {
lightModeOption.BorderBrush = mode == Services.ThemeMode.Light lightModeOption.BorderBrush = mode == ThemeMode.Light
? Application.Current?.Resources["AccentFillColorDefaultBrush"] as IBrush ? Application.Current?.Resources["AccentFillColorDefaultBrush"] as IBrush
: Application.Current?.Resources["CardStrokeColorDefaultBrush"] as IBrush; : Application.Current?.Resources["CardStrokeColorDefaultBrush"] as IBrush;
lightModeOption.BorderThickness = mode == Services.ThemeMode.Light lightModeOption.BorderThickness = mode == ThemeMode.Light
? new Thickness(2) ? new Thickness(2)
: new Thickness(1); : new Thickness(1);
} }
if (this.FindControl<Border>("DarkModeOption") is { } darkModeOption) if (this.FindControl<Border>("DarkModeOption") is { } darkModeOption)
{ {
darkModeOption.BorderBrush = mode == Services.ThemeMode.Dark darkModeOption.BorderBrush = mode == ThemeMode.Dark
? Application.Current?.Resources["AccentFillColorDefaultBrush"] as IBrush ? Application.Current?.Resources["AccentFillColorDefaultBrush"] as IBrush
: Application.Current?.Resources["CardStrokeColorDefaultBrush"] as IBrush; : Application.Current?.Resources["CardStrokeColorDefaultBrush"] as IBrush;
darkModeOption.BorderThickness = mode == Services.ThemeMode.Dark darkModeOption.BorderThickness = mode == ThemeMode.Dark
? new Thickness(2) ? new Thickness(2)
: new Thickness(1); : new Thickness(1);
} }

View File

@@ -1,5 +1,5 @@
using LanMountainDesktop.Launcher.Services; using LanMountainDesktop.Launcher.AirApp;
using LanMountainDesktop.Launcher.Services.AirApp; using LanMountainDesktop.Launcher.Infrastructure;
using Xunit; using Xunit;
namespace LanMountainDesktop.Tests; namespace LanMountainDesktop.Tests;

View File

@@ -69,7 +69,7 @@ public sealed class CornerRadiusStyleTests
ThemeVariant: "Dark")); ThemeVariant: "Dark"));
var context = new PluginDesktopComponentContext( var context = new PluginDesktopComponentContext(
new PluginManifest("plugin.id", "Plugin Name", "plugin.dll"), new LanMountainDesktop.PluginSdk.PluginManifest("plugin.id", "Plugin Name", "plugin.dll"),
"C:\\Plugins\\plugin.id", "C:\\Plugins\\plugin.id",
"C:\\Data\\plugin.id", "C:\\Data\\plugin.id",
new NullServiceProvider(), new NullServiceProvider(),

View File

@@ -1,7 +1,7 @@
using System.Diagnostics; using System.Diagnostics;
using LanMountainDesktop.ComponentSystem; using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Launcher; using LanMountainDesktop.Launcher;
using LanMountainDesktop.Launcher.Services.AirApp; using LanMountainDesktop.Launcher.AirApp;
using LanMountainDesktop.Shared.IPC.Abstractions.Services; using LanMountainDesktop.Shared.IPC.Abstractions.Services;
using Xunit; using Xunit;

View File

@@ -0,0 +1,6 @@
global using LanMountainDesktop.Launcher.AirApp;
global using LanMountainDesktop.Launcher.Deployment;
global using LanMountainDesktop.Launcher.Infrastructure;
global using LanMountainDesktop.Launcher.Ipc;
global using LanMountainDesktop.Launcher.Oobe;
global using LanMountainDesktop.Launcher.Update;

View File

@@ -1,4 +1,4 @@
using LanMountainDesktop.Launcher.Services; using LanMountainDesktop.Launcher.Plugins;
using System.IO.Compression; using System.IO.Compression;
using Xunit; using Xunit;

View File

@@ -18,7 +18,7 @@ public sealed class PluginManifestRuntimeTests
"""; """;
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
var manifest = PluginManifest.Load(stream, "plugin.json"); var manifest = LanMountainDesktop.PluginSdk.PluginManifest.Load(stream, "plugin.json");
Assert.NotNull(manifest.Runtime); Assert.NotNull(manifest.Runtime);
Assert.Equal(PluginRuntimeModes.InProcess, manifest.Runtime!.Mode); Assert.Equal(PluginRuntimeModes.InProcess, manifest.Runtime!.Mode);
@@ -40,7 +40,7 @@ public sealed class PluginManifestRuntimeTests
"""; """;
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
var ex = Assert.Throws<InvalidOperationException>(() => PluginManifest.Load(stream, "plugin.json")); var ex = Assert.Throws<InvalidOperationException>(() => LanMountainDesktop.PluginSdk.PluginManifest.Load(stream, "plugin.json"));
Assert.Contains("runtime.mode", ex.Message); Assert.Contains("runtime.mode", ex.Message);
Assert.Contains("shared-worker", ex.Message); Assert.Contains("shared-worker", ex.Message);