Add external public IPC host/client and plugin SDK

Introduce a new LanMountainDesktop.Shared.IPC project implementing a public IPC host and client (LanMountainDesktopIpcClient, PublicIpcHostService), IPC constants and routed notify IDs, DTOs and DI helpers for registering public services. Update Plugin SDK to allow plugins to contribute public IPC services and registrations, add related descriptors/records and extension helpers. Migrate Launcher/App to use the new public IPC for startup/loading notifications and wiring (including TryConnect helper), switch LoadingStateReporter to use the external notification publisher, and add host-side public services (app info, shell control, plugin catalog). Include integration tests and spec/checklist/docs for the external IPC public API.
This commit is contained in:
lincube
2026-04-22 14:55:30 +08:00
parent f51ec309a6
commit aa7c118d13
43 changed files with 1347 additions and 49 deletions

View File

@@ -20,10 +20,13 @@ using LanMountainDesktop.DesktopHost;
using LanMountainDesktop.Models;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.ExternalIpc;
using LanMountainDesktop.Services.Launcher;
using LanMountainDesktop.Services.Loading;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.Shared.Contracts.Launcher;
using LanMountainDesktop.Shared.IPC;
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
using LanMountainDesktop.Theme;
using LanMountainDesktop.ViewModels;
using LanMountainDesktop.Views;
@@ -55,6 +58,7 @@ public partial class App : Application
private readonly IHostApplicationLifecycle _hostApplicationLifecycle = new HostApplicationLifecycleService();
private readonly IDetachedComponentLibraryWindowService _detachedComponentLibraryWindowService = new DetachedComponentLibraryWindowService();
private readonly ILocationService _locationService = HostLocationServiceProvider.GetOrCreate();
private readonly DateTimeOffset _startupAt = DateTimeOffset.UtcNow;
private ISettingsPageRegistry? _settingsPageRegistry;
private ISettingsWindowService? _settingsWindowService;
private WeatherLocationRefreshService? _weatherLocationRefreshService;
@@ -75,7 +79,7 @@ public partial class App : Application
private bool _mainWindowClosed;
private bool _uiUnhandledExceptionHooked;
private DesktopShellHost? _desktopShellHost;
private LauncherIpcClient? _launcherIpcClient;
private PublicIpcHostService? _publicIpcHostService;
private LoadingStateManager? _loadingStateManager;
private LoadingStateReporter? _loadingStateReporter;
private bool _singleInstanceReleased;
@@ -160,6 +164,7 @@ public partial class App : Application
RegisterUiUnhandledExceptionGuard();
LinuxDesktopEntryInstaller.EnsureInstalled();
InitializePublicIpc();
_ = InitializeLauncherIpcAsync();
DesktopBootstrap.InitializeApplication(this, InitializeDesktopShell);
@@ -173,34 +178,24 @@ public partial class App : Application
private async Task InitializeLauncherIpcAsync()
{
if (!LauncherIpcClient.IsLaunchedByLauncher())
if (_loadingStateManager is not null)
return;
try
{
_launcherIpcClient = new LauncherIpcClient();
var connected = await _launcherIpcClient.ConnectAsync();
if (!connected)
{
return;
}
AppLogger.Info("LauncherIpc", "Connected to Launcher IPC server.");
bool hadBufferedMessages;
lock (_launcherProgressLock)
{
hadBufferedMessages = _pendingLauncherProgressMessages.Count > 0;
}
await FlushPendingLauncherProgressAsync();
_loadingStateManager = new LoadingStateManager();
_loadingStateReporter = new LoadingStateReporter(_loadingStateManager, _launcherIpcClient);
_loadingStateReporter = new LoadingStateReporter(_loadingStateManager, _publicIpcHostService);
_loadingStateReporter.Start();
_loadingStateManager.RegisterItem("system.init", LoadingItemType.System, "System Initialization", "Initialize core application services.");
_loadingStateManager.StartItem("system.init", "Launcher IPC connected.");
_loadingStateManager.StartItem("system.init", "Public IPC host ready.");
await FlushPendingLauncherProgressAsync();
if (!hadBufferedMessages)
{
@@ -238,8 +233,8 @@ public partial class App : Application
private void QueueOrSendLauncherProgress(StartupProgressMessage message, bool logSuccess)
{
var ipcClient = _launcherIpcClient;
if (ipcClient is null || !ipcClient.IsConnected)
var publicIpcHostService = _publicIpcHostService;
if (publicIpcHostService is null)
{
lock (_launcherProgressLock)
{
@@ -250,13 +245,13 @@ public partial class App : Application
return;
}
_ = SendLauncherProgressAsync(ipcClient, message, logSuccess);
_ = SendLauncherProgressAsync(publicIpcHostService, message, logSuccess);
}
private async Task FlushPendingLauncherProgressAsync()
{
var ipcClient = _launcherIpcClient;
if (ipcClient is null || !ipcClient.IsConnected)
var publicIpcHostService = _publicIpcHostService;
if (publicIpcHostService is null)
{
return;
}
@@ -270,15 +265,15 @@ public partial class App : Application
foreach (var pendingMessage in pendingMessages)
{
await SendLauncherProgressAsync(ipcClient, pendingMessage, logSuccess: false);
await SendLauncherProgressAsync(publicIpcHostService, pendingMessage, logSuccess: false);
}
}
private async Task SendLauncherProgressAsync(LauncherIpcClient ipcClient, StartupProgressMessage message, bool logSuccess)
private async Task SendLauncherProgressAsync(PublicIpcHostService publicIpcHostService, StartupProgressMessage message, bool logSuccess)
{
try
{
await ipcClient.ReportProgressAsync(message);
await publicIpcHostService.PublishStartupProgressAsync(message);
if (logSuccess)
{
AppLogger.Info("LauncherIpc", $"Successfully reported stage: {message.Stage}");
@@ -463,7 +458,7 @@ public partial class App : Application
try
{
_pluginRuntimeService?.Dispose();
_pluginRuntimeService = new PluginRuntimeService(_settingsFacade);
_pluginRuntimeService = new PluginRuntimeService(_settingsFacade, _publicIpcHostService);
HostSettingsFacadeProvider.BindPluginRuntime(_pluginRuntimeService);
_pluginRuntimeService.LoadInstalledPlugins();
}
@@ -1043,6 +1038,19 @@ public partial class App : Application
_pluginRuntimeService = null;
}
try
{
_publicIpcHostService?.Dispose();
}
catch (Exception ex)
{
AppLogger.Warn("PublicIpc", "Failed to dispose public IPC host during shutdown.", ex);
}
finally
{
_publicIpcHostService = null;
}
_settingsWindowService?.Close();
if (_settingsPageRegistry is IDisposable disposableRegistry)
{
@@ -1336,6 +1344,56 @@ public partial class App : Application
var languageCode = _localizationService.NormalizeLanguageCode(snapshot.LanguageCode);
return _localizationService.GetString(languageCode, key, fallback);
}
internal bool TryActivateMainWindowFromExternalIpc(string source)
{
return RestoreOrCreateMainWindowCore(showSingleInstanceNotice: false, source);
}
private void InitializePublicIpc()
{
if (_publicIpcHostService is not null)
{
return;
}
try
{
var version = typeof(App).Assembly.GetName().Version?.ToString() ?? "1.0.0";
_publicIpcHostService = new PublicIpcHostService();
_publicIpcHostService.PluginDescriptorProvider = BuildPublicPluginDescriptors;
_publicIpcHostService.RegisterPublicService<IPublicAppInfoService>(
new PublicAppInfoService(version, "Administrate", _startupAt));
_publicIpcHostService.RegisterPublicService<IPublicShellControlService>(
new PublicShellControlService());
_publicIpcHostService.RegisterPublicService<IPublicPluginCatalogService>(
new PublicPluginCatalogService(_publicIpcHostService));
_publicIpcHostService.Start();
AppLogger.Info("PublicIpc", $"Public IPC host started. PipeName='{IpcConstants.DefaultPipeName}'.");
}
catch (Exception ex)
{
AppLogger.Warn("PublicIpc", "Failed to initialize public IPC host.", ex);
}
}
private IReadOnlyList<PublicPluginDescriptor> BuildPublicPluginDescriptors()
{
var runtime = _pluginRuntimeService;
if (runtime is null)
{
return Array.Empty<PublicPluginDescriptor>();
}
return runtime.Catalog
.Select(entry => new PublicPluginDescriptor(
entry.Manifest.Id,
entry.Manifest.Name,
entry.Manifest.Version,
entry.IsLoaded,
entry.IsEnabled))
.ToArray();
}
}

View File

@@ -31,6 +31,7 @@
<ItemGroup>
<ProjectReference Include="..\LanMountainDesktop.Host.Abstractions\LanMountainDesktop.Host.Abstractions.csproj" />
<ProjectReference Include="..\LanMountainDesktop.Shared.Contracts\LanMountainDesktop.Shared.Contracts.csproj" />
<ProjectReference Include="..\LanMountainDesktop.Shared.IPC\LanMountainDesktop.Shared.IPC.csproj" />
<ProjectReference Include="..\LanMountainDesktop.Settings.Core\LanMountainDesktop.Settings.Core.csproj" />
<ProjectReference Include="..\LanMountainDesktop.Appearance\LanMountainDesktop.Appearance.csproj" />
<ProjectReference Include="..\LanMountainDesktop.DesktopComponents.Runtime\LanMountainDesktop.DesktopComponents.Runtime.csproj" />

View File

@@ -0,0 +1,29 @@
using LanMountainDesktop.Shared.IPC;
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
namespace LanMountainDesktop.Services.ExternalIpc;
internal sealed class PublicAppInfoService : IPublicAppInfoService
{
private readonly string _version;
private readonly string _codename;
private readonly DateTimeOffset _startedAt;
public PublicAppInfoService(string version, string codename, DateTimeOffset startedAt)
{
_version = version;
_codename = codename;
_startedAt = startedAt;
}
public PublicAppInfoSnapshot GetAppInfo()
{
return new PublicAppInfoSnapshot(
"LanMountainDesktop",
_version,
_codename,
IpcConstants.DefaultPipeName,
Environment.ProcessId,
_startedAt);
}
}

View File

@@ -0,0 +1,19 @@
using LanMountainDesktop.Shared.IPC;
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
namespace LanMountainDesktop.Services.ExternalIpc;
internal sealed class PublicPluginCatalogService : IPublicPluginCatalogService
{
private readonly PublicIpcHostService _publicIpcHostService;
public PublicPluginCatalogService(PublicIpcHostService publicIpcHostService)
{
_publicIpcHostService = publicIpcHostService;
}
public PublicIpcCatalogSnapshot GetCatalog()
{
return _publicIpcHostService.GetCatalogSnapshot();
}
}

View File

@@ -0,0 +1,47 @@
using Avalonia;
using Avalonia.Threading;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Shared.IPC.Abstractions.Services;
namespace LanMountainDesktop.Services.ExternalIpc;
internal sealed class PublicShellControlService : IPublicShellControlService
{
public Task<bool> ActivateMainWindowAsync()
{
return Dispatcher.UIThread.InvokeAsync(() =>
{
return (Application.Current as App)?.TryActivateMainWindowFromExternalIpc("PublicIpc") == true;
}).GetTask();
}
public Task<bool> OpenSettingsAsync(string? pageTag = null)
{
return Dispatcher.UIThread.InvokeAsync(() =>
{
if (Application.Current is not App app)
{
return false;
}
app.OpenIndependentSettingsModule("PublicIpc", pageTag);
return true;
}).GetTask();
}
public Task<bool> RestartAsync()
{
var lifecycle = App.CurrentHostApplicationLifecycle;
return Task.FromResult(lifecycle?.TryRestart(new HostApplicationLifecycleRequest(
Source: "PublicIpc",
Reason: "External IPC requested restart.")) == true);
}
public Task<bool> ExitAsync()
{
var lifecycle = App.CurrentHostApplicationLifecycle;
return Task.FromResult(lifecycle?.TryExit(new HostApplicationLifecycleRequest(
Source: "PublicIpc",
Reason: "External IPC requested exit.")) == true);
}
}

View File

@@ -1,6 +1,6 @@
using System.Timers;
using LanMountainDesktop.Services.Launcher;
using LanMountainDesktop.Shared.Contracts.Launcher;
using LanMountainDesktop.Shared.IPC;
namespace LanMountainDesktop.Services.Loading;
@@ -10,7 +10,7 @@ namespace LanMountainDesktop.Services.Loading;
public class LoadingStateReporter : IDisposable
{
private readonly LoadingStateManager _manager;
private readonly LauncherIpcClient? _ipcClient;
private readonly IExternalIpcNotificationPublisher? _notificationPublisher;
private readonly System.Timers.Timer _reportTimer;
private readonly object _lock = new();
private bool _isDisposed;
@@ -36,10 +36,10 @@ public class LoadingStateReporter : IDisposable
public LoadingStateReporter(
LoadingStateManager manager,
LauncherIpcClient? ipcClient = null)
IExternalIpcNotificationPublisher? notificationPublisher = null)
{
_manager = manager ?? throw new ArgumentNullException(nameof(manager));
_ipcClient = ipcClient;
_notificationPublisher = notificationPublisher;
// 创建定时上报定时器
_reportTimer = new System.Timers.Timer(ReportIntervalMs);
@@ -80,7 +80,7 @@ public class LoadingStateReporter : IDisposable
/// </summary>
public async Task ReportImmediatelyAsync()
{
if (_isDisposed || _ipcClient == null) return;
if (_isDisposed || _notificationPublisher == null) return;
var message = CreateDetailedProgressMessage();
await SendMessageAsync(message);
@@ -91,7 +91,7 @@ public class LoadingStateReporter : IDisposable
/// </summary>
public async Task ReportItemProgressAsync(string itemId, int percent, string? message = null)
{
if (_isDisposed || _ipcClient == null) return;
if (_isDisposed || _notificationPublisher == null) return;
var item = _manager.GetAllItems().FirstOrDefault(i => i.Id == itemId);
if (item == null) return;
@@ -121,7 +121,7 @@ public class LoadingStateReporter : IDisposable
/// </summary>
public async Task ReportStageChangeAsync(StartupStage stage, string? message = null)
{
if (_isDisposed || _ipcClient == null) return;
if (_isDisposed || _notificationPublisher == null) return;
var progressMessage = new DetailedProgressMessage
{
@@ -140,7 +140,7 @@ public class LoadingStateReporter : IDisposable
/// </summary>
public async Task ReportErrorAsync(string errorMessage, string? details = null)
{
if (_isDisposed || _ipcClient == null) return;
if (_isDisposed || _notificationPublisher == null) return;
var fullMessage = string.IsNullOrEmpty(details)
? errorMessage
@@ -280,7 +280,7 @@ public class LoadingStateReporter : IDisposable
/// </summary>
private async Task SendMessageAsync(DetailedProgressMessage message)
{
if (_ipcClient == null) return;
if (_notificationPublisher == null) return;
// 检查最小上报间隔
var now = DateTimeOffset.UtcNow;
@@ -293,15 +293,15 @@ public class LoadingStateReporter : IDisposable
try
{
// 转换为 StartupProgressMessage 以保持兼容性
var baseMessage = new StartupProgressMessage
var loadingStateMessage = _manager.GetLoadingStateMessage() with
{
Stage = message.Stage,
ProgressPercent = message.ProgressPercent,
OverallProgressPercent = message.ProgressPercent,
Message = FormatMessage(message),
Timestamp = DateTimeOffset.UtcNow
};
await _ipcClient.ReportProgressAsync(baseMessage);
await _notificationPublisher.NotifyAsync(IpcRoutedNotifyIds.LauncherLoadingState, loadingStateMessage);
_lastReportTime = DateTimeOffset.UtcNow;
}
catch (Exception ex)

View File

@@ -25,6 +25,7 @@ public sealed class LoadedPlugin : IDisposable, IAsyncDisposable
IReadOnlyList<PluginDesktopComponentRegistration> desktopComponents,
IReadOnlyList<PluginDesktopComponentEditorRegistration> desktopComponentEditors,
IReadOnlyList<PluginServiceExportDescriptor> exportedServices,
IReadOnlyList<PluginPublicIpcServiceDescriptor> publicIpcServices,
IReadOnlyList<IHostedService> hostedServices,
PluginLoadContext loadContext)
{
@@ -39,6 +40,7 @@ public sealed class LoadedPlugin : IDisposable, IAsyncDisposable
DesktopComponents = desktopComponents;
DesktopComponentEditors = desktopComponentEditors;
ExportedServices = exportedServices;
PublicIpcServices = publicIpcServices;
HostedServices = hostedServices;
LoadContext = loadContext;
}
@@ -67,6 +69,8 @@ public sealed class LoadedPlugin : IDisposable, IAsyncDisposable
public IReadOnlyList<PluginServiceExportDescriptor> ExportedServices { get; }
public IReadOnlyList<PluginPublicIpcServiceDescriptor> PublicIpcServices { get; }
public PluginLoadContext LoadContext { get; }
private IReadOnlyList<IHostedService> HostedServices { get; }

View File

@@ -14,8 +14,10 @@ using System.Threading.Tasks;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Shared.IPC;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using dotnetCampus.Ipc.CompilerServices.Attributes;
namespace LanMountainDesktop.Plugins;
@@ -187,9 +189,10 @@ public sealed class PluginLoader
.OrderBy(editor => editor.ComponentId, StringComparer.OrdinalIgnoreCase)
.ToArray();
var exportedServices = ResolveExports(manifest, pluginServices);
var publicIpcServices = ResolvePublicIpcServices(manifest, pluginServices);
AppLogger.Info(
"PluginLoader",
$"Plugin contributions resolved. PluginId='{manifest.Id}'; SettingsSections={settingsSections.Length}; Widgets={desktopComponents.Length}; Editors={desktopComponentEditors.Length}; Exports={exportedServices.Count}.");
$"Plugin contributions resolved. PluginId='{manifest.Id}'; SettingsSections={settingsSections.Length}; Widgets={desktopComponents.Length}; Editors={desktopComponentEditors.Length}; Exports={exportedServices.Count}; PublicIpcServices={publicIpcServices.Count}.");
hostedServices = pluginServices.GetServices<IHostedService>().ToArray();
StartHostedServices(hostedServices);
AppLogger.Info("PluginLoader", $"Hosted services started. PluginId='{manifest.Id}'; HostedServices={hostedServices.Count}.");
@@ -206,6 +209,7 @@ public sealed class PluginLoader
desktopComponents,
desktopComponentEditors,
exportedServices,
publicIpcServices,
hostedServices,
loadContext);
@@ -332,6 +336,7 @@ public sealed class PluginLoader
RegisterHostService<ISettingsService>(services, hostServices);
RegisterHostService<ISettingsCatalog>(services, hostServices);
RegisterHostService<IAppearanceThemeService>(services, hostServices);
RegisterHostService<IExternalIpcNotificationPublisher>(services, hostServices);
return services;
}
@@ -413,6 +418,68 @@ public sealed class PluginLoader
.ToArray();
}
private static IReadOnlyList<PluginPublicIpcServiceDescriptor> ResolvePublicIpcServices(
PluginManifest manifest,
IServiceProvider services)
{
var descriptors = new List<PluginPublicIpcServiceDescriptor>();
var seenKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var registration in services.GetServices<PluginPublicIpcServiceRegistration>())
{
var implementation = services.GetService(registration.ContractType)
?? throw new InvalidOperationException(
$"Plugin '{manifest.Id}' registered public IPC contract '{registration.ContractType.FullName}', but no singleton service instance was found.");
AddDescriptor(registration.ContractType, implementation, registration.ObjectId, registration.NotifyIds);
}
var builder = new RuntimePluginPublicIpcBuilder(services, AddDescriptor);
foreach (var contributor in services.GetServices<IPluginPublicIpcContributor>())
{
contributor.ConfigurePublicIpc(builder);
}
return descriptors;
void AddDescriptor(Type contractType, object implementation, string? objectId, IEnumerable<string>? notifyIds)
{
EnsurePublicIpcContract(manifest, contractType);
var normalizedObjectId = objectId ?? string.Empty;
var dedupeKey = $"{contractType.AssemblyQualifiedName}::{normalizedObjectId}";
if (!seenKeys.Add(dedupeKey))
{
throw new InvalidOperationException(
$"Plugin '{manifest.Id}' registered duplicate public IPC contract '{contractType.FullName}' with object id '{normalizedObjectId}'.");
}
descriptors.Add(new PluginPublicIpcServiceDescriptor(
contractType,
implementation,
string.IsNullOrEmpty(normalizedObjectId) ? null : normalizedObjectId,
notifyIds?
.Where(id => !string.IsNullOrWhiteSpace(id))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray() ?? []));
}
}
private static void EnsurePublicIpcContract(PluginManifest manifest, Type contractType)
{
if (!contractType.IsInterface)
{
throw new InvalidOperationException(
$"Plugin '{manifest.Id}' public IPC contract '{contractType.FullName}' must be an interface.");
}
if (!Attribute.IsDefined(contractType, typeof(IpcPublicAttribute), inherit: false))
{
throw new InvalidOperationException(
$"Plugin '{manifest.Id}' public IPC contract '{contractType.FullName}' must be marked with '{nameof(IpcPublicAttribute)}'.");
}
}
private static bool IsSupportedExportContract(PluginManifest manifest, Type contractType)
{
if (contractType.Assembly == typeof(IPlugin).Assembly)
@@ -1074,4 +1141,42 @@ public sealed class PluginLoader
string SourcePath,
PluginManifest Manifest,
PluginSourceKind SourceKind);
private sealed class RuntimePluginPublicIpcBuilder : IPluginPublicIpcBuilder
{
private readonly IServiceProvider _services;
private readonly Action<Type, object, string?, IEnumerable<string>?> _register;
public RuntimePluginPublicIpcBuilder(
IServiceProvider services,
Action<Type, object, string?, IEnumerable<string>?> register)
{
_services = services;
_register = register;
}
public IPluginPublicIpcBuilder AddService<TContract>(
string? objectId = null,
IEnumerable<string>? notifyIds = null)
where TContract : class
{
var implementation = _services.GetService(typeof(TContract))
?? throw new InvalidOperationException(
$"Plugin public IPC contributor requested contract '{typeof(TContract).FullName}', but no singleton service was registered.");
_register(typeof(TContract), implementation, objectId, notifyIds);
return this;
}
public IPluginPublicIpcBuilder AddService(
Type contractType,
object implementation,
string? objectId = null,
IEnumerable<string>? notifyIds = null)
{
ArgumentNullException.ThrowIfNull(contractType);
ArgumentNullException.ThrowIfNull(implementation);
_register(contractType, implementation, objectId, notifyIds);
return this;
}
}
}

View File

@@ -13,6 +13,7 @@ using LanMountainDesktop.Models;
using LanMountainDesktop.Plugins;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.Shared.IPC;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -31,6 +32,7 @@ public sealed class PluginRuntimeService : IDisposable
private readonly IPluginPackageManager _packageManager;
private readonly ISettingsFacadeService _settingsFacade;
private readonly SettingsCatalogService _settingsCatalogService;
private readonly PublicIpcHostService? _publicIpcHostService;
private readonly List<LoadedPlugin> _loadedPlugins = [];
private readonly List<PluginLoadResult> _loadResults = [];
private readonly List<PluginCatalogEntry> _catalog = [];
@@ -39,13 +41,16 @@ public sealed class PluginRuntimeService : IDisposable
private readonly List<PluginDesktopComponentEditorContribution> _desktopComponentEditors = [];
private readonly object _packageMutationGate = new();
public PluginRuntimeService(ISettingsFacadeService? settingsFacade = null)
public PluginRuntimeService(
ISettingsFacadeService? settingsFacade = null,
PublicIpcHostService? publicIpcHostService = null)
{
PluginsDirectory = Path.Combine(GetUserDataRootDirectory(), "Extensions", "Plugins");
_sharedContractManager = new PluginSharedContractManager(
Path.Combine(GetUserDataRootDirectory(), "PluginMarket"));
_packageManager = new PluginRuntimePackageManager(this);
_settingsFacade = settingsFacade ?? new SettingsFacadeService();
_publicIpcHostService = publicIpcHostService;
_settingsCatalogService = _settingsFacade.Catalog as SettingsCatalogService
?? new SettingsCatalogService();
if (_settingsFacade is SettingsFacadeService concreteFacade)
@@ -58,7 +63,8 @@ public sealed class PluginRuntimeService : IDisposable
_exportRegistry,
_settingsFacade,
_settingsFacade.Settings,
_settingsFacade.Catalog);
_settingsFacade.Catalog,
_publicIpcHostService);
_loaderOptions = CreateOptions();
_loader = new PluginLoader(_loaderOptions);
}
@@ -675,6 +681,8 @@ public sealed class PluginRuntimeService : IDisposable
AddSharedAssembly(options, typeof(App).Assembly);
AddSharedAssembly(options, typeof(IServiceCollection).Assembly);
AddSharedAssembly(options, typeof(HostBuilderContext).Assembly);
AddSharedAssembly(options, typeof(IExternalIpcNotificationPublisher).Assembly);
AddSharedAssembly(options, typeof(dotnetCampus.Ipc.Pipes.IpcProvider).Assembly);
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
@@ -761,6 +769,19 @@ public sealed class PluginRuntimeService : IDisposable
{
_desktopComponentEditors.Add(new PluginDesktopComponentEditorContribution(loadedPlugin, desktopComponentEditor));
}
if (_publicIpcHostService is not null)
{
foreach (var publicIpcService in loadedPlugin.PublicIpcServices)
{
_publicIpcHostService.RegisterPublicService(
publicIpcService.ContractType,
publicIpcService.Implementation,
publicIpcService.ObjectId,
loadedPlugin.Manifest.Id,
publicIpcService.NotifyIds);
}
}
}
private void RegisterSharedContractsForLoad(PluginManifest manifest)
@@ -990,11 +1011,12 @@ public sealed class PluginRuntimeService : IDisposable
{
private readonly IPluginPackageManager _packageManager;
private readonly IHostApplicationLifecycle _applicationLifecycle;
private readonly IPluginExportRegistry _exportRegistry;
private readonly ISettingsFacadeService _settingsFacade;
private readonly ISettingsService _settingsService;
private readonly ISettingsCatalog _settingsCatalog;
private readonly IAppearanceThemeService _appearanceThemeService;
private readonly IPluginExportRegistry _exportRegistry;
private readonly ISettingsFacadeService _settingsFacade;
private readonly ISettingsService _settingsService;
private readonly ISettingsCatalog _settingsCatalog;
private readonly IAppearanceThemeService _appearanceThemeService;
private readonly IExternalIpcNotificationPublisher? _externalIpcNotificationPublisher;
public PluginHostServiceProvider(
IPluginPackageManager packageManager,
@@ -1002,7 +1024,8 @@ public sealed class PluginRuntimeService : IDisposable
IPluginExportRegistry exportRegistry,
ISettingsFacadeService settingsFacade,
ISettingsService settingsService,
ISettingsCatalog settingsCatalog)
ISettingsCatalog settingsCatalog,
IExternalIpcNotificationPublisher? externalIpcNotificationPublisher)
{
_packageManager = packageManager;
_applicationLifecycle = applicationLifecycle;
@@ -1011,6 +1034,7 @@ public sealed class PluginRuntimeService : IDisposable
_settingsService = settingsService;
_settingsCatalog = settingsCatalog;
_appearanceThemeService = HostAppearanceThemeProvider.GetOrCreate();
_externalIpcNotificationPublisher = externalIpcNotificationPublisher;
}
public object? GetService(Type serviceType)
@@ -1050,6 +1074,11 @@ public sealed class PluginRuntimeService : IDisposable
return _appearanceThemeService;
}
if (serviceType == typeof(IExternalIpcNotificationPublisher))
{
return _externalIpcNotificationPublisher;
}
return null;
}
}