mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-31 21:03:43 +01:00
merge
This commit is contained in:
commit
95ec633cc5
163 changed files with 7036 additions and 1585 deletions
|
|
@ -19,6 +19,7 @@ using Dalamud.Interface;
|
|||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Internal.Windows.PluginInstaller;
|
||||
using Dalamud.Interface.Internal.Windows.Settings;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Internal;
|
||||
using Dalamud.Plugin.Internal.AutoUpdate;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
|
|
@ -100,7 +101,7 @@ internal sealed class DalamudPluginInterface : IDalamudPluginInterface, IDisposa
|
|||
public PluginLoadReason Reason { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not auto-updates have already completed this session.
|
||||
/// Gets a value indicating whether auto-updates have already completed this session.
|
||||
/// </summary>
|
||||
public bool IsAutoUpdateComplete => Service<AutoUpdateManager>.Get().IsAutoUpdateComplete;
|
||||
|
||||
|
|
@ -482,7 +483,7 @@ internal sealed class DalamudPluginInterface : IDalamudPluginInterface, IDisposa
|
|||
|
||||
/// <inheritdoc/>
|
||||
public async Task<T> CreateAsync<T>(params object[] scopedObjects) where T : class =>
|
||||
(T)await this.plugin.ServiceScope!.CreateAsync(typeof(T), this.GetPublicIocScopes(scopedObjects));
|
||||
(T)await this.plugin.ServiceScope!.CreateAsync(typeof(T), ObjectInstanceVisibility.ExposedToPlugins, this.GetPublicIocScopes(scopedObjects));
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Inject(object instance, params object[] scopedObjects)
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ public interface IDalamudPluginInterface
|
|||
/// <param name="kind">What action caused this event to be fired.</param>
|
||||
/// <param name="affectedThisPlugin">If this plugin was affected by the change.</param>
|
||||
public delegate void ActivePluginsChangedDelegate(PluginListInvalidationKind kind, bool affectedThisPlugin);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Event that gets fired when loc is changed
|
||||
/// </summary>
|
||||
|
|
@ -52,7 +52,7 @@ public interface IDalamudPluginInterface
|
|||
PluginLoadReason Reason { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not auto-updates have already completed this session.
|
||||
/// Gets a value indicating whether auto-updates have already completed this session.
|
||||
/// </summary>
|
||||
bool IsAutoUpdateComplete { get; }
|
||||
|
||||
|
|
|
|||
|
|
@ -34,12 +34,12 @@ public interface IExposedPlugin
|
|||
bool IsTesting { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this plugin is orphaned(belongs to a repo) or not.
|
||||
/// Gets a value indicating whether this plugin is orphaned(belongs to a repo) or not.
|
||||
/// </summary>
|
||||
bool IsOrphaned { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this plugin is serviced(repo still exists, but plugin no longer does).
|
||||
/// Gets a value indicating whether this plugin is serviced(repo still exists, but plugin no longer does).
|
||||
/// </summary>
|
||||
bool IsDecommissioned { get; }
|
||||
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ internal class AutoUpdateManager : IServiceType
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not auto-updates have already completed this session.
|
||||
/// Gets a value indicating whether auto-updates have already completed this session.
|
||||
/// </summary>
|
||||
public bool IsAutoUpdateComplete { get; private set; }
|
||||
|
||||
|
|
@ -458,7 +458,7 @@ internal class AutoUpdateManager : IServiceType
|
|||
.Where(
|
||||
p =>
|
||||
!p.InstalledPlugin.IsDev && // Never update dev-plugins
|
||||
p.InstalledPlugin.IsWantedByAnyProfile && // Never update plugins that are not wanted by any profile(not enabled)
|
||||
(p.InstalledPlugin.IsWantedByAnyProfile || this.config.UpdateDisabledPlugins) && // Never update plugins that are not wanted by any profile(not enabled)
|
||||
!p.InstalledPlugin.Manifest.ScheduledForDeletion); // Never update plugins that we want to get rid of
|
||||
|
||||
return updateablePlugins.Where(FilterPlugin).ToList();
|
||||
|
|
@ -499,7 +499,7 @@ internal class AutoUpdateManager : IServiceType
|
|||
condition.OnlyAny(ConditionFlag.NormalConditions,
|
||||
ConditionFlag.Jumping,
|
||||
ConditionFlag.Mounted,
|
||||
ConditionFlag.UsingParasol);
|
||||
ConditionFlag.UsingFashionAccessory);
|
||||
}
|
||||
|
||||
private bool IsPluginManagerReady()
|
||||
|
|
|
|||
199
Dalamud/Plugin/Internal/PluginErrorHandler.cs
Normal file
199
Dalamud/Plugin/Internal/PluginErrorHandler.cs
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Plugin.Internal.Types;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Plugin.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Service responsible for notifying the user when a plugin is creating errors.
|
||||
/// </summary>
|
||||
[ServiceManager.ScopedService]
|
||||
internal class PluginErrorHandler : IServiceType
|
||||
{
|
||||
private readonly LocalPlugin plugin;
|
||||
private readonly NotificationManager notificationManager;
|
||||
private readonly DalamudInterface di;
|
||||
|
||||
private readonly Dictionary<Type, Delegate> invokerCache = new();
|
||||
|
||||
private DateTime lastErrorTime = DateTime.MinValue;
|
||||
private IActiveNotification? activeNotification;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PluginErrorHandler"/> class.
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin we are notifying for.</param>
|
||||
/// <param name="notificationManager">The notification manager.</param>
|
||||
/// <param name="di">The dalamud interface class.</param>
|
||||
[ServiceManager.ServiceConstructor]
|
||||
public PluginErrorHandler(LocalPlugin plugin, NotificationManager notificationManager, DalamudInterface di)
|
||||
{
|
||||
this.plugin = plugin;
|
||||
this.notificationManager = notificationManager;
|
||||
this.di = di;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoke the specified delegate and catch any exceptions that occur.
|
||||
/// Writes an error message to the log if an exception occurs and shows
|
||||
/// a notification if the plugin is a dev plugin and the user has enabled error notifications.
|
||||
/// </summary>
|
||||
/// <param name="eventHandler">The delegate to invoke.</param>
|
||||
/// <param name="hint">A hint to show about the origin of the exception if an error occurs.</param>
|
||||
/// <param name="args">Arguments to the event handler.</param>
|
||||
/// <typeparam name="TDelegate">The type of the delegate.</typeparam>
|
||||
/// <returns>Whether invocation was successful/did not throw an exception.</returns>
|
||||
public bool InvokeAndCatch<TDelegate>(
|
||||
TDelegate? eventHandler,
|
||||
string hint,
|
||||
params object[] args)
|
||||
where TDelegate : Delegate
|
||||
{
|
||||
if (eventHandler == null)
|
||||
return true;
|
||||
|
||||
try
|
||||
{
|
||||
var invoker = this.GetInvoker<TDelegate>();
|
||||
invoker(eventHandler, args);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"[{this.plugin.InternalName}] Exception in event handler {{EventHandlerName}}", hint);
|
||||
this.NotifyError();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show a notification, if the plugin is a dev plugin and the user has enabled error notifications.
|
||||
/// This function has a cooldown built-in.
|
||||
/// </summary>
|
||||
public void NotifyError()
|
||||
{
|
||||
if (this.plugin is not LocalDevPlugin devPlugin)
|
||||
return;
|
||||
|
||||
if (!devPlugin.NotifyForErrors)
|
||||
return;
|
||||
|
||||
// If the notification is already active, we don't need to show it again.
|
||||
if (this.activeNotification is { DismissReason: null })
|
||||
return;
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
if (now - this.lastErrorTime < TimeSpan.FromMinutes(2))
|
||||
return;
|
||||
|
||||
this.lastErrorTime = now;
|
||||
|
||||
var creatingErrorsText = $"{devPlugin.Name} is creating errors";
|
||||
var notification = new Notification()
|
||||
{
|
||||
Title = creatingErrorsText,
|
||||
Icon = INotificationIcon.From(FontAwesomeIcon.Bolt),
|
||||
Type = NotificationType.Error,
|
||||
InitialDuration = TimeSpan.FromSeconds(15),
|
||||
MinimizedText = creatingErrorsText,
|
||||
Content = $"The plugin '{devPlugin.Name}' is creating errors. Click 'Show console' to learn more.\n\n" +
|
||||
$"You are seeing this because '{devPlugin.Name}' is a Dev Plugin.",
|
||||
RespectUiHidden = false,
|
||||
};
|
||||
|
||||
this.activeNotification = this.notificationManager.AddNotification(notification);
|
||||
this.activeNotification.DrawActions += _ =>
|
||||
{
|
||||
if (ImGui.Button("Show console"))
|
||||
{
|
||||
this.di.OpenLogWindow(this.plugin.InternalName);
|
||||
this.activeNotification.DismissNow();
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip("Show the console filtered to this plugin");
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Disable notifications"))
|
||||
{
|
||||
devPlugin.NotifyForErrors = false;
|
||||
this.activeNotification.DismissNow();
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip("Disable error notifications for this plugin");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Action<TDelegate, object[]> CreateInvoker<TDelegate>() where TDelegate : Delegate
|
||||
{
|
||||
var delegateType = typeof(TDelegate);
|
||||
var method = delegateType.GetMethod("Invoke");
|
||||
if (method == null)
|
||||
throw new InvalidOperationException($"Delegate {delegateType} does not have an Invoke method.");
|
||||
|
||||
var parameters = method.GetParameters();
|
||||
|
||||
// Create parameters for the lambda
|
||||
var delegateParam = Expression.Parameter(delegateType, "d");
|
||||
var argsParam = Expression.Parameter(typeof(object[]), "args");
|
||||
|
||||
// Create expressions to convert array elements to parameter types
|
||||
var callArgs = new Expression[parameters.Length];
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
var paramType = parameters[i].ParameterType;
|
||||
var arrayAccess = Expression.ArrayIndex(argsParam, Expression.Constant(i));
|
||||
callArgs[i] = Expression.Convert(arrayAccess, paramType);
|
||||
}
|
||||
|
||||
// Create the delegate invocation expression
|
||||
var callExpr = Expression.Call(delegateParam, method, callArgs);
|
||||
|
||||
// If return type is not void, discard the result
|
||||
Expression bodyExpr;
|
||||
if (method.ReturnType != typeof(void))
|
||||
{
|
||||
// Create a block that executes the call and then returns void
|
||||
bodyExpr = Expression.Block(
|
||||
Expression.Call(delegateParam, method, callArgs),
|
||||
Expression.Empty());
|
||||
}
|
||||
else
|
||||
{
|
||||
bodyExpr = callExpr;
|
||||
}
|
||||
|
||||
// Compile and return the lambda
|
||||
var lambda = Expression.Lambda<Action<TDelegate, object[]>>(
|
||||
bodyExpr, delegateParam, argsParam);
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
private Action<TDelegate, object[]> GetInvoker<TDelegate>() where TDelegate : Delegate
|
||||
{
|
||||
var delegateType = typeof(TDelegate);
|
||||
|
||||
if (!this.invokerCache.TryGetValue(delegateType, out var cachedInvoker))
|
||||
{
|
||||
cachedInvoker = CreateInvoker<TDelegate>();
|
||||
this.invokerCache[delegateType] = cachedInvoker;
|
||||
}
|
||||
|
||||
return (Action<TDelegate, object[]>)cachedInvoker;
|
||||
}
|
||||
}
|
||||
|
|
@ -284,7 +284,7 @@ internal class PluginManager : IInternalDisposableService
|
|||
/// Check if a manifest even has an available testing version.
|
||||
/// </summary>
|
||||
/// <param name="manifest">The manifest to test.</param>
|
||||
/// <returns>Whether or not a testing version is available.</returns>
|
||||
/// <returns>Whether a testing version is available.</returns>
|
||||
public static bool HasTestingVersion(IPluginManifest manifest)
|
||||
{
|
||||
var av = manifest.AssemblyVersion;
|
||||
|
|
@ -663,6 +663,8 @@ internal class PluginManager : IInternalDisposableService
|
|||
_ = Task.Run(
|
||||
async () =>
|
||||
{
|
||||
Log.Verbose("Starting async boot load");
|
||||
|
||||
// Load plugins that want to be loaded during Framework.Tick
|
||||
var framework = await Service<Framework>.GetAsync().ConfigureAwait(false);
|
||||
await framework.RunOnTick(
|
||||
|
|
@ -671,27 +673,35 @@ internal class PluginManager : IInternalDisposableService
|
|||
syncPlugins.Where(def => def.Manifest?.LoadRequiredState == 1),
|
||||
tokenSource.Token),
|
||||
cancellationToken: tokenSource.Token).ConfigureAwait(false);
|
||||
Log.Verbose("Loaded FrameworkTickSync plugins (LoadRequiredState == 1)");
|
||||
|
||||
loadTasks.Add(LoadPluginsAsync(
|
||||
"FrameworkTickAsync",
|
||||
asyncPlugins.Where(def => def.Manifest?.LoadRequiredState == 1),
|
||||
tokenSource.Token));
|
||||
Log.Verbose("Kicked off FrameworkTickAsync plugins (LoadRequiredState == 1)");
|
||||
|
||||
// Load plugins that want to be loaded during Framework.Tick, when drawing facilities are available
|
||||
_ = await Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync().ConfigureAwait(false);
|
||||
Log.Verbose(" InterfaceManager is ready, starting to load DrawAvailableSync plugins");
|
||||
await framework.RunOnTick(
|
||||
() => LoadPluginsSync(
|
||||
"DrawAvailableSync",
|
||||
syncPlugins.Where(def => def.Manifest?.LoadRequiredState is 0 or null),
|
||||
tokenSource.Token),
|
||||
cancellationToken: tokenSource.Token);
|
||||
Log.Verbose("Loaded DrawAvailableSync plugins (LoadRequiredState == 0 or null)");
|
||||
|
||||
loadTasks.Add(LoadPluginsAsync(
|
||||
"DrawAvailableAsync",
|
||||
asyncPlugins.Where(def => def.Manifest?.LoadRequiredState is 0 or null),
|
||||
tokenSource.Token));
|
||||
Log.Verbose("Kicked off DrawAvailableAsync plugins (LoadRequiredState == 0 or null)");
|
||||
|
||||
// Save signatures when all plugins are done loading, successful or not.
|
||||
try
|
||||
{
|
||||
Log.Verbose("Now waiting for {NumTasks} async load tasks", loadTasks.Count);
|
||||
await Task.WhenAll(loadTasks).ConfigureAwait(false);
|
||||
Log.Information("Loaded plugins on boot");
|
||||
}
|
||||
|
|
@ -715,8 +725,13 @@ internal class PluginManager : IInternalDisposableService
|
|||
}
|
||||
|
||||
this.StartupLoadTracking = null;
|
||||
},
|
||||
tokenSource.Token);
|
||||
}, tokenSource.Token).ContinueWith(t =>
|
||||
{
|
||||
if (t.IsFaulted)
|
||||
{
|
||||
Log.Error(t.Exception, "Failed to load FrameworkTickAsync/DrawAvailableAsync plugins");
|
||||
}
|
||||
}, TaskContinuationOptions.OnlyOnFaulted);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -776,7 +791,8 @@ internal class PluginManager : IInternalDisposableService
|
|||
/// only shown as disabled in the installed plugins window. This is a modified version of LoadAllPlugins that works
|
||||
/// a little differently.
|
||||
/// </summary>
|
||||
public void ScanDevPlugins()
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation. This function generally will not block as new plugins aren't loaded.</returns>
|
||||
public async Task ScanDevPluginsAsync()
|
||||
{
|
||||
// devPlugins are more freeform. Look for any dll and hope to get lucky.
|
||||
var devDllFiles = new List<FileInfo>();
|
||||
|
|
@ -823,8 +839,7 @@ internal class PluginManager : IInternalDisposableService
|
|||
try
|
||||
{
|
||||
// Add them to the list and let the user decide, nothing is auto-loaded.
|
||||
this.LoadPluginAsync(dllFile, manifest, PluginLoadReason.Installer, isDev: true, doNotLoad: true)
|
||||
.Wait();
|
||||
await this.LoadPluginAsync(dllFile, manifest, PluginLoadReason.Installer, isDev: true, doNotLoad: true);
|
||||
listChanged = true;
|
||||
}
|
||||
catch (InvalidPluginException)
|
||||
|
|
@ -1037,7 +1052,7 @@ internal class PluginManager : IInternalDisposableService
|
|||
/// </summary>
|
||||
/// <param name="metadata">The available plugin update.</param>
|
||||
/// <param name="notify">Whether to notify that installed plugins have changed afterwards.</param>
|
||||
/// <param name="dryRun">Whether or not to actually perform the update, or just indicate success.</param>
|
||||
/// <param name="dryRun">Whether to actually perform the update, or just indicate success.</param>
|
||||
/// <returns>The status of the update.</returns>
|
||||
public async Task<PluginUpdateStatus> UpdateSinglePluginAsync(AvailablePluginUpdate metadata, bool notify, bool dryRun)
|
||||
{
|
||||
|
|
@ -1188,32 +1203,20 @@ internal class PluginManager : IInternalDisposableService
|
|||
{
|
||||
// Testing exclusive
|
||||
if (manifest.IsTestingExclusive && !this.configuration.DoPluginTest)
|
||||
{
|
||||
Log.Verbose($"Testing exclusivity: {manifest.InternalName} - {manifest.AssemblyVersion} - {manifest.TestingAssemblyVersion}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Applicable version
|
||||
if (manifest.ApplicableVersion < this.dalamud.StartInfo.GameVersion)
|
||||
{
|
||||
Log.Verbose($"Game version: {manifest.InternalName} - {manifest.AssemblyVersion} - {manifest.TestingAssemblyVersion}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// API level - we keep the API before this in the installer to show as "outdated"
|
||||
var effectiveApiLevel = this.UseTesting(manifest) && manifest.TestingDalamudApiLevel != null ? manifest.TestingDalamudApiLevel.Value : manifest.DalamudApiLevel;
|
||||
if (effectiveApiLevel < DalamudApiLevel - 1 && !this.LoadAllApiLevels)
|
||||
{
|
||||
Log.Verbose($"API Level: {manifest.InternalName} - {manifest.AssemblyVersion} - {manifest.TestingAssemblyVersion}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Banned
|
||||
if (this.IsManifestBanned(manifest))
|
||||
{
|
||||
Log.Verbose($"Banned: {manifest.InternalName} - {manifest.AssemblyVersion} - {manifest.TestingAssemblyVersion}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1572,6 +1575,8 @@ internal class PluginManager : IInternalDisposableService
|
|||
/// <returns>The loaded plugin.</returns>
|
||||
private async Task<LocalPlugin> LoadPluginAsync(FileInfo dllFile, LocalPluginManifest manifest, PluginLoadReason reason, bool isDev = false, bool isBoot = false, bool doNotLoad = false)
|
||||
{
|
||||
// TODO: Split this function - it should only take care of adding the plugin to the list, not loading itself, that should be done through the plugin instance
|
||||
|
||||
var loadPlugin = !doNotLoad;
|
||||
|
||||
LocalPlugin? plugin;
|
||||
|
|
@ -1582,21 +1587,34 @@ internal class PluginManager : IInternalDisposableService
|
|||
throw new Exception("No internal name");
|
||||
}
|
||||
|
||||
if (isDev)
|
||||
// Track the plugin as soon as it is instantiated to prevent it from being loaded twice,
|
||||
// if the installer or DevPlugin scanner is attempting to add plugins while we are still loading boot plugins
|
||||
lock (this.pluginListLock)
|
||||
{
|
||||
Log.Information("Loading dev plugin {Name}", manifest.InternalName);
|
||||
plugin = new LocalDevPlugin(dllFile, manifest);
|
||||
// Check if this plugin is already loaded
|
||||
if (this.installedPluginsList.Any(lp => lp.DllFile.FullName == dllFile.FullName))
|
||||
throw new InvalidOperationException("Plugin at the provided path is already loaded");
|
||||
|
||||
// This is a dev plugin - turn ImGui asserts on by default if we haven't chosen yet
|
||||
// TODO(goat): Re-enable this when we have better tracing for what was rendering when
|
||||
// this.configuration.ImGuiAssertsEnabledAtStartup ??= true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Information("Loading plugin {Name}", manifest.InternalName);
|
||||
plugin = new LocalPlugin(dllFile, manifest);
|
||||
if (isDev)
|
||||
{
|
||||
Log.Information("Loading dev plugin {Name}", manifest.InternalName);
|
||||
plugin = new LocalDevPlugin(dllFile, manifest);
|
||||
|
||||
// This is a dev plugin - turn ImGui asserts on by default if we haven't chosen yet
|
||||
// TODO(goat): Re-enable this when we have better tracing for what was rendering when
|
||||
// this.configuration.ImGuiAssertsEnabledAtStartup ??= true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Information("Loading plugin {Name}", manifest.InternalName);
|
||||
plugin = new LocalPlugin(dllFile, manifest);
|
||||
}
|
||||
|
||||
this.installedPluginsList.Add(plugin);
|
||||
}
|
||||
|
||||
Log.Verbose("Starting to load plugin {Name} at {FileLocation}", manifest.InternalName, dllFile.FullName);
|
||||
|
||||
// Perform a migration from InternalName to GUIDs. The plugin should definitely have a GUID here.
|
||||
// This will also happen if you are installing a plugin with the installer, and that's intended!
|
||||
// It means that, if you have a profile which has unsatisfied plugins, installing a matching plugin will
|
||||
|
|
@ -1697,43 +1715,34 @@ internal class PluginManager : IInternalDisposableService
|
|||
catch (BannedPluginException)
|
||||
{
|
||||
// Out of date plugins get added so they can be updated.
|
||||
Log.Information($"Plugin was banned, adding anyways: {dllFile.Name}");
|
||||
Log.Information("{InternalName}: Plugin was banned, adding anyways", plugin.Manifest.InternalName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (plugin.IsDev)
|
||||
{
|
||||
// Dev plugins always get added to the list so they can be fiddled with in the UI
|
||||
Log.Information(ex, $"Dev plugin failed to load, adding anyways: {dllFile.Name}");
|
||||
|
||||
// NOTE(goat): This can't work - plugins don't "unload" if they fail to load.
|
||||
// plugin.Disable(); // Disable here, otherwise you can't enable+load later
|
||||
Log.Information(ex, "{InternalName}: Dev plugin failed to load", plugin.Manifest.InternalName);
|
||||
}
|
||||
else if (plugin.IsOutdated)
|
||||
{
|
||||
// Out of date plugins get added, so they can be updated.
|
||||
Log.Information(ex, $"Plugin was outdated, adding anyways: {dllFile.Name}");
|
||||
Log.Information(ex, "{InternalName}: Plugin was outdated", plugin.Manifest.InternalName);
|
||||
}
|
||||
else if (plugin.IsOrphaned)
|
||||
{
|
||||
// Orphaned plugins get added, so that users aren't confused.
|
||||
Log.Information(ex, $"Plugin was orphaned, adding anyways: {dllFile.Name}");
|
||||
Log.Information(ex, "{InternalName}: Plugin was orphaned", plugin.Manifest.InternalName);
|
||||
}
|
||||
else if (isBoot)
|
||||
{
|
||||
// During boot load, plugins always get added to the list so they can be fiddled with in the UI
|
||||
Log.Information(ex, $"Regular plugin failed to load, adding anyways: {dllFile.Name}");
|
||||
|
||||
// NOTE(goat): This can't work - plugins don't "unload" if they fail to load.
|
||||
// plugin.Disable(); // Disable here, otherwise you can't enable+load later
|
||||
Log.Information(ex, "{InternalName}: Regular plugin failed to load", plugin.Manifest.InternalName);
|
||||
}
|
||||
else if (!plugin.CheckPolicy())
|
||||
{
|
||||
// During boot load, plugins always get added to the list so they can be fiddled with in the UI
|
||||
Log.Information(ex, $"Plugin not loaded due to policy, adding anyways: {dllFile.Name}");
|
||||
|
||||
// NOTE(goat): This can't work - plugins don't "unload" if they fail to load.
|
||||
// plugin.Disable(); // Disable here, otherwise you can't enable+load later
|
||||
Log.Information(ex, "{InternalName}: Plugin not loaded due to policy", plugin.Manifest.InternalName);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -1742,14 +1751,6 @@ internal class PluginManager : IInternalDisposableService
|
|||
}
|
||||
}
|
||||
|
||||
if (plugin == null)
|
||||
throw new Exception("Plugin was null when adding to list");
|
||||
|
||||
lock (this.pluginListLock)
|
||||
{
|
||||
this.installedPluginsList.Add(plugin);
|
||||
}
|
||||
|
||||
// Mark as finished loading
|
||||
if (manifest.LoadSync)
|
||||
this.StartupLoadTracking?.Finish(manifest.InternalName);
|
||||
|
|
@ -1774,6 +1775,7 @@ internal class PluginManager : IInternalDisposableService
|
|||
var updates = this.AvailablePlugins
|
||||
.Where(remoteManifest => plugin.Manifest.InternalName == remoteManifest.InternalName)
|
||||
.Where(remoteManifest => plugin.Manifest.InstalledFromUrl == remoteManifest.SourceRepo.PluginMasterUrl || !remoteManifest.SourceRepo.IsThirdParty)
|
||||
.Where(remoteManifest => remoteManifest.MinimumDalamudVersion == null || Util.AssemblyVersionParsed >= remoteManifest.MinimumDalamudVersion)
|
||||
.Where(remoteManifest =>
|
||||
{
|
||||
var useTesting = this.UseTesting(remoteManifest);
|
||||
|
|
@ -1844,18 +1846,27 @@ internal class PluginManager : IInternalDisposableService
|
|||
_ = this.SetPluginReposFromConfigAsync(false);
|
||||
this.OnInstalledPluginsChanged += () => Task.Run(Troubleshooting.LogTroubleshooting);
|
||||
|
||||
Log.Information("[T3] PM repos OK!");
|
||||
Log.Information("Repos loaded!");
|
||||
}
|
||||
|
||||
using (Timings.Start("PM Cleanup Plugins"))
|
||||
{
|
||||
this.CleanupPlugins();
|
||||
Log.Information("[T3] PMC OK!");
|
||||
Log.Information("Plugin cleanup OK!");
|
||||
}
|
||||
|
||||
using (Timings.Start("PM Load Sync Plugins"))
|
||||
{
|
||||
var loadAllPlugins = Task.Run(this.LoadAllPlugins);
|
||||
var loadAllPlugins = Task.Run(this.LoadAllPlugins)
|
||||
.ContinueWith(t =>
|
||||
{
|
||||
if (t.IsFaulted)
|
||||
{
|
||||
Log.Error(t.Exception, "Error in LoadAllPlugins()");
|
||||
}
|
||||
|
||||
_ = Task.Run(Troubleshooting.LogTroubleshooting);
|
||||
});
|
||||
|
||||
// We wait for all blocking services and tasks to finish before kicking off the main thread in any mode.
|
||||
// This means that we don't want to block here if this stupid thing isn't enabled.
|
||||
|
|
@ -1865,10 +1876,8 @@ internal class PluginManager : IInternalDisposableService
|
|||
loadAllPlugins.Wait();
|
||||
}
|
||||
|
||||
Log.Information("[T3] PML OK!");
|
||||
Log.Information("Boot load started");
|
||||
}
|
||||
|
||||
_ = Task.Run(Troubleshooting.LogTroubleshooting);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ internal class Profile
|
|||
/// </summary>
|
||||
/// <param name="manager">The manager this profile belongs to.</param>
|
||||
/// <param name="model">The model this profile is tied to.</param>
|
||||
/// <param name="isDefaultProfile">Whether or not this profile is the default profile.</param>
|
||||
/// <param name="isBoot">Whether or not this profile was initialized during bootup.</param>
|
||||
/// <param name="isDefaultProfile">Whether this profile is the default profile.</param>
|
||||
/// <param name="isBoot">Whether this profile was initialized during bootup.</param>
|
||||
public Profile(ProfileManager manager, ProfileModel model, bool isDefaultProfile, bool isBoot)
|
||||
{
|
||||
this.manager = manager;
|
||||
|
|
@ -33,6 +33,18 @@ internal class Profile
|
|||
this.modelV1 = model as ProfileModelV1 ??
|
||||
throw new ArgumentException("Model was null or unhandled version");
|
||||
|
||||
// Migrate "policy"
|
||||
if (this.modelV1.StartupPolicy == null)
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
this.modelV1.StartupPolicy = this.modelV1.AlwaysEnableOnBoot
|
||||
? ProfileModelV1.ProfileStartupPolicy.AlwaysEnable
|
||||
: ProfileModelV1.ProfileStartupPolicy.RememberState;
|
||||
#pragma warning restore CS0618
|
||||
|
||||
Service<DalamudConfiguration>.Get().QueueSave();
|
||||
}
|
||||
|
||||
// We don't actually enable plugins here, PM will do it on bootup
|
||||
if (isDefaultProfile)
|
||||
{
|
||||
|
|
@ -40,20 +52,40 @@ internal class Profile
|
|||
this.IsEnabled = this.modelV1.IsEnabled = true;
|
||||
this.Name = this.modelV1.Name = "DEFAULT";
|
||||
}
|
||||
else if (this.modelV1.AlwaysEnableOnBoot && isBoot)
|
||||
else if (isBoot)
|
||||
{
|
||||
this.IsEnabled = true;
|
||||
Log.Verbose("{Guid} set enabled because bootup", this.modelV1.Guid);
|
||||
}
|
||||
else if (this.modelV1.IsEnabled)
|
||||
{
|
||||
this.IsEnabled = true;
|
||||
Log.Verbose("{Guid} set enabled because remember", this.modelV1.Guid);
|
||||
if (this.modelV1.StartupPolicy == ProfileModelV1.ProfileStartupPolicy.AlwaysEnable)
|
||||
{
|
||||
this.IsEnabled = true;
|
||||
Log.Verbose("{Guid} set enabled because always enable", this.modelV1.Guid);
|
||||
}
|
||||
else if (this.modelV1.StartupPolicy == ProfileModelV1.ProfileStartupPolicy.AlwaysDisable)
|
||||
{
|
||||
this.IsEnabled = false;
|
||||
Log.Verbose("{Guid} set disabled because always disable", this.modelV1.Guid);
|
||||
}
|
||||
else if (this.modelV1.StartupPolicy == ProfileModelV1.ProfileStartupPolicy.RememberState)
|
||||
{
|
||||
this.IsEnabled = this.modelV1.IsEnabled;
|
||||
Log.Verbose("{Guid} set enabled because remember", this.modelV1.Guid);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(this.modelV1.StartupPolicy));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Verbose("{Guid} not enabled", this.modelV1.Guid);
|
||||
}
|
||||
|
||||
Log.Verbose("Init profile {Guid} ({Name}) enabled:{Enabled} policy:{Policy} plugins:{NumPlugins} will be enabled:{Status}",
|
||||
this.modelV1.Guid,
|
||||
this.modelV1.Name,
|
||||
this.modelV1.IsEnabled,
|
||||
this.modelV1.StartupPolicy,
|
||||
this.modelV1.Plugins.Count,
|
||||
this.IsEnabled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -72,12 +104,12 @@ internal class Profile
|
|||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this profile shall always be enabled at boot.
|
||||
/// </summary>
|
||||
public bool AlwaysEnableAtBoot
|
||||
public ProfileModelV1.ProfileStartupPolicy StartupPolicy
|
||||
{
|
||||
get => this.modelV1.AlwaysEnableOnBoot;
|
||||
get => this.modelV1.StartupPolicy ?? ProfileModelV1.ProfileStartupPolicy.RememberState;
|
||||
set
|
||||
{
|
||||
this.modelV1.AlwaysEnableOnBoot = value;
|
||||
this.modelV1.StartupPolicy = value;
|
||||
Service<DalamudConfiguration>.Get().QueueSave();
|
||||
}
|
||||
}
|
||||
|
|
@ -88,12 +120,12 @@ internal class Profile
|
|||
public Guid Guid => this.modelV1.Guid;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this profile is currently enabled.
|
||||
/// Gets a value indicating whether this profile is currently enabled.
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this profile is the default profile.
|
||||
/// Gets a value indicating whether this profile is the default profile.
|
||||
/// </summary>
|
||||
public bool IsDefaultProfile { get; }
|
||||
|
||||
|
|
@ -119,8 +151,8 @@ internal class Profile
|
|||
/// Set this profile's state. This cannot be called for the default profile.
|
||||
/// This will block until all states have been applied.
|
||||
/// </summary>
|
||||
/// <param name="enabled">Whether or not the profile is enabled.</param>
|
||||
/// <param name="apply">Whether or not the current state should immediately be applied.</param>
|
||||
/// <param name="enabled">Whether the profile is enabled.</param>
|
||||
/// <param name="apply">Whether the current state should immediately be applied.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown when an untoggleable profile is toggled.</exception>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task SetStateAsync(bool enabled, bool apply = true)
|
||||
|
|
@ -158,13 +190,13 @@ internal class Profile
|
|||
/// </summary>
|
||||
/// <param name="workingPluginId">The ID of the plugin.</param>
|
||||
/// <param name="internalName">The internal name of the plugin, if available.</param>
|
||||
/// <param name="state">Whether or not the plugin should be enabled.</param>
|
||||
/// <param name="apply">Whether or not the current state should immediately be applied.</param>
|
||||
/// <param name="state">Whether the plugin should be enabled.</param>
|
||||
/// <param name="apply">Whether the current state should immediately be applied.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task AddOrUpdateAsync(Guid workingPluginId, string? internalName, bool state, bool apply = true)
|
||||
{
|
||||
Debug.Assert(workingPluginId != Guid.Empty, "Trying to add plugin with empty guid");
|
||||
|
||||
|
||||
lock (this)
|
||||
{
|
||||
var existing = this.modelV1.Plugins.FirstOrDefault(x => x.WorkingPluginId == workingPluginId);
|
||||
|
|
@ -182,9 +214,9 @@ internal class Profile
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Log.Information("Adding plugin {Plugin}({Guid}) to profile {Profile} with state {State}", internalName, workingPluginId, this.Guid, state);
|
||||
|
||||
|
||||
// We need to remove this plugin from the default profile, if it declares it.
|
||||
if (!this.IsDefaultProfile && this.manager.DefaultProfile.WantsPlugin(workingPluginId) != null)
|
||||
{
|
||||
|
|
@ -203,9 +235,9 @@ internal class Profile
|
|||
/// This will block until all states have been applied.
|
||||
/// </summary>
|
||||
/// <param name="workingPluginId">The ID of the plugin.</param>
|
||||
/// <param name="apply">Whether or not the current state should immediately be applied.</param>
|
||||
/// <param name="apply">Whether the current state should immediately be applied.</param>
|
||||
/// <param name="checkDefault">
|
||||
/// Whether or not to throw when a plugin is removed from the default profile, without being in another profile.
|
||||
/// Whether to throw when a plugin is removed from the default profile, without being in another profile.
|
||||
/// Used to prevent orphan plugins, but can be ignored when cleaning up old entries.
|
||||
/// </param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
|
|
@ -221,7 +253,7 @@ internal class Profile
|
|||
if (!this.modelV1.Plugins.Remove(entry))
|
||||
throw new Exception("Couldn't remove plugin from model collection");
|
||||
}
|
||||
|
||||
|
||||
Log.Information("Removing plugin {Plugin}({Guid}) from profile {Profile}", entry.InternalName, entry.WorkingPluginId, this.Guid);
|
||||
|
||||
// We need to add this plugin back to the default profile, if we were the last profile to have it.
|
||||
|
|
@ -260,7 +292,7 @@ internal class Profile
|
|||
// TODO: What should happen if a profile has a GUID locked in, but the plugin
|
||||
// is not installed anymore? That probably means that the user uninstalled the plugin
|
||||
// and is now reinstalling it. We should still satisfy that and update the ID.
|
||||
|
||||
|
||||
if (plugin.InternalName == internalName && plugin.WorkingPluginId == Guid.Empty)
|
||||
{
|
||||
plugin.WorkingPluginId = newGuid;
|
||||
|
|
@ -268,7 +300,7 @@ internal class Profile
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Service<DalamudConfiguration>.Get().QueueSave();
|
||||
}
|
||||
|
||||
|
|
@ -319,7 +351,7 @@ internal sealed class PluginNotFoundException : ProfileOperationException
|
|||
: base($"The plugin '{internalName}' was not found in the profile")
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PluginNotFoundException"/> class.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ internal class ProfileManager : IServiceType
|
|||
public IEnumerable<Profile> Profiles => this.profiles;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not the profile manager is busy enabling/disabling plugins.
|
||||
/// Gets a value indicating whether the profile manager is busy enabling/disabling plugins.
|
||||
/// </summary>
|
||||
public bool IsBusy => this.isBusy;
|
||||
|
||||
|
|
@ -71,8 +71,8 @@ internal class ProfileManager : IServiceType
|
|||
/// <param name="workingPluginId">The ID of the plugin.</param>
|
||||
/// <param name="internalName">The internal name of the plugin, if available.</param>
|
||||
/// <param name="defaultState">The state the plugin shall be in, if it needs to be added.</param>
|
||||
/// <param name="addIfNotDeclared">Whether or not the plugin should be added to the default preset, if it's not present in any preset.</param>
|
||||
/// <returns>Whether or not the plugin shall be enabled.</returns>
|
||||
/// <param name="addIfNotDeclared">Whether the plugin should be added to the default preset, if it's not present in any preset.</param>
|
||||
/// <returns>Whether the plugin shall be enabled.</returns>
|
||||
public async Task<bool> GetWantStateAsync(Guid workingPluginId, string? internalName, bool defaultState, bool addIfNotDeclared = true)
|
||||
{
|
||||
var want = false;
|
||||
|
|
@ -106,7 +106,7 @@ internal class ProfileManager : IServiceType
|
|||
/// Check whether a plugin is declared in any profile.
|
||||
/// </summary>
|
||||
/// <param name="workingPluginId">The ID of the plugin.</param>
|
||||
/// <returns>Whether or not the plugin is in any profile.</returns>
|
||||
/// <returns>Whether the plugin is in any profile.</returns>
|
||||
public bool IsInAnyProfile(Guid workingPluginId)
|
||||
{
|
||||
lock (this.profiles)
|
||||
|
|
@ -118,7 +118,7 @@ internal class ProfileManager : IServiceType
|
|||
/// A plugin can never be in the default profile if it is in any other profile.
|
||||
/// </summary>
|
||||
/// <param name="workingPluginId">The ID of the plugin.</param>
|
||||
/// <returns>Whether or not the plugin is in the default profile.</returns>
|
||||
/// <returns>Whether the plugin is in the default profile.</returns>
|
||||
public bool IsInDefaultProfile(Guid workingPluginId)
|
||||
=> this.DefaultProfile.WantsPlugin(workingPluginId) != null;
|
||||
|
||||
|
|
@ -193,6 +193,10 @@ internal class ProfileManager : IServiceType
|
|||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Unsupported profile model version");
|
||||
}
|
||||
|
||||
this.config.SavedProfiles!.Add(newModel);
|
||||
this.config.QueueSave();
|
||||
|
|
|
|||
|
|
@ -9,19 +9,47 @@ namespace Dalamud.Plugin.Internal.Profiles;
|
|||
/// </summary>
|
||||
public class ProfileModelV1 : ProfileModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum representing the startup policy of a profile.
|
||||
/// </summary>
|
||||
public enum ProfileStartupPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Remember the last state of the profile.
|
||||
/// </summary>
|
||||
RememberState,
|
||||
|
||||
/// <summary>
|
||||
/// Always enable the profile.
|
||||
/// </summary>
|
||||
AlwaysEnable,
|
||||
|
||||
/// <summary>
|
||||
/// Always disable the profile.
|
||||
/// </summary>
|
||||
AlwaysDisable,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the prefix of this version.
|
||||
/// </summary>
|
||||
public static string SerializedPrefix => "DP1";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not this profile should always be enabled at boot.
|
||||
/// Gets or sets a value indicating whether this profile should always be enabled at boot.
|
||||
/// </summary>
|
||||
[JsonProperty("b")]
|
||||
[Obsolete("Superseded by StartupPolicy")]
|
||||
public bool AlwaysEnableOnBoot { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not this profile is currently enabled.
|
||||
/// Gets or sets the policy to use when Dalamud is loading.
|
||||
/// </summary>
|
||||
[JsonProperty("p")]
|
||||
public ProfileStartupPolicy? StartupPolicy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this profile is currently enabled.
|
||||
/// </summary>
|
||||
[JsonProperty("e")]
|
||||
public bool IsEnabled { get; set; } = false;
|
||||
|
|
@ -46,14 +74,14 @@ public class ProfileModelV1 : ProfileModel
|
|||
/// Gets or sets the internal name of the plugin.
|
||||
/// </summary>
|
||||
public string? InternalName { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an ID uniquely identifying this specific instance of a plugin.
|
||||
/// </summary>
|
||||
public Guid WorkingPluginId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not this entry is enabled.
|
||||
/// Gets or sets a value indicating whether this entry is enabled.
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ internal class ProfilePluginEntry
|
|||
/// </summary>
|
||||
/// <param name="internalName">The internal name of the plugin.</param>
|
||||
/// <param name="workingPluginId">The ID of the plugin.</param>
|
||||
/// <param name="state">A value indicating whether or not this entry is enabled.</param>
|
||||
/// <param name="state">A value indicating whether this entry is enabled.</param>
|
||||
public ProfilePluginEntry(string internalName, Guid workingPluginId, bool state)
|
||||
{
|
||||
this.InternalName = internalName;
|
||||
|
|
@ -22,14 +22,14 @@ internal class ProfilePluginEntry
|
|||
/// Gets the internal name of the plugin.
|
||||
/// </summary>
|
||||
public string InternalName { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an ID uniquely identifying this specific instance of a plugin.
|
||||
/// </summary>
|
||||
public Guid WorkingPluginId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this entry is enabled.
|
||||
/// Gets a value indicating whether this entry is enabled.
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Dalamud.Plugin.Internal.Types;
|
|||
/// This class represents a dev plugin and all facets of its lifecycle.
|
||||
/// The DLL on disk, dependencies, loaded assembly, etc.
|
||||
/// </summary>
|
||||
internal class LocalDevPlugin : LocalPlugin
|
||||
internal sealed class LocalDevPlugin : LocalPlugin
|
||||
{
|
||||
private static readonly ModuleLog Log = new("PLUGIN");
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ internal class LocalDevPlugin : LocalPlugin
|
|||
configuration.DevPluginSettings[dllFile.FullName] = this.devSettings = new DevPluginSettings();
|
||||
configuration.QueueSave();
|
||||
}
|
||||
|
||||
|
||||
// Legacy dev plugins might not have this!
|
||||
if (this.devSettings.WorkingPluginId == Guid.Empty)
|
||||
{
|
||||
|
|
@ -85,7 +85,17 @@ internal class LocalDevPlugin : LocalPlugin
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether users should be notified when this plugin
|
||||
/// is causing errors.
|
||||
/// </summary>
|
||||
public bool NotifyForErrors
|
||||
{
|
||||
get => this.devSettings.NotifyForErrors;
|
||||
set => this.devSettings.NotifyForErrors = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an ID uniquely identifying this specific instance of a devPlugin.
|
||||
/// </summary>
|
||||
|
|
@ -152,7 +162,7 @@ internal class LocalDevPlugin : LocalPlugin
|
|||
if (manifestPath.Exists)
|
||||
this.manifest = LocalPluginManifest.Load(manifestPath) ?? throw new Exception("Could not reload manifest.");
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnPreReload()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ using Dalamud.Plugin.Internal.Exceptions;
|
|||
using Dalamud.Plugin.Internal.Loader;
|
||||
using Dalamud.Plugin.Internal.Profiles;
|
||||
using Dalamud.Plugin.Internal.Types.Manifest;
|
||||
using Dalamud.Utility;
|
||||
|
||||
namespace Dalamud.Plugin.Internal.Types;
|
||||
|
||||
|
|
@ -62,12 +63,13 @@ internal class LocalPlugin : IAsyncDisposable
|
|||
}
|
||||
|
||||
this.DllFile = dllFile;
|
||||
this.State = PluginState.Unloaded;
|
||||
|
||||
// Although it is conditionally used here, we need to set the initial value regardless.
|
||||
this.manifestFile = LocalPluginManifest.GetManifestFile(this.DllFile);
|
||||
this.manifest = manifest;
|
||||
|
||||
this.State = PluginState.Unloaded;
|
||||
|
||||
var needsSaveDueToLegacyFiles = false;
|
||||
|
||||
// This converts from the ".disabled" file feature to the manifest instead.
|
||||
|
|
@ -177,13 +179,13 @@ internal class LocalPlugin : IAsyncDisposable
|
|||
public bool IsTesting => this.manifest.IsTestingExclusive || this.manifest.Testing;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this plugin is orphaned(belongs to a repo) or not.
|
||||
/// Gets a value indicating whether this plugin is orphaned(belongs to a repo) or not.
|
||||
/// </summary>
|
||||
public bool IsOrphaned => !this.IsDev &&
|
||||
this.GetSourceRepository() == null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not this plugin is serviced(repo still exists, but plugin no longer does).
|
||||
/// Gets a value indicating whether this plugin is serviced(repo still exists, but plugin no longer does).
|
||||
/// </summary>
|
||||
public bool IsDecommissioned => !this.IsDev &&
|
||||
this.GetSourceRepository()?.State == PluginRepositoryState.Success &&
|
||||
|
|
@ -312,6 +314,9 @@ internal class LocalPlugin : IAsyncDisposable
|
|||
if (!this.CheckPolicy())
|
||||
throw new PluginPreconditionFailedException($"Unable to load {this.Name} as a load policy forbids it");
|
||||
|
||||
if (this.Manifest.MinimumDalamudVersion != null && this.Manifest.MinimumDalamudVersion > Util.AssemblyVersionParsed)
|
||||
throw new PluginPreconditionFailedException($"Unable to load {this.Name}, Dalamud version is lower than minimum required version {this.Manifest.MinimumDalamudVersion}");
|
||||
|
||||
this.State = PluginState.Loading;
|
||||
Log.Information($"Loading {this.DllFile.Name}");
|
||||
|
||||
|
|
@ -352,19 +357,13 @@ internal class LocalPlugin : IAsyncDisposable
|
|||
}
|
||||
|
||||
this.loader.Reload();
|
||||
this.RefreshAssemblyInformation();
|
||||
}
|
||||
|
||||
// Load the assembly
|
||||
this.pluginAssembly ??= this.loader.LoadDefaultAssembly();
|
||||
|
||||
this.AssemblyName = this.pluginAssembly.GetName();
|
||||
|
||||
// Find the plugin interface implementation. It is guaranteed to exist after checking in the ctor.
|
||||
this.pluginType ??= this.pluginAssembly.GetTypes()
|
||||
.First(type => type.IsAssignableTo(typeof(IDalamudPlugin)));
|
||||
Log.Verbose("{Name} ({Guid}): Have type", this.InternalName, this.EffectiveWorkingPluginId);
|
||||
|
||||
// Check for any loaded plugins with the same assembly name
|
||||
var assemblyName = this.pluginAssembly.GetName().Name;
|
||||
var assemblyName = this.pluginAssembly!.GetName().Name;
|
||||
foreach (var otherPlugin in pluginManager.InstalledPlugins)
|
||||
{
|
||||
// During hot-reloading, this plugin will be in the plugin list, and the instance will have been disposed
|
||||
|
|
@ -376,7 +375,7 @@ internal class LocalPlugin : IAsyncDisposable
|
|||
if (otherPluginAssemblyName == assemblyName && otherPluginAssemblyName != null)
|
||||
{
|
||||
this.State = PluginState.Unloaded;
|
||||
Log.Debug($"Duplicate assembly: {this.Name}");
|
||||
Log.Debug("Duplicate assembly: {Name}", this.InternalName);
|
||||
|
||||
throw new DuplicatePluginException(assemblyName);
|
||||
}
|
||||
|
|
@ -392,7 +391,7 @@ internal class LocalPlugin : IAsyncDisposable
|
|||
this.instance = await CreatePluginInstance(
|
||||
this.manifest,
|
||||
this.serviceScope,
|
||||
this.pluginType,
|
||||
this.pluginType!,
|
||||
this.dalamudInterface);
|
||||
this.State = PluginState.Loaded;
|
||||
Log.Information("Finished loading {PluginName}", this.InternalName);
|
||||
|
|
@ -504,7 +503,7 @@ internal class LocalPlugin : IAsyncDisposable
|
|||
/// <summary>
|
||||
/// Check if anything forbids this plugin from loading.
|
||||
/// </summary>
|
||||
/// <returns>Whether or not this plugin shouldn't load.</returns>
|
||||
/// <returns>Whether this plugin shouldn't load.</returns>
|
||||
public bool CheckPolicy()
|
||||
{
|
||||
var startInfo = Service<Dalamud>.Get().StartInfo;
|
||||
|
|
@ -578,7 +577,7 @@ internal class LocalPlugin : IAsyncDisposable
|
|||
var newInstanceTask = forceFrameworkThread ? framework.RunOnFrameworkThread(Create) : Create();
|
||||
return await newInstanceTask.ConfigureAwait(false);
|
||||
|
||||
async Task<IDalamudPlugin> Create() => (IDalamudPlugin)await scope.CreateAsync(type, dalamudInterface);
|
||||
async Task<IDalamudPlugin> Create() => (IDalamudPlugin)await scope.CreateAsync(type, ObjectInstanceVisibility.ExposedToPlugins, dalamudInterface);
|
||||
}
|
||||
|
||||
private static void SetupLoaderConfig(LoaderConfig config)
|
||||
|
|
@ -620,42 +619,60 @@ internal class LocalPlugin : IAsyncDisposable
|
|||
throw;
|
||||
}
|
||||
|
||||
this.RefreshAssemblyInformation();
|
||||
}
|
||||
|
||||
private void RefreshAssemblyInformation()
|
||||
{
|
||||
if (this.loader == null)
|
||||
throw new InvalidOperationException("No loader available");
|
||||
|
||||
try
|
||||
{
|
||||
this.pluginAssembly = this.loader.LoadDefaultAssembly();
|
||||
this.AssemblyName = this.pluginAssembly.GetName();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.pluginAssembly = null;
|
||||
this.pluginType = null;
|
||||
this.loader.Dispose();
|
||||
|
||||
this.ResetLoader();
|
||||
Log.Error(ex, $"Not a plugin: {this.DllFile.FullName}");
|
||||
throw new InvalidPluginException(this.DllFile);
|
||||
}
|
||||
|
||||
if (this.pluginAssembly == null)
|
||||
{
|
||||
this.ResetLoader();
|
||||
Log.Error("Plugin assembly is null: {DllFileFullName}", this.DllFile.FullName);
|
||||
throw new InvalidPluginException(this.DllFile);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.pluginType = this.pluginAssembly.GetTypes().FirstOrDefault(type => type.IsAssignableTo(typeof(IDalamudPlugin)));
|
||||
}
|
||||
catch (ReflectionTypeLoadException ex)
|
||||
{
|
||||
Log.Error(ex, $"Could not load one or more types when searching for IDalamudPlugin: {this.DllFile.FullName}");
|
||||
// Something blew up when parsing types, but we still want to look for IDalamudPlugin. Let Load() handle the error.
|
||||
this.pluginType = ex.Types.FirstOrDefault(type => type != null && type.IsAssignableTo(typeof(IDalamudPlugin)));
|
||||
this.ResetLoader();
|
||||
Log.Error(ex, "Could not load one or more types when searching for IDalamudPlugin: {DllFileFullName}", this.DllFile.FullName);
|
||||
throw;
|
||||
}
|
||||
|
||||
if (this.pluginType == default)
|
||||
if (this.pluginType == null)
|
||||
{
|
||||
this.pluginAssembly = null;
|
||||
this.pluginType = null;
|
||||
this.loader.Dispose();
|
||||
|
||||
Log.Error($"Nothing inherits from IDalamudPlugin: {this.DllFile.FullName}");
|
||||
this.ResetLoader();
|
||||
Log.Error("Nothing inherits from IDalamudPlugin: {DllFileFullName}", this.DllFile.FullName);
|
||||
throw new InvalidPluginException(this.DllFile);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetLoader()
|
||||
{
|
||||
this.pluginAssembly = null;
|
||||
this.pluginType = null;
|
||||
this.loader?.Dispose();
|
||||
this.loader = null;
|
||||
}
|
||||
|
||||
/// <summary>Clears and disposes all resources associated with the plugin instance.</summary>
|
||||
/// <param name="disposalMode">Whether to clear and dispose <see cref="loader"/>.</param>
|
||||
/// <returns>Exceptions, if any occurred.</returns>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public interface IPluginManifest
|
|||
/// Gets the public name of the plugin.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a punchline of the plugins functions.
|
||||
/// </summary>
|
||||
|
|
@ -26,7 +26,7 @@ public interface IPluginManifest
|
|||
/// Gets the author/s of the plugin.
|
||||
/// </summary>
|
||||
public string Author { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the plugin can be unloaded asynchronously.
|
||||
/// </summary>
|
||||
|
|
@ -41,17 +41,22 @@ public interface IPluginManifest
|
|||
/// Gets the assembly version of the plugin's testing variant.
|
||||
/// </summary>
|
||||
public Version? TestingAssemblyVersion { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum Dalamud assembly version this plugin requires.
|
||||
/// </summary>
|
||||
public Version? MinimumDalamudVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DIP17 channel name.
|
||||
/// </summary>
|
||||
public string? Dip17Channel { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last time this plugin was updated.
|
||||
/// </summary>
|
||||
public long LastUpdate { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a changelog, null if none exists.
|
||||
/// </summary>
|
||||
|
|
@ -88,7 +93,7 @@ public interface IPluginManifest
|
|||
/// Gets an URL to the website or source code of the plugin.
|
||||
/// </summary>
|
||||
public string? RepoUrl { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a description of the plugins functions.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ internal record PluginManifest : IPluginManifest
|
|||
public List<string>? CategoryTags { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the plugin is hidden in the plugin installer.
|
||||
/// Gets or sets a value indicating whether the plugin is hidden in the plugin installer.
|
||||
/// This value comes from the plugin master and is in addition to the list of hidden names kept by Dalamud.
|
||||
/// </summary>
|
||||
[JsonProperty]
|
||||
|
|
@ -75,6 +75,10 @@ internal record PluginManifest : IPluginManifest
|
|||
[JsonConverter(typeof(GameVersionConverter))]
|
||||
public GameVersion? ApplicableVersion { get; init; } = GameVersion.Any;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonProperty]
|
||||
public Version? MinimumDalamudVersion { get; init; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonProperty]
|
||||
public int DalamudApiLevel { get; init; } = PluginManager.DalamudApiLevel;
|
||||
|
|
|
|||
|
|
@ -13,8 +13,16 @@ public interface IAddonEventManager
|
|||
/// <param name="atkEventType">Event type for this event handler.</param>
|
||||
/// <param name="atkUnitBase">The parent addon for this event handler.</param>
|
||||
/// <param name="atkResNode">The specific node that will trigger this event handler.</param>
|
||||
[Obsolete("Use AddonEventDelegate instead")]
|
||||
public delegate void AddonEventHandler(AddonEventType atkEventType, nint atkUnitBase, nint atkResNode);
|
||||
|
||||
/// <summary>
|
||||
/// Delegate to be called when an event is received.
|
||||
/// </summary>
|
||||
/// <param name="atkEventType">The AtkEventType that triggered this event.</param>
|
||||
/// <param name="data">The event data object for use in handling this event.</param>
|
||||
public delegate void AddonEventDelegate(AddonEventType atkEventType, AddonEventData data);
|
||||
|
||||
/// <summary>
|
||||
/// Registers an event handler for the specified addon, node, and type.
|
||||
/// </summary>
|
||||
|
|
@ -23,8 +31,19 @@ public interface IAddonEventManager
|
|||
/// <param name="eventType">The event type for this event.</param>
|
||||
/// <param name="eventHandler">The handler to call when event is triggered.</param>
|
||||
/// <returns>IAddonEventHandle used to remove the event. Null if no event was added.</returns>
|
||||
[Obsolete("Use AddEvent with AddonEventDelegate instead of AddonEventHandler")]
|
||||
IAddonEventHandle? AddEvent(nint atkUnitBase, nint atkResNode, AddonEventType eventType, AddonEventHandler eventHandler);
|
||||
|
||||
/// <summary>
|
||||
/// Registers an event handler for the specified addon, node, and type.
|
||||
/// </summary>
|
||||
/// <param name="atkUnitBase">The parent addon for this event.</param>
|
||||
/// <param name="atkResNode">The node that will trigger this event.</param>
|
||||
/// <param name="eventType">The event type for this event.</param>
|
||||
/// <param name="eventDelegate">The handler to call when event is triggered.</param>
|
||||
/// <returns>IAddonEventHandle used to remove the event. Null if no event was added.</returns>
|
||||
IAddonEventHandle? AddEvent(nint atkUnitBase, nint atkResNode, AddonEventType eventType, AddonEventDelegate eventDelegate);
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters an event handler with the specified event id and event type.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ public interface IClientState
|
|||
/// Gets the current Territory the player resides in.
|
||||
/// </summary>
|
||||
public ushort TerritoryType { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current Map the player resides in.
|
||||
/// </summary>
|
||||
|
|
@ -101,17 +101,17 @@ public interface IClientState
|
|||
public bool IsLoggedIn { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not the user is playing PvP.
|
||||
/// Gets a value indicating whether the user is playing PvP.
|
||||
/// </summary>
|
||||
public bool IsPvP { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not the user is playing PvP, excluding the Wolves' Den.
|
||||
/// Gets a value indicating whether the user is playing PvP, excluding the Wolves' Den.
|
||||
/// </summary>
|
||||
public bool IsPvPExcludingDen { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the client is currently in Group Pose (GPose) mode.
|
||||
/// Gets a value indicating whether the client is currently in Group Pose (GPose) mode.
|
||||
/// </summary>
|
||||
public bool IsGPosing { get; }
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,15 @@ public interface ISeStringEvaluator
|
|||
/// <returns>An evaluated <see cref="ReadOnlySeString"/>.</returns>
|
||||
ReadOnlySeString Evaluate(ReadOnlySeStringSpan str, Span<SeStringParameter> localParameters = default, ClientLanguage? language = null);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates macros in a macro string.
|
||||
/// </summary>
|
||||
/// <param name="macroString">The macro string.</param>
|
||||
/// <param name="localParameters">An optional list of local parameters.</param>
|
||||
/// <param name="language">An optional language override.</param>
|
||||
/// <returns>An evaluated <see cref="ReadOnlySeString"/>.</returns>
|
||||
ReadOnlySeString EvaluateMacroString(string macroString, Span<SeStringParameter> localParameters = default, ClientLanguage? language = null);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates macros in text from the Addon sheet.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ namespace Dalamud.Game;
|
|||
public interface ISigScanner
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not the search on this module is performed on a copy.
|
||||
/// Gets a value indicating whether the search on this module is performed on a copy.
|
||||
/// </summary>
|
||||
public bool IsCopy { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not the ProcessModule is 32-bit.
|
||||
/// Gets a value indicating whether the ProcessModule is 32-bit.
|
||||
/// </summary>
|
||||
public bool Is32BitProcess { get; }
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ public interface ISigScanner
|
|||
/// <param name="offset">The offset from function start of the instruction using the data.</param>
|
||||
/// <returns>An IntPtr to the static memory location.</returns>
|
||||
public nint GetStaticAddressFromSig(string signature, int offset = 0);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Try scanning for a .data address using a .text function.
|
||||
/// This is intended to be used with IDA sigs.
|
||||
|
|
@ -95,14 +95,14 @@ public interface ISigScanner
|
|||
/// <param name="offset">The offset from function start of the instruction using the data.</param>
|
||||
/// <returns>true if the signature was found.</returns>
|
||||
public bool TryGetStaticAddressFromSig(string signature, out nint result, int offset = 0);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Scan for a byte signature in the .data section.
|
||||
/// </summary>
|
||||
/// <param name="signature">The signature.</param>
|
||||
/// <returns>The real offset of the found signature.</returns>
|
||||
public nint ScanData(string signature);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Try scanning for a byte signature in the .data section.
|
||||
/// </summary>
|
||||
|
|
@ -110,14 +110,14 @@ public interface ISigScanner
|
|||
/// <param name="result">The real offset of the signature, if found.</param>
|
||||
/// <returns>true if the signature was found.</returns>
|
||||
public bool TryScanData(string signature, out nint result);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Scan for a byte signature in the whole module search area.
|
||||
/// </summary>
|
||||
/// <param name="signature">The signature.</param>
|
||||
/// <returns>The real offset of the found signature.</returns>
|
||||
public nint ScanModule(string signature);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Try scanning for a byte signature in the whole module search area.
|
||||
/// </summary>
|
||||
|
|
@ -125,7 +125,7 @@ public interface ISigScanner
|
|||
/// <param name="result">The real offset of the signature, if found.</param>
|
||||
/// <returns>true if the signature was found.</returns>
|
||||
public bool TryScanModule(string signature, out nint result);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Resolve a RVA address.
|
||||
/// </summary>
|
||||
|
|
@ -133,14 +133,14 @@ public interface ISigScanner
|
|||
/// <param name="relOffset">The relative offset.</param>
|
||||
/// <returns>The calculated offset.</returns>
|
||||
public nint ResolveRelativeAddress(nint nextInstAddr, int relOffset);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Scan for a byte signature in the .text section.
|
||||
/// </summary>
|
||||
/// <param name="signature">The signature.</param>
|
||||
/// <returns>The real offset of the found signature.</returns>
|
||||
public nint ScanText(string signature);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Try scanning for a byte signature in the .text section.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ using Dalamud.Interface.Internal.Windows.Data.Widgets;
|
|||
using Dalamud.Interface.Textures;
|
||||
using Dalamud.Interface.Textures.TextureWraps;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
using Lumina.Data.Files;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
|
@ -45,6 +47,14 @@ public interface ITextureProvider
|
|||
bool cpuWrite,
|
||||
string? debugName = null);
|
||||
|
||||
/// <summary>Creates a texture that can be drawn from an <see cref="ImDrawList"/> or an <see cref="ImDrawData"/>.
|
||||
/// </summary>
|
||||
/// <param name="debugName">Name for debug display purposes.</param>
|
||||
/// <returns>A new draw list texture.</returns>
|
||||
/// <remarks>No new resource is allocated upfront; it will be done when <see cref="IDrawListTextureWrap.Size"/> is
|
||||
/// set with positive values for both components.</remarks>
|
||||
IDrawListTextureWrap CreateDrawListTexture(string? debugName = null);
|
||||
|
||||
/// <summary>Creates a texture from the given existing texture, cropping and converting pixel format as needed.
|
||||
/// </summary>
|
||||
/// <param name="wrap">The source texture wrap. The passed value may be disposed once this function returns,
|
||||
|
|
@ -169,6 +179,14 @@ public interface ITextureProvider
|
|||
string? debugName = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Creates a texture from clipboard.</summary>
|
||||
/// <param name="debugName">Name for debug display purposes.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the status of the operation.</returns>
|
||||
Task<IDalamudTextureWrap> CreateFromClipboardAsync(
|
||||
string? debugName = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Gets the supported bitmap decoders.</summary>
|
||||
/// <returns>The supported bitmap decoders.</returns>
|
||||
/// <remarks>
|
||||
|
|
@ -191,6 +209,11 @@ public interface ITextureProvider
|
|||
/// <para>Caching the returned object is not recommended. Performance benefit will be minimal.</para>
|
||||
/// </remarks>
|
||||
ISharedImmediateTexture GetFromGameIcon(in GameIconLookup lookup);
|
||||
|
||||
/// <summary>Gets a value indicating whether the current desktop clipboard contains an image that can be attempted
|
||||
/// to read using <see cref="CreateFromClipboardAsync"/>.</summary>
|
||||
/// <returns><c>true</c> if it is the case.</returns>
|
||||
bool HasClipboardImage();
|
||||
|
||||
/// <summary>Gets a shared texture corresponding to the given game resource icon specifier.</summary>
|
||||
/// <remarks>
|
||||
|
|
@ -200,7 +223,7 @@ public interface ITextureProvider
|
|||
/// </remarks>
|
||||
/// <param name="lookup">A game icon specifier.</param>
|
||||
/// <param name="texture">The resulting <see cref="ISharedImmediateTexture"/>.</param>
|
||||
/// <returns>Whether or not the lookup succeeded.</returns>
|
||||
/// <returns>Whether the lookup succeeded.</returns>
|
||||
bool TryGetFromGameIcon(in GameIconLookup lookup, [NotNullWhen(true)] out ISharedImmediateTexture? texture);
|
||||
|
||||
/// <summary>Gets a shared texture corresponding to the given path to a game resource.</summary>
|
||||
|
|
@ -221,7 +244,7 @@ public interface ITextureProvider
|
|||
/// <para>Caching the returned object is not recommended. Performance benefit will be minimal.</para>
|
||||
/// </remarks>
|
||||
ISharedImmediateTexture GetFromFile(string path);
|
||||
|
||||
|
||||
/// <summary>Gets a shared texture corresponding to the given file on the filesystem.</summary>
|
||||
/// <param name="file">The file on the filesystem to load.</param>
|
||||
/// <returns>The shared texture that you may use to obtain the loaded texture wrap and load states.</returns>
|
||||
|
|
|
|||
|
|
@ -106,4 +106,17 @@ public interface ITextureReadbackProvider
|
|||
IReadOnlyDictionary<string, object>? props = null,
|
||||
bool leaveWrapOpen = false,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Copies the texture to clipboard.</summary>
|
||||
/// <param name="wrap">Texture wrap to copy.</param>
|
||||
/// <param name="preferredFileNameWithoutExtension">Preferred file name.</param>
|
||||
/// <param name="leaveWrapOpen">Whether to leave <paramref name="wrap"/> non-disposed when the returned
|
||||
/// <see cref="Task{TResult}"/> completes.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the status of the operation.</returns>
|
||||
Task CopyToClipboardAsync(
|
||||
IDalamudTextureWrap wrap,
|
||||
string? preferredFileNameWithoutExtension = null,
|
||||
bool leaveWrapOpen = false,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue