Brings back the 1.5.6 per-channel colour editor lost in the v1.6.0 rewrite: presets (incl. brand styling), per-ChatType reset/import-from-game/colour-edit over Config.ChatColours, the colour-selected-input-channel toggle, and the theme adopt-banner. The colour-edit drag recolours live but defers SaveConfig to release (IsItemDeactivatedAfterEdit) to avoid a per-frame full-config write.
420 lines
20 KiB
C#
420 lines
20 KiB
C#
using Dalamud.Interface.ImGuiFileDialog;
|
|
using Dalamud.Plugin;
|
|
using Dalamud.Plugin.Services;
|
|
using HellionChat.Infrastructure.Hosting;
|
|
using HellionChat.Infrastructure.Logging;
|
|
using HellionChat.Ipc;
|
|
using HellionChat.Themes;
|
|
using HellionChat.Ui;
|
|
using HellionChat.Util;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace HellionChat;
|
|
|
|
// Builds the generic-host DI container that drives v1.5.0+. The factory is
|
|
// invoked synchronously from Plugin.ctor (after the schema gate clears) so the
|
|
// container exists before PluginLifecycle.LoadAsync runs. See plan §1 for the
|
|
// deliberate divergence from Lightless' deferred Func-delegate pattern.
|
|
internal static class PluginHostFactory
|
|
{
|
|
public static IHost Build(Plugin plugin, PluginHostDependencies dependencies)
|
|
{
|
|
return new HostBuilder()
|
|
.UseContentRoot(dependencies.PluginInterface.ConfigDirectory.FullName)
|
|
.ConfigureLogging(logging =>
|
|
{
|
|
logging.ClearProviders();
|
|
logging.AddDalamudLogging(dependencies.PluginLog);
|
|
logging.SetMinimumLevel(LogLevel.Trace);
|
|
})
|
|
// ValidateOnBuild eagerly instantiates every singleton at Build time
|
|
// so missing registrations / ConstructorCallSite cycles throw on
|
|
// load instead of producing a silent hang. ValidateScopes is cheap
|
|
// (we only use singletons) but guards against future Scoped misuse.
|
|
.UseDefaultServiceProvider(o =>
|
|
{
|
|
o.ValidateOnBuild = true;
|
|
o.ValidateScopes = true;
|
|
})
|
|
.ConfigureServices(services => ConfigureServices(services, plugin, dependencies))
|
|
.Build();
|
|
}
|
|
|
|
private static void ConfigureServices(
|
|
IServiceCollection services,
|
|
Plugin plugin,
|
|
PluginHostDependencies dependencies
|
|
)
|
|
{
|
|
// Block A — Dalamud services (21 [PluginService] singletons).
|
|
services.AddSingleton(dependencies);
|
|
services.AddSingleton(dependencies.PluginInterface);
|
|
services.AddSingleton(dependencies.PluginLog);
|
|
services.AddSingleton(dependencies.ChatGui);
|
|
services.AddSingleton(dependencies.ClientState);
|
|
services.AddSingleton(dependencies.CommandManager);
|
|
services.AddSingleton(dependencies.Condition);
|
|
services.AddSingleton(dependencies.DataManager);
|
|
services.AddSingleton(dependencies.Framework);
|
|
services.AddSingleton(dependencies.GameGui);
|
|
services.AddSingleton(dependencies.KeyState);
|
|
services.AddSingleton(dependencies.ObjectTable);
|
|
services.AddSingleton(dependencies.PartyList);
|
|
services.AddSingleton(dependencies.TargetManager);
|
|
services.AddSingleton(dependencies.TextureProvider);
|
|
services.AddSingleton(dependencies.GameInteropProvider);
|
|
services.AddSingleton(dependencies.GameConfig);
|
|
services.AddSingleton(dependencies.Notification);
|
|
services.AddSingleton(dependencies.AddonLifecycle);
|
|
services.AddSingleton(dependencies.PlayerState);
|
|
services.AddSingleton(dependencies.Evaluator);
|
|
services.AddSingleton(dependencies.SelfTestRegistry);
|
|
|
|
// Self-references: Plugin and its WindowSystem already exist.
|
|
services.AddSingleton(plugin);
|
|
services.AddSingleton(plugin.WindowSystem);
|
|
services.AddSingleton<PluginLifecycle>();
|
|
|
|
// Block B — HellionChat singletons. Factory lambdas because most
|
|
// classes are internal-sealed and the default activator only sees
|
|
// public ctors.
|
|
services.AddSingleton<IPlatformUtil>(_ => new DalamudPlatformUtil());
|
|
services.AddSingleton<IPluginLogProxy>(sp => new DalamudPluginLogProxy(
|
|
sp.GetRequiredService<IPluginLog>()
|
|
));
|
|
services.AddSingleton<FileDialogManager>(_ => new FileDialogManager());
|
|
services.AddSingleton(sp => new Commands(sp.GetRequiredService<ILogger<Commands>>()));
|
|
services.AddSingleton(sp => new FontManager(
|
|
sp.GetRequiredService<IDalamudPluginInterface>()
|
|
));
|
|
services.AddSingleton(sp => new IpcManager(sp.GetRequiredService<ILogger<IpcManager>>()));
|
|
services.AddSingleton(sp => new ExtraChat(sp.GetRequiredService<ILogger<ExtraChat>>()));
|
|
|
|
services.AddSingleton(sp => new ThemeRegistry(
|
|
Path.Combine(
|
|
sp.GetRequiredService<IDalamudPluginInterface>().ConfigDirectory.FullName,
|
|
"themes"
|
|
),
|
|
sp.GetRequiredService<ILogger<ThemeRegistry>>()
|
|
));
|
|
|
|
services.AddSingleton(_ => new Ui.StyleEngine.TokenResolver());
|
|
services.AddSingleton(sp => new Ui.StyleEngine.PushStack(
|
|
sp.GetRequiredService<Ui.StyleEngine.TokenResolver>()
|
|
));
|
|
|
|
services.AddSingleton(sp => new GameFunctions.GameFunctions(
|
|
sp.GetRequiredService<Plugin>(),
|
|
sp.GetRequiredService<ILogger<GameFunctions.GameFunctions>>(),
|
|
sp.GetRequiredService<ILoggerFactory>()
|
|
));
|
|
services.AddSingleton(sp => new TypingIpc(
|
|
sp.GetRequiredService<Plugin>(),
|
|
sp.GetRequiredService<Ui.Components.InputBar>(),
|
|
sp.GetRequiredService<ILogger<TypingIpc>>()
|
|
));
|
|
|
|
services.AddSingleton(sp => new Integrations.HonorificService(
|
|
sp.GetRequiredService<IDalamudPluginInterface>(),
|
|
sp.GetRequiredService<ILogger<Integrations.HonorificService>>(),
|
|
sp.GetRequiredService<IFramework>()
|
|
));
|
|
services.AddSingleton(sp => new Services.TellRouterService(
|
|
sp.GetRequiredService<IChatGui>(),
|
|
sp.GetRequiredService<ILogger<Services.TellRouterService>>()
|
|
));
|
|
|
|
services.AddSingleton(sp => new Ui.Components.HonorificHeader(
|
|
sp.GetRequiredService<Integrations.HonorificService>(),
|
|
sp.GetRequiredService<FontManager>(),
|
|
sp.GetRequiredService<ThemeRegistry>(),
|
|
sp.GetRequiredService<Ui.StyleEngine.TokenResolver>()
|
|
));
|
|
services.AddSingleton(sp => new Ui.Components.Sidebar(
|
|
sp.GetRequiredService<ThemeRegistry>(),
|
|
sp.GetRequiredService<Ui.StyleEngine.TokenResolver>(),
|
|
sp.GetRequiredService<FontManager>(),
|
|
sp.GetRequiredService<ILogger<Ui.Components.Sidebar>>(),
|
|
sp.GetRequiredService<Ui.Windows.ChannelPopoutPool>()
|
|
));
|
|
services.AddSingleton(sp => new Ui.Components.MessageList(
|
|
sp.GetRequiredService<FontManager>(),
|
|
sp.GetRequiredService<Ui.Components.ChunkRenderer>()
|
|
));
|
|
services.AddSingleton(_ => new Ui.Components.SymbolPicker());
|
|
services.AddSingleton(sp => new Ui.Components.InputBar(
|
|
sp.GetRequiredService<Ui.Components.SymbolPicker>(),
|
|
sp.GetRequiredService<FontManager>(),
|
|
sp.GetRequiredService<ThemeRegistry>(),
|
|
sp.GetRequiredService<Ui.StyleEngine.TokenResolver>(),
|
|
sp.GetRequiredService<ILogger<Ui.Components.InputBar>>(),
|
|
() => sp.GetRequiredService<Plugin>().SettingsWindow.Toggle(),
|
|
sp.GetRequiredService<Ui.CommandHelpWindow>()
|
|
));
|
|
services.AddSingleton(sp => new Ui.Components.Settings.TabSidebar(
|
|
sp.GetRequiredService<FontManager>()
|
|
));
|
|
services.AddSingleton(sp => new Ui.Components.Settings.ContentArea());
|
|
services.AddSingleton(sp => new Ui.Components.Settings.ThemePicker(
|
|
sp.GetRequiredService<ThemeRegistry>(),
|
|
sp.GetRequiredService<Plugin>()
|
|
));
|
|
services.AddSingleton(sp => new Ui.Components.Settings.ColorPicker(
|
|
sp.GetRequiredService<ThemeRegistry>()
|
|
));
|
|
services.AddSingleton(sp => new Ui.Components.Settings.LivePreviewPanel(
|
|
sp.GetRequiredService<ThemeRegistry>(),
|
|
sp.GetRequiredService<Ui.StyleEngine.TokenResolver>(),
|
|
sp.GetRequiredService<FontManager>()
|
|
));
|
|
services.AddSingleton(sp => new Ui.Components.Settings.ThemeImportExportRow(
|
|
sp.GetRequiredService<ThemeRegistry>(),
|
|
sp.GetRequiredService<ILogger<Ui.Components.Settings.ThemeImportExportRow>>()
|
|
));
|
|
services.AddSingleton(sp => new Ui.Components.Settings.FontsSection(
|
|
sp.GetRequiredService<Plugin>(),
|
|
sp.GetRequiredService<FontManager>()
|
|
));
|
|
services.AddSingleton(sp => new Ui.Components.Settings.ChatColourPicker(
|
|
sp.GetRequiredService<Plugin>(),
|
|
sp.GetRequiredService<ThemeRegistry>()
|
|
));
|
|
services.AddSingleton(sp => new Ui.Components.Settings.Tabs.AppearanceTab(
|
|
sp.GetRequiredService<Ui.Components.Settings.ThemePicker>(),
|
|
sp.GetRequiredService<Ui.Components.Settings.ColorPicker>(),
|
|
sp.GetRequiredService<Ui.Components.Settings.LivePreviewPanel>(),
|
|
sp.GetRequiredService<Ui.Components.Settings.ThemeImportExportRow>(),
|
|
sp.GetRequiredService<Ui.Components.Settings.FontsSection>(),
|
|
sp.GetRequiredService<Ui.Components.Settings.ChatColourPicker>()
|
|
));
|
|
services.AddSingleton(sp => new Ui.Components.Settings.Tabs.GeneralTab(
|
|
sp.GetRequiredService<Plugin>()
|
|
));
|
|
services.AddSingleton(sp => new Ui.Components.Settings.Tabs.ChatTab(
|
|
sp.GetRequiredService<Plugin>()
|
|
));
|
|
services.AddSingleton(sp => new Ui.Components.Settings.Tabs.WindowTab(
|
|
sp.GetRequiredService<Plugin>()
|
|
));
|
|
services.AddSingleton(sp => new Ui.Components.Settings.Tabs.ChannelsTab(
|
|
sp.GetRequiredService<Plugin>()
|
|
));
|
|
services.AddSingleton(sp => new Ui.Components.Settings.Tabs.DataPrivacyTab(
|
|
sp.GetRequiredService<Plugin>()
|
|
));
|
|
services.AddSingleton(sp => new Ui.Components.Settings.Tabs.AboutTab(
|
|
sp.GetRequiredService<FontManager>(),
|
|
sp.GetRequiredService<Plugin>(),
|
|
sp.GetRequiredService<Integrations.HonorificService>(),
|
|
sp.GetRequiredService<ThemeRegistry>(),
|
|
sp.GetRequiredService<IPlatformUtil>()
|
|
));
|
|
services.AddSingleton(sp => new Ui.Components.StatusBar(
|
|
sp.GetRequiredService<ThemeRegistry>(),
|
|
sp.GetRequiredService<FontManager>()
|
|
));
|
|
services.AddSingleton(sp => new Ui.Components.TopTabBar(
|
|
sp.GetRequiredService<Ui.Windows.ChannelPopoutPool>()
|
|
));
|
|
services.AddSingleton(sp => new Ui.Windows.MainWindow(
|
|
sp.GetRequiredService<Ui.Components.HonorificHeader>(),
|
|
sp.GetRequiredService<Ui.Components.Sidebar>(),
|
|
sp.GetRequiredService<Ui.Components.TopTabBar>(),
|
|
sp.GetRequiredService<Ui.Components.MessageList>(),
|
|
sp.GetRequiredService<Ui.Components.InputBar>(),
|
|
sp.GetRequiredService<Ui.Components.StatusBar>(),
|
|
sp.GetRequiredService<Lender<PayloadHandler>>()
|
|
));
|
|
services.AddSingleton(sp => new Integrations.FailedTellNotifier(
|
|
sp.GetRequiredService<ILogger<Integrations.FailedTellNotifier>>()
|
|
));
|
|
services.AddSingleton(sp => new Integrations.CustomAudioPlayer(
|
|
sp.GetRequiredService<ILogger<Integrations.CustomAudioPlayer>>()
|
|
));
|
|
|
|
services.AddSingleton(sp => new MessageManager(
|
|
sp.GetRequiredService<Plugin>(),
|
|
sp.GetRequiredService<ILogger<MessageManager>>(),
|
|
sp.GetRequiredService<ILoggerFactory>()
|
|
));
|
|
|
|
// MessageStore is allocated inside MessageManager.ctor; a separate
|
|
// container singleton would double-construct the SQLite handle.
|
|
services.AddSingleton(sp =>
|
|
{
|
|
var pluginRef = sp.GetRequiredService<Plugin>();
|
|
var manager = sp.GetRequiredService<MessageManager>();
|
|
return new AutoTellTabsService(
|
|
pluginRef,
|
|
manager,
|
|
manager.Store,
|
|
sp.GetRequiredService<ILogger<AutoTellTabsService>>()
|
|
);
|
|
});
|
|
|
|
// Factory-lambdas for ChunkRenderer, PayloadHandler, and Lender<PayloadHandler>
|
|
// because all three are internal-sealed (ActivatorUtilities can't reflect into
|
|
// internal ctors) and Lender<T> has an internal ctor by design.
|
|
// PayloadHandler registered twice: once as singleton for G/H, once via Lender<T> for per-frame isolation (I/J/K).
|
|
services.AddSingleton(sp => new Ui.Components.ChunkRenderer(
|
|
sp.GetRequiredService<ThemeRegistry>(),
|
|
sp.GetRequiredService<FontManager>(),
|
|
sp.GetRequiredService<ILogger<Ui.Components.ChunkRenderer>>(),
|
|
sp.GetRequiredService<GameFunctions.GameFunctions>()
|
|
));
|
|
services.AddSingleton(sp => MakePayloadHandler(sp));
|
|
services.AddSingleton(sp => new Lender<PayloadHandler>(() => MakePayloadHandler(sp)));
|
|
|
|
// Pop-out windows: each gets its OWN MessageList + InputBar so the
|
|
// channel pill and message scroll are per-window. The PayloadHandler is
|
|
// attached post-build (ChannelPopoutInitHostedService), NEVER via ctor
|
|
// (plan §B.2 — would close a silent FactoryCallSite cycle).
|
|
services.AddSingleton<Func<int, Ui.Windows.ChannelPopoutWindow>>(sp =>
|
|
slot => new Ui.Windows.ChannelPopoutWindow(
|
|
slot,
|
|
new Ui.Components.MessageList(
|
|
sp.GetRequiredService<FontManager>(),
|
|
sp.GetRequiredService<Ui.Components.ChunkRenderer>()
|
|
),
|
|
new Ui.Components.InputBar(
|
|
sp.GetRequiredService<Ui.Components.SymbolPicker>(),
|
|
sp.GetRequiredService<FontManager>(),
|
|
sp.GetRequiredService<ThemeRegistry>(),
|
|
sp.GetRequiredService<Ui.StyleEngine.TokenResolver>(),
|
|
sp.GetRequiredService<ILogger<Ui.Components.InputBar>>(),
|
|
() => sp.GetRequiredService<Plugin>().SettingsWindow.Toggle(),
|
|
sp.GetRequiredService<Ui.CommandHelpWindow>()
|
|
),
|
|
sp.GetRequiredService<ILogger<Ui.Windows.ChannelPopoutWindow>>(),
|
|
sp.GetRequiredService<FontManager>()
|
|
)
|
|
);
|
|
services.AddSingleton(sp => new Ui.Windows.ChannelPopoutPool(
|
|
sp.GetRequiredService<Func<int, Ui.Windows.ChannelPopoutWindow>>(),
|
|
sp.GetRequiredService<ILogger<Ui.Windows.ChannelPopoutPool>>()
|
|
));
|
|
|
|
// Block C — Windows. WindowSystem.AddWindow is called from
|
|
// PluginLifecycle.LoadAsync on the framework thread.
|
|
services.AddSingleton(sp => new Ui.Windows.SettingsWindow(
|
|
sp.GetRequiredService<Plugin>(),
|
|
sp.GetRequiredService<Ui.Components.Settings.TabSidebar>(),
|
|
sp.GetRequiredService<Ui.Components.Settings.ContentArea>(),
|
|
sp.GetRequiredService<Ui.Components.Settings.ThemePicker>(),
|
|
sp.GetRequiredService<Ui.Components.Settings.ColorPicker>(),
|
|
sp.GetRequiredService<Ui.Components.Settings.LivePreviewPanel>(),
|
|
sp.GetRequiredService<Ui.Components.Settings.Tabs.AppearanceTab>(),
|
|
sp.GetRequiredService<Ui.Components.Settings.Tabs.GeneralTab>(),
|
|
sp.GetRequiredService<Ui.Components.Settings.Tabs.ChatTab>(),
|
|
sp.GetRequiredService<Ui.Components.Settings.Tabs.WindowTab>(),
|
|
sp.GetRequiredService<Ui.Components.Settings.Tabs.ChannelsTab>(),
|
|
sp.GetRequiredService<Ui.Components.Settings.Tabs.DataPrivacyTab>(),
|
|
sp.GetRequiredService<Ui.Components.Settings.Tabs.AboutTab>(),
|
|
sp.GetRequiredService<ILoggerFactory>()
|
|
));
|
|
services.AddSingleton(sp => new DbViewer(
|
|
sp.GetRequiredService<Plugin>(),
|
|
sp.GetRequiredService<ILogger<DbViewer>>()
|
|
));
|
|
services.AddSingleton(sp => new InputPreview(
|
|
sp.GetRequiredService<Ui.Components.ChunkRenderer>(),
|
|
sp.GetRequiredService<Lender<PayloadHandler>>(),
|
|
sp.GetRequiredService<Ui.Windows.MainWindow>(),
|
|
sp.GetRequiredService<Ui.Components.InputBar>(),
|
|
sp.GetRequiredService<ILogger<InputPreview>>()
|
|
));
|
|
// No MainWindow ctor-param: breaks the InputBar -> CommandHelpWindow ->
|
|
// MainWindow -> InputBar singleton cycle. MainWindow is wired post-build
|
|
// via CommandHelpWindowInitHostedService.
|
|
services.AddSingleton(sp => new CommandHelpWindow(
|
|
sp.GetRequiredService<Ui.Components.ChunkRenderer>(),
|
|
sp.GetRequiredService<ILogger<CommandHelpWindow>>()
|
|
));
|
|
services.AddSingleton(sp => new SeStringDebugger(sp.GetRequiredService<Plugin>()));
|
|
services.AddSingleton(sp => new DebuggerWindow(
|
|
sp.GetRequiredService<Plugin>(),
|
|
sp.GetRequiredService<PayloadHandler>()
|
|
));
|
|
services.AddSingleton(sp => new FirstRunWizard(sp.GetRequiredService<Plugin>()));
|
|
|
|
// Hosted-service adapters: thin wrappers around the existing init
|
|
// methods so the service class bodies stay unchanged. FontManager
|
|
// does not need one — its ctor runs the init inline inside a single
|
|
// SuppressAutoRebuild block on eager resolve.
|
|
services.AddHostedService(sp => new ThemeRegistryInitHostedService(
|
|
sp.GetRequiredService<ThemeRegistry>(),
|
|
sp.GetRequiredService<FontManager>()
|
|
));
|
|
services.AddHostedService(sp => new IpcManagerInitHostedService(
|
|
sp.GetRequiredService<IpcManager>()
|
|
));
|
|
services.AddHostedService(sp => new TypingIpcInitHostedService(
|
|
sp.GetRequiredService<TypingIpc>()
|
|
));
|
|
services.AddHostedService(sp => new ExtraChatInitHostedService(
|
|
sp.GetRequiredService<ExtraChat>()
|
|
));
|
|
services.AddHostedService(sp => new MessageManagerInitHostedService(
|
|
sp.GetRequiredService<IDalamudPluginInterface>(),
|
|
sp.GetRequiredService<MessageManager>()
|
|
));
|
|
services.AddHostedService(sp => new AutoTellTabsServiceInitHostedService(
|
|
sp.GetRequiredService<AutoTellTabsService>()
|
|
));
|
|
services.AddHostedService(
|
|
sp => new Infrastructure.Hosting.FailedTellNotifierInitHostedService(
|
|
sp.GetRequiredService<Integrations.FailedTellNotifier>()
|
|
)
|
|
);
|
|
services.AddHostedService(sp => new PayloadHandlerInitHostedService(
|
|
sp.GetRequiredService<PayloadHandler>(),
|
|
sp.GetRequiredService<Ui.Components.MessageList>()
|
|
));
|
|
services.AddHostedService(sp => new CommandHelpWindowInitHostedService(
|
|
sp.GetRequiredService<Ui.CommandHelpWindow>(),
|
|
sp.GetRequiredService<Ui.Windows.MainWindow>()
|
|
));
|
|
services.AddHostedService(sp => new ChannelPopoutInitHostedService(
|
|
sp.GetRequiredService<Ui.Windows.ChannelPopoutPool>(),
|
|
sp.GetRequiredService<PayloadHandler>()
|
|
));
|
|
}
|
|
|
|
private static PayloadHandler MakePayloadHandler(IServiceProvider sp) =>
|
|
new(
|
|
sp.GetRequiredService<ThemeRegistry>(),
|
|
sp.GetRequiredService<IpcManager>(),
|
|
sp.GetRequiredService<GameFunctions.GameFunctions>(),
|
|
sp.GetRequiredService<Ui.Components.InputBar>(),
|
|
sp.GetRequiredService<Ui.Windows.MainWindow>(),
|
|
sp.GetRequiredService<Ui.Components.ChunkRenderer>(),
|
|
sp.GetRequiredService<ILogger<PayloadHandler>>()
|
|
);
|
|
}
|
|
|
|
internal sealed record PluginHostDependencies(
|
|
IDalamudPluginInterface PluginInterface,
|
|
IPluginLog PluginLog,
|
|
IChatGui ChatGui,
|
|
IClientState ClientState,
|
|
ICommandManager CommandManager,
|
|
ICondition Condition,
|
|
IDataManager DataManager,
|
|
IFramework Framework,
|
|
IGameGui GameGui,
|
|
IKeyState KeyState,
|
|
IObjectTable ObjectTable,
|
|
IPartyList PartyList,
|
|
ITargetManager TargetManager,
|
|
ITextureProvider TextureProvider,
|
|
IGameInteropProvider GameInteropProvider,
|
|
IGameConfig GameConfig,
|
|
INotificationManager Notification,
|
|
IAddonLifecycle AddonLifecycle,
|
|
IPlayerState PlayerState,
|
|
ISeStringEvaluator Evaluator,
|
|
ISelfTestRegistry SelfTestRegistry
|
|
);
|