Files
LanMountainDesktop/LanMountainDesktop/Services/Launcher/LauncherIpcClient.cs
lincube 001d77968f Stamp release versions and harden launcher
Add automatic release version stamping and multiple launcher reliability improvements. The Release workflow now runs scripts/Set-ReleaseVersion.ps1 in build jobs to inject tag-derived Version/AssemblyVersion into project metadata; several .csproj/Directory.Build.props and app.manifest files were changed to use a dev placeholder. Introduced AppVersionProvider (and related runtime metadata) to centralize version resolution and updated DeploymentLocator to use it and to prefer package-root/version.json. Launcher startup flow was hardened: added startup success tracking, public-activation recovery path, improved success/fallback semantics, and related IPC handling. UI/UX fixes include OOBE entrance/exit animation improvements (scaling-aware, concurrent fade+translate) and minor window lifecycle reorder in DesktopShellHost. CommandContext now recognizes restart and key=value args. New DesktopTrayService and .trae spec files (spec, checklist, tasks) document shell/tray hardening work. Miscellaneous logging, comments and housekeeping edits across launcher and shared contracts to support the above.
2026-04-23 00:27:01 +08:00

96 lines
2.7 KiB
C#

using System.Buffers;
using System.Diagnostics;
using System.IO.Pipes;
using System.Text.Json;
using LanMountainDesktop.Shared.Contracts.Launcher;
namespace LanMountainDesktop.Services.Launcher;
/// <summary>
/// Launcher IPC 客户端,用于向 Launcher 报告启动进度。
/// </summary>
public class LauncherIpcClient : IDisposable
{
private static readonly JsonSerializerOptions StartupProgressJsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
private const int LengthPrefixSize = 4;
private NamedPipeClientStream? _pipeClient;
private bool _isConnected;
private readonly object _writeLock = new();
public bool IsConnected => _isConnected && _pipeClient?.IsConnected == true;
public async Task<bool> ConnectAsync(CancellationToken cancellationToken = default)
{
try
{
_pipeClient = new NamedPipeClientStream(
".",
LauncherIpcConstants.PipeName,
PipeDirection.Out);
await _pipeClient.ConnectAsync(5000, cancellationToken);
_isConnected = true;
return true;
}
catch (TimeoutException)
{
return false;
}
catch (Exception ex)
{
AppLogger.Warn("LauncherIpc", $"Failed to connect to Launcher IPC: {ex.Message}");
return false;
}
}
public async Task ReportProgressAsync(StartupProgressMessage message)
{
if (!_isConnected || _pipeClient?.IsConnected != true)
{
return;
}
try
{
var json = JsonSerializer.Serialize(message, StartupProgressJsonOptions);
var payload = System.Text.Encoding.UTF8.GetBytes(json);
var lengthPrefix = BitConverter.GetBytes(payload.Length);
Debug.Assert(lengthPrefix.Length == LengthPrefixSize);
lock (_writeLock)
{
_pipeClient.Write(lengthPrefix, 0, LengthPrefixSize);
_pipeClient.Write(payload, 0, payload.Length);
_pipeClient.Flush();
}
await Task.CompletedTask;
}
catch (IOException)
{
_isConnected = false;
}
catch (Exception ex)
{
AppLogger.Warn("LauncherIpc", $"Failed to report progress: {ex.Message}");
_isConnected = false;
}
}
public static bool IsLaunchedByLauncher()
{
return LauncherRuntimeMetadata.GetLauncherProcessId(Environment.GetCommandLineArgs()) is not null;
}
public void Dispose()
{
_isConnected = false;
_pipeClient?.Dispose();
}
}