fix.在线安装器,启动器

This commit is contained in:
lincube
2026-06-05 11:08:11 +08:00
parent bb4e90ea8d
commit 8c88e305ee
42 changed files with 1507 additions and 393 deletions

View File

@@ -132,6 +132,27 @@ internal sealed class DataLocationResolver
return ResolveDataRoot(config);
}
public string ResolveDataRoot(DataLocationMode mode, string? customPath = null)
{
return ResolveDataRoot(BuildConfig(mode, customPath));
}
public DataLocationConfig BuildConfig(DataLocationMode mode, string? customPath = null)
{
var targetDataRoot = mode == DataLocationMode.Portable
? Path.GetFullPath(!string.IsNullOrWhiteSpace(customPath)
? customPath
: DefaultPortableDataPath)
: _defaultSystemDataPath;
return new DataLocationConfig
{
DataLocationMode = mode.ToString(),
SystemDataPath = _defaultSystemDataPath,
PortableDataPath = mode == DataLocationMode.Portable ? targetDataRoot : null
};
}
private string ResolveDataRoot(DataLocationConfig? config)
{
if (config is null)
@@ -193,18 +214,8 @@ internal sealed class DataLocationResolver
public bool ApplyLocationChoice(DataLocationMode mode, string? customPath = null, bool migrateExistingData = false)
{
var targetDataRoot = mode == DataLocationMode.Portable
? Path.GetFullPath(!string.IsNullOrWhiteSpace(customPath)
? customPath
: DefaultPortableDataPath)
: _defaultSystemDataPath;
var config = new DataLocationConfig
{
DataLocationMode = mode.ToString(),
SystemDataPath = _defaultSystemDataPath,
PortableDataPath = mode == DataLocationMode.Portable ? targetDataRoot : null
};
var config = BuildConfig(mode, customPath);
var targetDataRoot = ResolveDataRoot(config);
// 先创建目录结构
try

View File

@@ -57,6 +57,23 @@ internal sealed class OobeCompletionResult
public string ErrorMessage { get; init; } = string.Empty;
}
internal sealed class OobeSessionDraft
{
public DataLocationMode DataLocationMode { get; init; } = DataLocationMode.System;
public bool MigrateExistingData { get; init; }
public HostAppSettingsStartupChoices StartupChoices { get; init; }
public PrivacyConfig PrivacyConfig { get; init; } = new();
public bool PrivacyAgreementAccepted { get; init; }
public string PrivacyUserId { get; init; } = string.Empty;
public string PrivacyDeviceId { get; init; } = string.Empty;
}
internal sealed record LauncherExecutionSnapshot(
bool IsElevated,
string UserName,

View File

@@ -1,67 +0,0 @@
using Avalonia.Threading;
using LanMountainDesktop.Launcher.Models;
using LanMountainDesktop.Launcher.Views;
namespace LanMountainDesktop.Launcher.Oobe;
internal sealed class DataLocationOobeStep : IOobeStep
{
private readonly DataLocationResolver _resolver;
public DataLocationOobeStep(DataLocationResolver resolver)
{
_resolver = resolver;
}
public async Task RunAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var existingConfig = _resolver.LoadConfig();
if (existingConfig is not null)
{
Logger.Info("DataLocation OOBE step skipped: config already exists.");
return;
}
DataLocationPromptWindow? window = null;
await Dispatcher.UIThread.InvokeAsync(() =>
{
window = new DataLocationPromptWindow(_resolver);
window.Show();
});
if (window is null)
{
Logger.Warn("DataLocation OOBE step failed: window could not be created.");
return;
}
try
{
var result = await window.WaitForChoiceAsync().ConfigureAwait(false);
if (result is null)
{
Logger.Info("DataLocation OOBE step: user cancelled or closed window. Using default system location.");
_resolver.ApplyLocationChoice(DataLocationMode.System, null, false);
}
else
{
var success = _resolver.ApplyLocationChoice(result.SelectedMode, null, result.MigrateExistingData);
Logger.Info(
$"DataLocation OOBE step: user selected '{result.SelectedMode}'. " +
$"Migrate={result.MigrateExistingData}; Success={success}.");
}
}
finally
{
await Dispatcher.UIThread.InvokeAsync(() =>
{
if (window.IsVisible)
{
window.Close();
}
});
}
}
}

View File

@@ -1,6 +1,15 @@
using LanMountainDesktop.Launcher.Models;
namespace LanMountainDesktop.Launcher.Oobe;
internal sealed record OobeStepResult(bool ContinueLaunch, LauncherResult? Result = null)
{
public static OobeStepResult Continue { get; } = new(true);
public static OobeStepResult Complete(LauncherResult result) => new(false, result);
}
internal interface IOobeStep
{
Task RunAsync(CancellationToken cancellationToken);
Task<OobeStepResult> RunAsync(CancellationToken cancellationToken);
}

View File

@@ -0,0 +1,92 @@
using System.Text.Json;
using LanMountainDesktop.Launcher.Models;
namespace LanMountainDesktop.Launcher.Oobe;
internal sealed class OobeSessionCommitService
{
private readonly DataLocationResolver _dataLocationResolver;
private readonly OobeStateService _oobeStateService;
private readonly CommandContext _context;
private readonly Func<bool, bool>? _setWindowsStartup;
public OobeSessionCommitService(
DataLocationResolver dataLocationResolver,
OobeStateService oobeStateService,
CommandContext context,
Func<bool, bool>? setWindowsStartup = null)
{
_dataLocationResolver = dataLocationResolver;
_oobeStateService = oobeStateService;
_context = context;
_setWindowsStartup = setWindowsStartup;
}
public OobeCompletionResult Commit(OobeSessionDraft draft)
{
ArgumentNullException.ThrowIfNull(draft);
if (!_dataLocationResolver.ApplyLocationChoice(
draft.DataLocationMode,
customPath: null,
draft.MigrateExistingData))
{
return Failure("data_location_save_failed", "Failed to save the selected data location.");
}
var dataRoot = _dataLocationResolver.ResolveDataRoot();
try
{
var settingsPath = HostAppSettingsOobeMerger.GetSettingsFilePath(dataRoot);
HostAppSettingsOobeMerger.MergeStartupPresentation(settingsPath, draft.StartupChoices);
}
catch (Exception ex)
{
return Failure("startup_settings_save_failed", ex.Message);
}
var setWindowsStartup = _setWindowsStartup ?? new LauncherWindowsStartupService().SetEnabled;
if (OperatingSystem.IsWindows() &&
!setWindowsStartup(draft.StartupChoices.AutoStartWithWindows))
{
return Failure("windows_startup_save_failed", "Failed to save Windows startup preference.");
}
try
{
var launcherDataPath = _dataLocationResolver.ResolveLauncherDataPath();
Directory.CreateDirectory(launcherDataPath);
var privacyConfigPath = Path.Combine(launcherDataPath, "privacy-config.json");
var privacyJson = JsonSerializer.Serialize(draft.PrivacyConfig, AppJsonContext.Default.PrivacyConfig);
File.WriteAllText(privacyConfigPath, privacyJson);
var agreementService = new PrivacyAgreementService(launcherDataPath);
if (!agreementService.SaveAgreement(
draft.PrivacyAgreementAccepted,
draft.PrivacyUserId,
draft.PrivacyDeviceId))
{
return Failure("privacy_agreement_save_failed", "Failed to save privacy agreement state.");
}
}
catch (Exception ex)
{
return Failure("privacy_settings_save_failed", ex.Message);
}
var completion = _oobeStateService.MarkCompleted(_context, dataRoot);
return completion.Success
? completion
: Failure(completion.ResultCode, completion.ErrorMessage);
}
private static OobeCompletionResult Failure(string code, string message) =>
new()
{
Success = false,
ResultCode = code,
ErrorMessage = message
};
}

View File

@@ -7,10 +7,12 @@ internal sealed class OobeStateService
{
private const int CurrentSchemaVersion = 1;
private readonly string _appRoot;
private readonly string? _stateRootOverride;
private readonly string _stateDirectory;
private readonly string _statePath;
private readonly string _legacyStatePath;
private readonly string _legacyMarkerPath;
private readonly IReadOnlyList<string> _legacyStatePaths;
private readonly IReadOnlyList<string> _legacyMarkerPaths;
private readonly LauncherExecutionSnapshot _executionSnapshot;
public OobeStateService(
@@ -18,21 +20,17 @@ internal sealed class OobeStateService
string? stateRootOverride = null,
LauncherExecutionSnapshot? executionSnapshot = null)
{
_ = Path.GetFullPath(appRoot);
_appRoot = Path.GetFullPath(appRoot);
_stateRootOverride = string.IsNullOrWhiteSpace(stateRootOverride)
? null
: Path.GetFullPath(stateRootOverride);
_executionSnapshot = executionSnapshot ?? LauncherExecutionContext.Capture();
var stateRoot = string.IsNullOrWhiteSpace(stateRootOverride)
? ResolveStateRoot(appRoot)
: Path.GetFullPath(stateRootOverride);
_stateDirectory = Path.Combine(stateRoot, "Launcher", "state");
_statePath = Path.Combine(_stateDirectory, "oobe-state.json");
var stateRoot = ResolveCurrentStateRoot();
(_stateDirectory, _statePath) = BuildStatePaths(stateRoot);
var legacyRoot = string.IsNullOrWhiteSpace(stateRootOverride)
? Path.GetFullPath(appRoot)
: Path.GetFullPath(stateRootOverride);
var legacyStateDirectory = Path.Combine(legacyRoot, ".launcher", "state");
_legacyStatePath = Path.Combine(legacyStateDirectory, "oobe-state.json");
_legacyMarkerPath = Path.Combine(legacyStateDirectory, "first_run_completed");
_legacyStatePaths = BuildLegacyPaths("oobe-state.json");
_legacyMarkerPaths = BuildLegacyPaths("first_run_completed");
}
public OobeLaunchDecision Evaluate(CommandContext context)
@@ -47,10 +45,17 @@ internal sealed class OobeStateService
}
public OobeCompletionResult MarkCompleted(CommandContext context)
{
return MarkCompleted(context, null);
}
public OobeCompletionResult MarkCompleted(CommandContext context, string? stateRoot)
{
try
{
Directory.CreateDirectory(_stateDirectory);
var (stateDirectory, statePath) = BuildStatePaths(
string.IsNullOrWhiteSpace(stateRoot) ? ResolveCurrentStateRoot() : Path.GetFullPath(stateRoot));
Directory.CreateDirectory(stateDirectory);
var payload = new OobeStateFile
{
SchemaVersion = CurrentSchemaVersion,
@@ -60,14 +65,14 @@ internal sealed class OobeStateService
LaunchSource = context.LaunchSource
};
var tempPath = Path.Combine(_stateDirectory, $"oobe-state.{Guid.NewGuid():N}.tmp");
var tempPath = Path.Combine(stateDirectory, $"oobe-state.{Guid.NewGuid():N}.tmp");
var json = JsonSerializer.Serialize(payload, AppJsonContext.Default.OobeStateFile);
File.WriteAllText(tempPath, json);
File.Move(tempPath, _statePath, overwrite: true);
File.Move(tempPath, statePath, overwrite: true);
TryDeleteLegacyMarker();
Logger.Info(
$"OOBE completion persisted. LaunchSource='{context.LaunchSource}'; StatePath='{_statePath}'; " +
$"OOBE completion persisted. LaunchSource='{context.LaunchSource}'; StatePath='{statePath}'; " +
$"UserSid='{_executionSnapshot.UserSid ?? string.Empty}'.");
return new OobeCompletionResult
@@ -110,20 +115,27 @@ internal sealed class OobeStateService
return EvaluateStateFile(context, _statePath, migratedLegacyState: false);
}
if (File.Exists(_legacyStatePath))
foreach (var legacyStatePath in _legacyStatePaths)
{
return EvaluateStateFile(context, _legacyStatePath, migratedLegacyState: false);
if (File.Exists(legacyStatePath))
{
var decision = EvaluateStateFile(context, legacyStatePath, migratedLegacyState: true);
if (decision.Status == OobeStateStatus.Completed)
{
_ = MarkCompleted(context);
}
return decision;
}
}
if (File.Exists(_legacyMarkerPath))
foreach (var legacyMarkerPath in _legacyMarkerPaths)
{
migratedLegacyMarker = TryMigrateLegacyMarker(context);
return BuildDecision(context, OobeStateStatus.Completed, shouldShowOobe: false, usedLegacyMarker: true, migratedLegacyMarker: migratedLegacyMarker);
}
if (_executionSnapshot.IsElevated)
{
return BuildSuppressedDecision(context, "elevated", "oobe_suppressed_elevated");
if (File.Exists(legacyMarkerPath))
{
migratedLegacyMarker = TryMigrateLegacyMarker(context);
return BuildDecision(context, OobeStateStatus.Completed, shouldShowOobe: false, usedLegacyMarker: true, migratedLegacyMarker: migratedLegacyMarker);
}
}
if (string.Equals(context.LaunchSource, "postinstall", StringComparison.OrdinalIgnoreCase))
@@ -159,15 +171,18 @@ internal sealed class OobeStateService
private void TryDeleteLegacyMarker()
{
try
foreach (var legacyMarkerPath in _legacyMarkerPaths)
{
if (File.Exists(_legacyMarkerPath))
try
{
if (File.Exists(legacyMarkerPath))
{
File.Delete(legacyMarkerPath);
}
}
catch
{
File.Delete(_legacyMarkerPath);
}
}
catch
{
}
}
@@ -225,6 +240,44 @@ internal sealed class OobeStateService
};
}
private string ResolveCurrentStateRoot()
{
return _stateRootOverride ?? ResolveStateRoot(_appRoot);
}
private static (string StateDirectory, string StatePath) BuildStatePaths(string stateRoot)
{
var stateDirectory = Path.Combine(Path.GetFullPath(stateRoot), "Launcher", "state");
return (stateDirectory, Path.Combine(stateDirectory, "oobe-state.json"));
}
private IReadOnlyList<string> BuildLegacyPaths(string fileName)
{
var roots = new List<string>();
if (_stateRootOverride is not null)
{
roots.Add(_stateRootOverride);
}
else
{
roots.Add(ResolveDefaultSystemStateRoot());
roots.Add(_appRoot);
try
{
roots.Add(ResolveCurrentStateRoot());
}
catch
{
}
}
return roots
.Where(root => !string.IsNullOrWhiteSpace(root))
.Select(root => Path.Combine(Path.GetFullPath(root), ".launcher", "state", fileName))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
}
private static string ResolveStateRoot(string appRoot)
{
try
@@ -243,4 +296,15 @@ internal sealed class OobeStateService
return Path.Combine(appData, "LanMountainDesktop");
}
}
private static string ResolveDefaultSystemStateRoot()
{
var appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
if (string.IsNullOrWhiteSpace(appData))
{
return string.Empty;
}
return Path.Combine(appData, "LanMountainDesktop");
}
}

View File

@@ -7,14 +7,19 @@ internal sealed class WelcomeOobeStep : IOobeStep
{
private readonly CommandContext _context;
private readonly OobeStateService _oobeStateService;
private readonly DataLocationResolver _dataLocationResolver;
public WelcomeOobeStep(OobeStateService oobeStateService, CommandContext context)
public WelcomeOobeStep(
OobeStateService oobeStateService,
CommandContext context,
DataLocationResolver dataLocationResolver)
{
_oobeStateService = oobeStateService;
_context = context;
_dataLocationResolver = dataLocationResolver;
}
public async Task RunAsync(CancellationToken cancellationToken)
public async Task<OobeStepResult> RunAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -27,16 +32,32 @@ internal sealed class WelcomeOobeStep : IOobeStep
if (window is null)
{
return;
return BuildCancelledResult("OOBE window could not be created.");
}
await window.WaitForEnterAsync().ConfigureAwait(false);
var completion = _oobeStateService.MarkCompleted(_context);
var draft = await window.WaitForCompletionAsync().ConfigureAwait(false);
if (draft is null)
{
Logger.Info("OOBE was cancelled before completion; Host launch will be skipped.");
return BuildCancelledResult("OOBE was cancelled before completion.");
}
var completion = new OobeSessionCommitService(
_dataLocationResolver,
_oobeStateService,
_context)
.Commit(draft);
if (!completion.Success)
{
Logger.Warn(
$"OOBE completion state was not persisted. ResultCode='{completion.ResultCode}'; " +
$"OOBE session was not persisted. ResultCode='{completion.ResultCode}'; " +
$"Error='{completion.ErrorMessage}'.");
return OobeStepResult.Complete(LaunchResultBuilder.Build(
false,
"oobe",
completion.ResultCode,
"OOBE settings could not be saved.",
errorMessage: completion.ErrorMessage));
}
await Dispatcher.UIThread.InvokeAsync(() =>
@@ -46,5 +67,16 @@ internal sealed class WelcomeOobeStep : IOobeStep
window.Close();
}
});
return OobeStepResult.Continue;
}
private static OobeStepResult BuildCancelledResult(string message)
{
return OobeStepResult.Complete(LaunchResultBuilder.Build(
false,
"oobe",
"oobe_cancelled",
message));
}
}

View File

@@ -1,3 +1,4 @@
using System.Diagnostics;
using LanMountainDesktop.Shared.IPC;
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
@@ -24,11 +25,21 @@ internal sealed class AirAppRuntimeBridge
return;
}
var process = AirAppRuntimeProcessStarter.Start(new AirAppRuntimeStartRequest(
_appRoot,
Environment.ProcessId,
0,
_dataRoot));
Process? process;
try
{
process = AirAppRuntimeProcessStarter.Start(new AirAppRuntimeStartRequest(
_appRoot,
Environment.ProcessId,
0,
_dataRoot));
}
catch (Exception ex)
{
Logger.Warn($"AirApp Runtime start request failed. AppRoot='{_appRoot}'; Error='{ex.Message}'.");
return;
}
Logger.Info($"AirApp Runtime start requested. Pid={(process is null ? -1 : process.Id)}; AppRoot='{_appRoot}'.");
for (var attempt = 1; attempt <= ConnectAttempts; attempt++)

View File

@@ -116,7 +116,7 @@ internal static class PreviewEntryHandler
{
try
{
await window.WaitForEnterAsync().ConfigureAwait(false);
await window.WaitForCompletionAsync().ConfigureAwait(false);
}
catch (Exception ex)
{

View File

@@ -24,8 +24,6 @@ internal static class LauncherGuiCoordinator
var startupAttemptRegistry = new StartupAttemptRegistry();
var coordinatorPipeName = LauncherCoordinatorIpcServer.CreatePipeName();
var successPolicy = LauncherOrchestrator.ResolveSuccessPolicyKey(context);
var airAppRuntimeBridge = new AirAppRuntimeBridge(appRoot, dataLocationResolver.ResolveDataRoot());
await airAppRuntimeBridge.EnsureStartedAsync().ConfigureAwait(false);
if (!startupAttemptRegistry.TryReserveCoordinator(
context.LaunchSource,
@@ -123,6 +121,7 @@ internal static class LauncherGuiCoordinator
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);
}

View File

@@ -35,8 +35,7 @@ internal sealed class LauncherOrchestrator
_dataLocationResolver = new DataLocationResolver(deploymentLocator.GetAppRoot());
_oobeSteps =
[
new WelcomeOobeStep(_oobeStateService, _context),
new DataLocationOobeStep(_dataLocationResolver)
new WelcomeOobeStep(_oobeStateService, _context, _dataLocationResolver)
];
_pipeline = pipeline ?? new LaunchPipeline(
[

View File

@@ -108,6 +108,8 @@ internal sealed class HostLaunchService
return HostLaunchOutcome.FromResult(prerequisiteFailure);
}
await EnsureAirAppRuntimeStartedAsync(context.DeploymentLocator.GetAppRoot(), dataRoot).ConfigureAwait(false);
var hostPath = plan.HostPath;
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
@@ -204,6 +206,18 @@ internal sealed class HostLaunchService
details));
}
private static async Task EnsureAirAppRuntimeStartedAsync(string appRoot, string? dataRoot)
{
try
{
await new AirAppRuntimeBridge(appRoot, dataRoot).EnsureStartedAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.Warn($"AirApp Runtime pre-start failed; Host fallback remains available. Error='{ex.Message}'.");
}
}
private static async Task<HostStartAttempt> StartHostProcessAsync(
HostLaunchPlan plan,
HostStartMode startMode,

View File

@@ -13,7 +13,18 @@ internal sealed class OobeGatePhase : ILaunchPhase
await LaunchUiPresenter.HideSplashAsync(context.SplashWindow).ConfigureAwait(false);
foreach (var step in context.OobeSteps)
{
await step.RunAsync(cancellationToken).ConfigureAwait(false);
var stepResult = await step.RunAsync(cancellationToken).ConfigureAwait(false);
if (!stepResult.ContinueLaunch)
{
context.WindowsClosingByOrchestrator = true;
await LaunchUiPresenter.CloseWindowsAsync(context.SplashWindow, context.LoadingDetailsWindow).ConfigureAwait(false);
return new LaunchPhaseResult(
LaunchPhaseStatus.Completed,
stepResult.Result ?? LaunchResultBuilder.BuildFailure(
"oobe",
"oobe_cancelled",
"OOBE did not complete."));
}
}
await LaunchUiPresenter.ShowSplashAsync(context.SplashWindow).ConfigureAwait(false);

View File

@@ -126,7 +126,7 @@ public partial class DevDebugWindow : Window
try
{
// 等待用户点击开始按钮
await oobeWindow.WaitForEnterAsync();
await oobeWindow.WaitForCompletionAsync();
// 用户点击后窗口会自动关闭通过OobeWindow内部的动画和关闭逻辑
Console.WriteLine("[DevDebugWindow] OOBE completed by user");

View File

@@ -17,10 +17,11 @@ public partial class OobeWindow : Window
private const int AnimationDurationMs = 300;
private const int TypingDelayMs = 100;
private readonly TaskCompletionSource<bool> _completionSource = new();
private readonly TaskCompletionSource<OobeSessionDraft?> _completionSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
private readonly DataLocationResolver _resolver;
private bool _isTransitioning;
private bool _isDebugMode;
private bool _isCompleting;
private int _currentStep = 1;
// 数据位置选择
@@ -40,6 +41,7 @@ public partial class OobeWindow : Window
AvaloniaXamlLoader.Load(this);
Loaded += OnWindowLoaded;
Opened += OnWindowOpened;
Closed += OnWindowClosed;
var appRoot = AppDomain.CurrentDomain.BaseDirectory;
_resolver = new DataLocationResolver(appRoot);
@@ -51,7 +53,7 @@ public partial class OobeWindow : Window
_isDebugMode = isDebugMode;
}
public Task WaitForEnterAsync() => _completionSource.Task;
internal Task<OobeSessionDraft?> WaitForCompletionAsync() => _completionSource.Task;
private void OnWindowLoaded(object? sender, RoutedEventArgs e)
{
@@ -261,6 +263,14 @@ public partial class OobeWindow : Window
await PlayTypingAnimationAsync();
}
private void OnWindowClosed(object? sender, EventArgs e)
{
if (!_isCompleting)
{
_completionSource.TrySetResult(null);
}
}
private async Task PlayTypingAnimationAsync()
{
var typingTextBlock = this.FindControl<TextBlock>("TypingTextBlock");
@@ -477,11 +487,6 @@ public partial class OobeWindow : Window
if (_isTransitioning) return;
// 应用数据位置选择
if (!_isDebugMode)
{
_resolver.ApplyLocationChoice(_selectedDataLocationMode, null, _migrateExistingData);
}
await NavigateToStep(4);
}
@@ -495,7 +500,6 @@ public partial class OobeWindow : Window
private async void OnStartupPresentationNextClick(object? sender, RoutedEventArgs e)
{
if (_isTransitioning) return;
SaveOobeStartupPresentation();
await NavigateToStep(5);
}
@@ -521,7 +525,7 @@ public partial class OobeWindow : Window
private void RefreshOobeStartupPresentationFromDisk()
{
var path = HostAppSettingsOobeMerger.GetSettingsFilePath(_resolver.ResolveDataRoot());
var path = HostAppSettingsOobeMerger.GetSettingsFilePath(ResolveSelectedDataRoot());
var defaults = HostAppSettingsOobeMerger.LoadStartupDefaults(path);
if (this.FindControl<Border>("OobeSlideTransitionSection") is { } slideSection)
@@ -675,8 +679,6 @@ public partial class OobeWindow : Window
if (_isTransitioning) return;
// 保存隐私设置
SavePrivacySettings();
await NavigateToStep(6);
}
@@ -725,13 +727,15 @@ public partial class OobeWindow : Window
try
{
await PlayExitAnimationAsync();
_completionSource.TrySetResult(true);
_isCompleting = true;
_completionSource.TrySetResult(BuildSessionDraft());
Close();
}
catch (Exception ex)
{
Console.Error.WriteLine($"[OobeWindow] Error: {ex.Message}");
_completionSource.TrySetResult(true);
_isCompleting = true;
_completionSource.TrySetResult(BuildSessionDraft());
Close();
}
}
@@ -978,6 +982,43 @@ public partial class OobeWindow : Window
}
}
private OobeSessionDraft BuildSessionDraft()
{
var privacy = BuildPrivacyConfig();
return new OobeSessionDraft
{
DataLocationMode = _selectedDataLocationMode,
MigrateExistingData = _migrateExistingData,
StartupChoices = CollectOobeStartupChoices(),
PrivacyConfig = privacy,
PrivacyAgreementAccepted = this.FindControl<CheckBox>("PrivacyAgreementCheckBox")?.IsChecked ?? false,
PrivacyUserId = privacy.TelemetryId,
PrivacyDeviceId = GetDeviceIdentifier()
};
}
private PrivacyConfig BuildPrivacyConfig()
{
return new PrivacyConfig
{
CrashTelemetryEnabled = this.FindControl<ToggleSwitch>("CrashTelemetryToggle")?.IsChecked ?? true,
UsageTelemetryEnabled = this.FindControl<ToggleSwitch>("UsageTelemetryToggle")?.IsChecked ?? true,
TelemetryId = this.FindControl<TextBox>("TelemetryIdTextBox")?.Text ?? Guid.NewGuid().ToString("N")
};
}
private string ResolveSelectedDataRoot()
{
try
{
return _resolver.ResolveDataRoot(_selectedDataLocationMode);
}
catch
{
return _resolver.DefaultSystemDataPath;
}
}
private static double EaseOutCubic(double t) => 1 - Math.Pow(1 - t, 3);
private static double EaseOutQuad(double t) => 1 - Math.Pow(1 - t, 2);
private static double EaseOutBack(double t)