mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-14 12:44:16 +01:00
Merge branch 'refs/heads/master' into apiX
This commit is contained in:
commit
cce11a82be
11 changed files with 548 additions and 229 deletions
15
.github/workflows/main.yml
vendored
15
.github/workflows/main.yml
vendored
|
|
@ -7,6 +7,10 @@ concurrency:
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build on Windows
|
name: Build on Windows
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
attestations: write
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Dalamud
|
- name: Checkout Dalamud
|
||||||
|
|
@ -40,6 +44,17 @@ jobs:
|
||||||
run: .\sign.ps1 .\bin\Release
|
run: .\sign.ps1 .\bin\Release
|
||||||
- name: Create hashlist
|
- name: Create hashlist
|
||||||
run: .\CreateHashList.ps1 .\bin\Release
|
run: .\CreateHashList.ps1 .\bin\Release
|
||||||
|
- name: Attest Build
|
||||||
|
if: ${{ github.repository_owner == 'goatcorp' && github.event_name == 'push' }}
|
||||||
|
uses: actions/attest-build-provenance@v1
|
||||||
|
with:
|
||||||
|
subject-path: |
|
||||||
|
bin/Release/hashes.json
|
||||||
|
bin/Release/Dalamud.dll
|
||||||
|
bin/Release/DalamudCrashHandler.exe
|
||||||
|
bin/Release/Dalamud.*.dll
|
||||||
|
bin/Release/Dalamud.*.exe
|
||||||
|
bin/Release/FFXIVClientStructs.dll
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,11 @@ public enum PluginInstallerOpenKind
|
||||||
/// Open to the "Installed Plugins" page.
|
/// Open to the "Installed Plugins" page.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
InstalledPlugins,
|
InstalledPlugins,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Open to the "Can be updated" page.
|
||||||
|
/// </summary>
|
||||||
|
UpdateablePlugins,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Open to the "Changelogs" page.
|
/// Open to the "Changelogs" page.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
using CheapLoc;
|
using CheapLoc;
|
||||||
|
|
@ -143,6 +144,13 @@ internal class DalamudCommands : IServiceType
|
||||||
HelpMessage = "ImGui DEBUG",
|
HelpMessage = "ImGui DEBUG",
|
||||||
ShowInHelp = false,
|
ShowInHelp = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
commandManager.AddHandler("/xlcopylog", new CommandInfo(this.OnCopyLogCommand)
|
||||||
|
{
|
||||||
|
HelpMessage = Loc.Localize(
|
||||||
|
"DalamudCopyLogHelp",
|
||||||
|
"Copy the dalamud.log file to your clipboard."),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUnloadCommand(string command, string arguments)
|
private void OnUnloadCommand(string command, string arguments)
|
||||||
|
|
@ -406,4 +414,17 @@ internal class DalamudCommands : IServiceType
|
||||||
{
|
{
|
||||||
Service<DalamudInterface>.Get().ToggleProfilerWindow();
|
Service<DalamudInterface>.Get().ToggleProfilerWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnCopyLogCommand(string command, string arguments)
|
||||||
|
{
|
||||||
|
var chatGui = Service<ChatGui>.Get();
|
||||||
|
var logPath = Path.Join(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||||
|
"XIVLauncher",
|
||||||
|
"dalamud.log");
|
||||||
|
var message = Util.CopyFilesToClipboard([logPath])
|
||||||
|
? Loc.Localize("DalamudLogCopySuccess", "Log file copied to clipboard.")
|
||||||
|
: Loc.Localize("DalamudLogCopyFailure", "Could not copy log file to clipboard.");
|
||||||
|
chatGui.Print(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,46 +16,49 @@ internal class PluginCategoryManager
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// First categoryId for tag based categories.
|
/// First categoryId for tag based categories.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int FirstTagBasedCategoryId = 100;
|
private const int FirstTagBasedCategoryId = 100;
|
||||||
|
|
||||||
private readonly CategoryInfo[] categoryList =
|
private readonly CategoryInfo[] categoryList =
|
||||||
{
|
[
|
||||||
new(0, "special.all", () => Locs.Category_All),
|
new(CategoryKind.All, "special.all", () => Locs.Category_All),
|
||||||
new(1, "special.isTesting", () => Locs.Category_IsTesting, CategoryInfo.AppearCondition.DoPluginTest),
|
new(CategoryKind.IsTesting, "special.isTesting", () => Locs.Category_IsTesting, CategoryInfo.AppearCondition.DoPluginTest),
|
||||||
new(2, "special.availableForTesting", () => Locs.Category_AvailableForTesting, CategoryInfo.AppearCondition.DoPluginTest),
|
new(CategoryKind.AvailableForTesting, "special.availableForTesting", () => Locs.Category_AvailableForTesting, CategoryInfo.AppearCondition.DoPluginTest),
|
||||||
new(10, "special.devInstalled", () => Locs.Category_DevInstalled),
|
new(CategoryKind.DevInstalled, "special.devInstalled", () => Locs.Category_DevInstalled),
|
||||||
new(11, "special.devIconTester", () => Locs.Category_IconTester),
|
new(CategoryKind.IconTester, "special.devIconTester", () => Locs.Category_IconTester),
|
||||||
new(12, "special.dalamud", () => Locs.Category_Dalamud),
|
new(CategoryKind.DalamudChangelogs, "special.dalamud", () => Locs.Category_Dalamud),
|
||||||
new(13, "special.plugins", () => Locs.Category_Plugins),
|
new(CategoryKind.PluginChangelogs, "special.plugins", () => Locs.Category_Plugins),
|
||||||
new(14, "special.profiles", () => Locs.Category_PluginProfiles),
|
new(CategoryKind.PluginProfiles, "special.profiles", () => Locs.Category_PluginProfiles),
|
||||||
new(FirstTagBasedCategoryId + 0, "other", () => Locs.Category_Other),
|
new(CategoryKind.UpdateablePlugins, "special.updateable", () => Locs.Category_UpdateablePlugins),
|
||||||
new(FirstTagBasedCategoryId + 1, "jobs", () => Locs.Category_Jobs),
|
|
||||||
new(FirstTagBasedCategoryId + 2, "ui", () => Locs.Category_UI),
|
// Tag-driven categories
|
||||||
new(FirstTagBasedCategoryId + 3, "minigames", () => Locs.Category_MiniGames),
|
new(CategoryKind.Other, "other", () => Locs.Category_Other),
|
||||||
new(FirstTagBasedCategoryId + 4, "inventory", () => Locs.Category_Inventory),
|
new(CategoryKind.Jobs, "jobs", () => Locs.Category_Jobs),
|
||||||
new(FirstTagBasedCategoryId + 5, "sound", () => Locs.Category_Sound),
|
new(CategoryKind.Ui, "ui", () => Locs.Category_UI),
|
||||||
new(FirstTagBasedCategoryId + 6, "social", () => Locs.Category_Social),
|
new(CategoryKind.MiniGames, "minigames", () => Locs.Category_MiniGames),
|
||||||
new(FirstTagBasedCategoryId + 7, "utility", () => Locs.Category_Utility),
|
new(CategoryKind.Inventory, "inventory", () => Locs.Category_Inventory),
|
||||||
|
new(CategoryKind.Sound, "sound", () => Locs.Category_Sound),
|
||||||
|
new(CategoryKind.Social, "social", () => Locs.Category_Social),
|
||||||
|
new(CategoryKind.Utility, "utility", () => Locs.Category_Utility)
|
||||||
|
|
||||||
// order doesn't matter, all tag driven categories should have Id >= FirstTagBasedCategoryId
|
// order doesn't matter, all tag driven categories should have Id >= FirstTagBasedCategoryId
|
||||||
};
|
];
|
||||||
|
|
||||||
private GroupInfo[] groupList =
|
private GroupInfo[] groupList =
|
||||||
{
|
[
|
||||||
new(GroupKind.DevTools, () => Locs.Group_DevTools, 10, 11),
|
new(GroupKind.DevTools, () => Locs.Group_DevTools, CategoryKind.DevInstalled, CategoryKind.IconTester),
|
||||||
new(GroupKind.Installed, () => Locs.Group_Installed, 0, 1, 14),
|
new(GroupKind.Installed, () => Locs.Group_Installed, CategoryKind.All, CategoryKind.IsTesting, CategoryKind.UpdateablePlugins, CategoryKind.PluginProfiles),
|
||||||
new(GroupKind.Available, () => Locs.Group_Available, 0),
|
new(GroupKind.Available, () => Locs.Group_Available, CategoryKind.All),
|
||||||
new(GroupKind.Changelog, () => Locs.Group_Changelog, 0, 12, 13),
|
new(GroupKind.Changelog, () => Locs.Group_Changelog, CategoryKind.All, CategoryKind.DalamudChangelogs, CategoryKind.PluginChangelogs)
|
||||||
|
|
||||||
// order important, used for drawing, keep in sync with defaults for currentGroupIdx
|
// order important, used for drawing, keep in sync with defaults for currentGroupIdx
|
||||||
};
|
];
|
||||||
|
|
||||||
private int currentGroupIdx = 2;
|
private int currentGroupIdx = 2;
|
||||||
private int currentCategoryIdx = 0;
|
private CategoryKind currentCategoryKind = CategoryKind.All;
|
||||||
private bool isContentDirty;
|
private bool isContentDirty;
|
||||||
|
|
||||||
private Dictionary<PluginManifest, int[]> mapPluginCategories = new();
|
private Dictionary<PluginManifest, CategoryKind[]> mapPluginCategories = new();
|
||||||
private List<int> highlightedCategoryIds = new();
|
private List<CategoryKind> highlightedCategoryKinds = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Type of category group.
|
/// Type of category group.
|
||||||
|
|
@ -83,6 +86,97 @@ internal class PluginCategoryManager
|
||||||
Changelog,
|
Changelog,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Type of category.
|
||||||
|
/// </summary>
|
||||||
|
public enum CategoryKind
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// All plugins.
|
||||||
|
/// </summary>
|
||||||
|
All = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plugins currently being tested.
|
||||||
|
/// </summary>
|
||||||
|
IsTesting = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plugins available for testing.
|
||||||
|
/// </summary>
|
||||||
|
AvailableForTesting = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Installed dev plugins.
|
||||||
|
/// </summary>
|
||||||
|
DevInstalled = 10,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Icon tester.
|
||||||
|
/// </summary>
|
||||||
|
IconTester = 11,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changelogs for Dalamud.
|
||||||
|
/// </summary>
|
||||||
|
DalamudChangelogs = 12,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changelogs for plugins.
|
||||||
|
/// </summary>
|
||||||
|
PluginChangelogs = 13,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Change plugin profiles.
|
||||||
|
/// </summary>
|
||||||
|
PluginProfiles = 14,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updateable plugins.
|
||||||
|
/// </summary>
|
||||||
|
UpdateablePlugins = 15,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plugins tagged as "other".
|
||||||
|
/// </summary>
|
||||||
|
Other = FirstTagBasedCategoryId + 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plugins tagged as "jobs".
|
||||||
|
/// </summary>
|
||||||
|
Jobs = FirstTagBasedCategoryId + 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plugins tagged as "ui".
|
||||||
|
/// </summary>
|
||||||
|
Ui = FirstTagBasedCategoryId + 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plugins tagged as "minigames".
|
||||||
|
/// </summary>
|
||||||
|
MiniGames = FirstTagBasedCategoryId + 3,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plugins tagged as "inventory".
|
||||||
|
/// </summary>
|
||||||
|
Inventory = FirstTagBasedCategoryId + 4,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plugins tagged as "sound".
|
||||||
|
/// </summary>
|
||||||
|
Sound = FirstTagBasedCategoryId + 5,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plugins tagged as "social".
|
||||||
|
/// </summary>
|
||||||
|
Social = FirstTagBasedCategoryId + 6,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plugins tagged as "utility".
|
||||||
|
/// </summary>
|
||||||
|
Utility = FirstTagBasedCategoryId + 7,
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the list of all known categories.
|
/// Gets the list of all known categories.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -92,39 +186,50 @@ internal class PluginCategoryManager
|
||||||
/// Gets the list of all known UI groups.
|
/// Gets the list of all known UI groups.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GroupInfo[] GroupList => this.groupList;
|
public GroupInfo[] GroupList => this.groupList;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets current group.
|
/// Gets or sets the current group kind.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int CurrentGroupIdx
|
public GroupKind CurrentGroupKind
|
||||||
{
|
{
|
||||||
get => this.currentGroupIdx;
|
get => this.groupList[this.currentGroupIdx].GroupKind;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (this.currentGroupIdx != value)
|
var newIdx = Array.FindIndex(this.groupList, x => x.GroupKind == value);
|
||||||
|
if (newIdx >= 0)
|
||||||
{
|
{
|
||||||
this.currentGroupIdx = value;
|
this.currentGroupIdx = newIdx;
|
||||||
this.currentCategoryIdx = 0;
|
this.currentCategoryKind = this.CurrentGroup.Categories.First();
|
||||||
this.isContentDirty = true;
|
this.isContentDirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets current category, index in current Group.Categories array.
|
/// Gets information about currently selected group.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int CurrentCategoryIdx
|
public GroupInfo CurrentGroup => this.groupList[this.currentGroupIdx];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the current category kind.
|
||||||
|
/// </summary>
|
||||||
|
public CategoryKind CurrentCategoryKind
|
||||||
{
|
{
|
||||||
get => this.currentCategoryIdx;
|
get => this.currentCategoryKind;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (this.currentCategoryIdx != value)
|
if (this.currentCategoryKind != value)
|
||||||
{
|
{
|
||||||
this.currentCategoryIdx = value;
|
this.currentCategoryKind = value;
|
||||||
this.isContentDirty = true;
|
this.isContentDirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets information about currently selected category.
|
||||||
|
/// </summary>
|
||||||
|
public CategoryInfo CurrentCategory => this.categoryList.First(x => x.CategoryKind == this.currentCategoryKind);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether current group + category selection changed recently.
|
/// Gets a value indicating whether current group + category selection changed recently.
|
||||||
|
|
@ -133,13 +238,12 @@ internal class PluginCategoryManager
|
||||||
public bool IsContentDirty => this.isContentDirty;
|
public bool IsContentDirty => this.isContentDirty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether <see cref="CurrentCategoryIdx"/> and <see cref="CurrentGroupIdx"/> are valid.
|
/// Gets a value indicating whether <see cref="CurrentCategoryKind"/> and <see cref="CurrentGroupKind"/> are valid.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsSelectionValid =>
|
public bool IsSelectionValid =>
|
||||||
(this.currentGroupIdx >= 0) &&
|
(this.currentGroupIdx >= 0) &&
|
||||||
(this.currentGroupIdx < this.groupList.Length) &&
|
(this.currentGroupIdx < this.groupList.Length) &&
|
||||||
(this.currentCategoryIdx >= 0) &&
|
this.groupList[this.currentGroupIdx].Categories.Contains(this.currentCategoryKind);
|
||||||
(this.currentCategoryIdx < this.groupList[this.currentGroupIdx].Categories.Count);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Rebuild available categories based on currently available plugins.
|
/// Rebuild available categories based on currently available plugins.
|
||||||
|
|
@ -151,10 +255,10 @@ internal class PluginCategoryManager
|
||||||
this.mapPluginCategories.Clear();
|
this.mapPluginCategories.Clear();
|
||||||
|
|
||||||
var groupAvail = Array.Find(this.groupList, x => x.GroupKind == GroupKind.Available);
|
var groupAvail = Array.Find(this.groupList, x => x.GroupKind == GroupKind.Available);
|
||||||
var prevCategoryIds = new List<int>();
|
var prevCategoryIds = new List<CategoryKind>();
|
||||||
prevCategoryIds.AddRange(groupAvail.Categories);
|
prevCategoryIds.AddRange(groupAvail.Categories);
|
||||||
|
|
||||||
var categoryList = new List<int>();
|
var categoryList = new List<CategoryKind>();
|
||||||
var allCategoryIndices = new List<int>();
|
var allCategoryIndices = new List<int>();
|
||||||
|
|
||||||
foreach (var manifest in availablePlugins)
|
foreach (var manifest in availablePlugins)
|
||||||
|
|
@ -170,10 +274,10 @@ internal class PluginCategoryManager
|
||||||
var matchIdx = Array.FindIndex(this.CategoryList, x => x.Tag.Equals(tag, StringComparison.InvariantCultureIgnoreCase));
|
var matchIdx = Array.FindIndex(this.CategoryList, x => x.Tag.Equals(tag, StringComparison.InvariantCultureIgnoreCase));
|
||||||
if (matchIdx >= 0)
|
if (matchIdx >= 0)
|
||||||
{
|
{
|
||||||
var categoryId = this.CategoryList[matchIdx].CategoryId;
|
var categoryKind = this.CategoryList[matchIdx].CategoryKind;
|
||||||
if (categoryId >= FirstTagBasedCategoryId)
|
if ((int)categoryKind >= FirstTagBasedCategoryId)
|
||||||
{
|
{
|
||||||
categoryList.Add(categoryId);
|
categoryList.Add(categoryKind);
|
||||||
|
|
||||||
if (!allCategoryIndices.Contains(matchIdx))
|
if (!allCategoryIndices.Contains(matchIdx))
|
||||||
{
|
{
|
||||||
|
|
@ -185,7 +289,7 @@ internal class PluginCategoryManager
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PluginManager.HasTestingVersion(manifest) || manifest.IsTestingExclusive)
|
if (PluginManager.HasTestingVersion(manifest) || manifest.IsTestingExclusive)
|
||||||
categoryList.Add(2);
|
categoryList.Add(CategoryKind.AvailableForTesting);
|
||||||
|
|
||||||
// always add, even if empty
|
// always add, even if empty
|
||||||
this.mapPluginCategories.Add(manifest, categoryList.ToArray());
|
this.mapPluginCategories.Add(manifest, categoryList.ToArray());
|
||||||
|
|
@ -203,11 +307,11 @@ internal class PluginCategoryManager
|
||||||
|
|
||||||
foreach (var categoryIdx in allCategoryIndices)
|
foreach (var categoryIdx in allCategoryIndices)
|
||||||
{
|
{
|
||||||
groupAvail.Categories.Add(this.CategoryList[categoryIdx].CategoryId);
|
groupAvail.Categories.Add(this.CategoryList[categoryIdx].CategoryKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
// compare with prev state and mark as dirty if needed
|
// compare with prev state and mark as dirty if needed
|
||||||
var noCategoryChanges = Enumerable.SequenceEqual(prevCategoryIds, groupAvail.Categories);
|
var noCategoryChanges = prevCategoryIds.SequenceEqual(groupAvail.Categories);
|
||||||
if (!noCategoryChanges)
|
if (!noCategoryChanges)
|
||||||
{
|
{
|
||||||
this.isContentDirty = true;
|
this.isContentDirty = true;
|
||||||
|
|
@ -228,20 +332,20 @@ internal class PluginCategoryManager
|
||||||
{
|
{
|
||||||
var groupInfo = this.groupList[this.currentGroupIdx];
|
var groupInfo = this.groupList[this.currentGroupIdx];
|
||||||
|
|
||||||
var includeAll = (this.currentCategoryIdx == 0) || (groupInfo.GroupKind != GroupKind.Available);
|
var includeAll = (this.currentCategoryKind == CategoryKind.All) || (groupInfo.GroupKind != GroupKind.Available);
|
||||||
if (includeAll)
|
if (includeAll)
|
||||||
{
|
{
|
||||||
result.AddRange(plugins);
|
result.AddRange(plugins);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var selectedCategoryInfo = Array.Find(this.categoryList, x => x.CategoryId == groupInfo.Categories[this.currentCategoryIdx]);
|
var selectedCategoryInfo = Array.Find(this.categoryList, x => x.CategoryKind == this.currentCategoryKind);
|
||||||
|
|
||||||
foreach (var plugin in plugins)
|
foreach (var plugin in plugins)
|
||||||
{
|
{
|
||||||
if (this.mapPluginCategories.TryGetValue(plugin, out var pluginCategoryIds))
|
if (this.mapPluginCategories.TryGetValue(plugin, out var pluginCategoryIds))
|
||||||
{
|
{
|
||||||
var matchIdx = Array.IndexOf(pluginCategoryIds, selectedCategoryInfo.CategoryId);
|
var matchIdx = Array.IndexOf(pluginCategoryIds, selectedCategoryInfo.CategoryKind);
|
||||||
if (matchIdx >= 0)
|
if (matchIdx >= 0)
|
||||||
{
|
{
|
||||||
result.Add(plugin);
|
result.Add(plugin);
|
||||||
|
|
@ -269,20 +373,19 @@ internal class PluginCategoryManager
|
||||||
/// <param name="plugins">List of plugins whose categories should be highlighted.</param>
|
/// <param name="plugins">List of plugins whose categories should be highlighted.</param>
|
||||||
public void SetCategoryHighlightsForPlugins(IEnumerable<PluginManifest> plugins)
|
public void SetCategoryHighlightsForPlugins(IEnumerable<PluginManifest> plugins)
|
||||||
{
|
{
|
||||||
this.highlightedCategoryIds.Clear();
|
ArgumentNullException.ThrowIfNull(plugins);
|
||||||
|
|
||||||
|
this.highlightedCategoryKinds.Clear();
|
||||||
|
|
||||||
if (plugins != null)
|
foreach (var entry in plugins)
|
||||||
{
|
{
|
||||||
foreach (var entry in plugins)
|
if (this.mapPluginCategories.TryGetValue(entry, out var pluginCategories))
|
||||||
{
|
{
|
||||||
if (this.mapPluginCategories.TryGetValue(entry, out var pluginCategories))
|
foreach (var categoryKind in pluginCategories)
|
||||||
{
|
{
|
||||||
foreach (var categoryId in pluginCategories)
|
if (!this.highlightedCategoryKinds.Contains(categoryKind))
|
||||||
{
|
{
|
||||||
if (!this.highlightedCategoryIds.Contains(categoryId))
|
this.highlightedCategoryKinds.Add(categoryKind);
|
||||||
{
|
|
||||||
this.highlightedCategoryIds.Add(categoryId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -292,9 +395,9 @@ internal class PluginCategoryManager
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if category should be highlighted.
|
/// Checks if category should be highlighted.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="categoryId">CategoryId to check.</param>
|
/// <param name="categoryKind">CategoryKind to check.</param>
|
||||||
/// <returns>true if highlight is needed.</returns>
|
/// <returns>true if highlight is needed.</returns>
|
||||||
public bool IsCategoryHighlighted(int categoryId) => this.highlightedCategoryIds.Contains(categoryId);
|
public bool IsCategoryHighlighted(CategoryKind categoryKind) => this.highlightedCategoryKinds.Contains(categoryKind);
|
||||||
|
|
||||||
private IEnumerable<string> GetCategoryTagsForManifest(PluginManifest pluginManifest)
|
private IEnumerable<string> GetCategoryTagsForManifest(PluginManifest pluginManifest)
|
||||||
{
|
{
|
||||||
|
|
@ -314,7 +417,7 @@ internal class PluginCategoryManager
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unique Id number of category, tag match based should be greater of equal <see cref="FirstTagBasedCategoryId"/>.
|
/// Unique Id number of category, tag match based should be greater of equal <see cref="FirstTagBasedCategoryId"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int CategoryId;
|
public CategoryKind CategoryKind;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tag from plugin manifest to match.
|
/// Tag from plugin manifest to match.
|
||||||
|
|
@ -326,13 +429,13 @@ internal class PluginCategoryManager
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="CategoryInfo"/> struct.
|
/// Initializes a new instance of the <see cref="CategoryInfo"/> struct.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="categoryId">Unique id of category.</param>
|
/// <param name="categoryKind">Kind of the category.</param>
|
||||||
/// <param name="tag">Tag to match.</param>
|
/// <param name="tag">Tag to match.</param>
|
||||||
/// <param name="nameFunc">Function returning localized name of category.</param>
|
/// <param name="nameFunc">Function returning localized name of category.</param>
|
||||||
/// <param name="condition">Condition to be checked when deciding whether this category should be shown.</param>
|
/// <param name="condition">Condition to be checked when deciding whether this category should be shown.</param>
|
||||||
public CategoryInfo(int categoryId, string tag, Func<string> nameFunc, AppearCondition condition = AppearCondition.None)
|
public CategoryInfo(CategoryKind categoryKind, string tag, Func<string> nameFunc, AppearCondition condition = AppearCondition.None)
|
||||||
{
|
{
|
||||||
this.CategoryId = categoryId;
|
this.CategoryKind = categoryKind;
|
||||||
this.Tag = tag;
|
this.Tag = tag;
|
||||||
this.nameFunc = nameFunc;
|
this.nameFunc = nameFunc;
|
||||||
this.Condition = condition;
|
this.Condition = condition;
|
||||||
|
|
@ -378,7 +481,7 @@ internal class PluginCategoryManager
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// List of categories in container.
|
/// List of categories in container.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<int> Categories;
|
public List<CategoryKind> Categories;
|
||||||
|
|
||||||
private Func<string> nameFunc;
|
private Func<string> nameFunc;
|
||||||
|
|
||||||
|
|
@ -388,7 +491,7 @@ internal class PluginCategoryManager
|
||||||
/// <param name="groupKind">Type of group.</param>
|
/// <param name="groupKind">Type of group.</param>
|
||||||
/// <param name="nameFunc">Function returning localized name of category.</param>
|
/// <param name="nameFunc">Function returning localized name of category.</param>
|
||||||
/// <param name="categories">List of category Ids to hardcode.</param>
|
/// <param name="categories">List of category Ids to hardcode.</param>
|
||||||
public GroupInfo(GroupKind groupKind, Func<string> nameFunc, params int[] categories)
|
public GroupInfo(GroupKind groupKind, Func<string> nameFunc, params CategoryKind[] categories)
|
||||||
{
|
{
|
||||||
this.GroupKind = groupKind;
|
this.GroupKind = groupKind;
|
||||||
this.nameFunc = nameFunc;
|
this.nameFunc = nameFunc;
|
||||||
|
|
@ -432,6 +535,8 @@ internal class PluginCategoryManager
|
||||||
|
|
||||||
public static string Category_PluginProfiles => Loc.Localize("InstallerCategoryPluginProfiles", "Plugin Collections");
|
public static string Category_PluginProfiles => Loc.Localize("InstallerCategoryPluginProfiles", "Plugin Collections");
|
||||||
|
|
||||||
|
public static string Category_UpdateablePlugins => Loc.Localize("InstallerCategoryCanBeUpdated", "Can be updated");
|
||||||
|
|
||||||
public static string Category_Other => Loc.Localize("InstallerCategoryOther", "Other");
|
public static string Category_Other => Loc.Localize("InstallerCategoryOther", "Other");
|
||||||
|
|
||||||
public static string Category_Jobs => Loc.Localize("InstallerCategoryJobs", "Jobs");
|
public static string Category_Jobs => Loc.Localize("InstallerCategoryJobs", "Jobs");
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
public PluginInstallerWindow(PluginImageCache imageCache, DalamudConfiguration configuration)
|
public PluginInstallerWindow(PluginImageCache imageCache, DalamudConfiguration configuration)
|
||||||
: base(
|
: base(
|
||||||
Locs.WindowTitle + (configuration.DoPluginTest ? Locs.WindowTitleMod_Testing : string.Empty) + "###XlPluginInstaller",
|
Locs.WindowTitle + (configuration.DoPluginTest ? Locs.WindowTitleMod_Testing : string.Empty) + "###XlPluginInstaller",
|
||||||
ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar)
|
ImGuiWindowFlags.NoScrollbar)
|
||||||
{
|
{
|
||||||
this.IsOpen = true;
|
this.IsOpen = true;
|
||||||
this.imageCache = imageCache;
|
this.imageCache = imageCache;
|
||||||
|
|
@ -222,6 +222,14 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
IsTesting = 1 << 6,
|
IsTesting = 1 << 6,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum InstalledPluginListFilter
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Testing,
|
||||||
|
Updateable,
|
||||||
|
Dev,
|
||||||
|
}
|
||||||
|
|
||||||
private bool AnyOperationInProgress => this.installStatus == OperationStatus.InProgress ||
|
private bool AnyOperationInProgress => this.installStatus == OperationStatus.InProgress ||
|
||||||
this.updateStatus == OperationStatus.InProgress ||
|
this.updateStatus == OperationStatus.InProgress ||
|
||||||
this.enableDisableStatus == OperationStatus.InProgress;
|
this.enableDisableStatus == OperationStatus.InProgress;
|
||||||
|
|
@ -421,21 +429,27 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
{
|
{
|
||||||
case PluginInstallerOpenKind.AllPlugins:
|
case PluginInstallerOpenKind.AllPlugins:
|
||||||
// Plugins group
|
// Plugins group
|
||||||
this.categoryManager.CurrentGroupIdx = 2;
|
this.categoryManager.CurrentGroupKind = PluginCategoryManager.GroupKind.Available;
|
||||||
// All category
|
// All category
|
||||||
this.categoryManager.CurrentCategoryIdx = 0;
|
this.categoryManager.CurrentCategoryKind = PluginCategoryManager.CategoryKind.All;
|
||||||
break;
|
break;
|
||||||
case PluginInstallerOpenKind.InstalledPlugins:
|
case PluginInstallerOpenKind.InstalledPlugins:
|
||||||
// Installed group
|
// Installed group
|
||||||
this.categoryManager.CurrentGroupIdx = 1;
|
this.categoryManager.CurrentGroupKind = PluginCategoryManager.GroupKind.Installed;
|
||||||
// All category
|
// All category
|
||||||
this.categoryManager.CurrentCategoryIdx = 0;
|
this.categoryManager.CurrentCategoryKind = PluginCategoryManager.CategoryKind.All;
|
||||||
|
break;
|
||||||
|
case PluginInstallerOpenKind.UpdateablePlugins:
|
||||||
|
// Installed group
|
||||||
|
this.categoryManager.CurrentGroupKind = PluginCategoryManager.GroupKind.Installed;
|
||||||
|
// Updateable category
|
||||||
|
this.categoryManager.CurrentCategoryKind = PluginCategoryManager.CategoryKind.UpdateablePlugins;
|
||||||
break;
|
break;
|
||||||
case PluginInstallerOpenKind.Changelogs:
|
case PluginInstallerOpenKind.Changelogs:
|
||||||
// Changelog group
|
// Changelog group
|
||||||
this.categoryManager.CurrentGroupIdx = 3;
|
this.categoryManager.CurrentGroupKind = PluginCategoryManager.GroupKind.Changelog;
|
||||||
// Plugins category
|
// Plugins category
|
||||||
this.categoryManager.CurrentCategoryIdx = 2;
|
this.categoryManager.CurrentCategoryKind = PluginCategoryManager.CategoryKind.All;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException(nameof(kind), kind, null);
|
throw new ArgumentOutOfRangeException(nameof(kind), kind, null);
|
||||||
|
|
@ -598,7 +612,8 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
ImGui.SetCursorPosX(windowSize.X - sortSelectWidth - (style.ItemSpacing.X * 2) - searchInputWidth - searchClearButtonWidth);
|
ImGui.SetCursorPosX(windowSize.X - sortSelectWidth - (style.ItemSpacing.X * 2) - searchInputWidth - searchClearButtonWidth);
|
||||||
|
|
||||||
var isProfileManager =
|
var isProfileManager =
|
||||||
this.categoryManager.CurrentGroupIdx == 1 && this.categoryManager.CurrentCategoryIdx == 2;
|
this.categoryManager.CurrentGroupKind == PluginCategoryManager.GroupKind.Installed &&
|
||||||
|
this.categoryManager.CurrentCategoryKind == PluginCategoryManager.CategoryKind.PluginProfiles;
|
||||||
|
|
||||||
// Disable search if profile editor
|
// Disable search if profile editor
|
||||||
using (ImRaii.Disabled(isProfileManager))
|
using (ImRaii.Disabled(isProfileManager))
|
||||||
|
|
@ -628,7 +643,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable sort if changelogs or profile editor
|
// Disable sort if changelogs or profile editor
|
||||||
using (ImRaii.Disabled(this.categoryManager.CurrentGroupIdx == 3 || isProfileManager))
|
using (ImRaii.Disabled(this.categoryManager.CurrentGroupKind == PluginCategoryManager.GroupKind.Changelog || isProfileManager))
|
||||||
{
|
{
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.SetCursorPosY(downShift);
|
ImGui.SetCursorPosY(downShift);
|
||||||
|
|
@ -727,7 +742,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
this.loadingIndicatorKind = LoadingIndicatorKind.UpdatingAll;
|
this.loadingIndicatorKind = LoadingIndicatorKind.UpdatingAll;
|
||||||
|
|
||||||
var toUpdate = this.pluginListUpdatable
|
var toUpdate = this.pluginListUpdatable
|
||||||
.Where(x => x.InstalledPlugin.IsLoaded)
|
.Where(x => x.InstalledPlugin.IsWantedByAnyProfile)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
Task.Run(() => pluginManager.UpdatePluginsAsync(toUpdate, false))
|
Task.Run(() => pluginManager.UpdatePluginsAsync(toUpdate, false))
|
||||||
|
|
@ -768,9 +783,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
Service<PluginManager>.Get().PrintUpdatedPlugins(this.updatedPlugins, Locs.PluginUpdateHeader_Chatbox);
|
Service<PluginManager>.Get().PrintUpdatedPlugins(this.updatedPlugins, Locs.PluginUpdateHeader_Chatbox);
|
||||||
notifications.AddNotification(Locs.Notifications_UpdatesInstalled(this.updatePluginCount), Locs.Notifications_UpdatesInstalledTitle, NotificationType.Success);
|
notifications.AddNotification(Locs.Notifications_UpdatesInstalled(this.updatePluginCount), Locs.Notifications_UpdatesInstalledTitle, NotificationType.Success);
|
||||||
|
|
||||||
var installedGroupIdx = this.categoryManager.GroupList.TakeWhile(
|
this.categoryManager.CurrentGroupKind = PluginCategoryManager.GroupKind.Installed;
|
||||||
x => x.GroupKind != PluginCategoryManager.GroupKind.Installed).Count();
|
|
||||||
this.categoryManager.CurrentGroupIdx = installedGroupIdx;
|
|
||||||
}
|
}
|
||||||
else if (this.updatePluginCount == 0)
|
else if (this.updatePluginCount == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -1216,7 +1229,8 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
|
|
||||||
if (proxy.LocalPlugin != null)
|
if (proxy.LocalPlugin != null)
|
||||||
{
|
{
|
||||||
this.DrawInstalledPlugin(proxy.LocalPlugin, i++, proxy.RemoteManifest, true);
|
var update = this.pluginListUpdatable.FirstOrDefault(up => up.InstalledPlugin == proxy.LocalPlugin);
|
||||||
|
this.DrawInstalledPlugin(proxy.LocalPlugin, i++, proxy.RemoteManifest, update);
|
||||||
}
|
}
|
||||||
else if (proxy.RemoteManifest != null)
|
else if (proxy.RemoteManifest != null)
|
||||||
{
|
{
|
||||||
|
|
@ -1226,8 +1240,8 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
ImGui.PopID();
|
ImGui.PopID();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawInstalledPluginList(bool filterTesting)
|
private void DrawInstalledPluginList(InstalledPluginListFilter filter)
|
||||||
{
|
{
|
||||||
var pluginList = this.pluginListInstalled;
|
var pluginList = this.pluginListInstalled;
|
||||||
var manager = Service<PluginManager>.Get();
|
var manager = Service<PluginManager>.Get();
|
||||||
|
|
@ -1248,47 +1262,57 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var drewAny = false;
|
||||||
var i = 0;
|
var i = 0;
|
||||||
foreach (var plugin in filteredList)
|
foreach (var plugin in filteredList)
|
||||||
{
|
{
|
||||||
if (filterTesting && !manager.HasTestingOptIn(plugin.Manifest))
|
if (filter == InstalledPluginListFilter.Testing && !manager.HasTestingOptIn(plugin.Manifest))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// Find applicable update and manifest, if we have them
|
||||||
|
AvailablePluginUpdate? update = null;
|
||||||
|
RemotePluginManifest? remoteManifest = null;
|
||||||
|
|
||||||
// Find the applicable remote manifest
|
if (filter != InstalledPluginListFilter.Dev)
|
||||||
var remoteManifest = this.pluginListAvailable
|
{
|
||||||
.FirstOrDefault(rm => rm.InternalName == plugin.Manifest.InternalName &&
|
update = this.pluginListUpdatable.FirstOrDefault(up => up.InstalledPlugin == plugin);
|
||||||
rm.RepoUrl == plugin.Manifest.RepoUrl);
|
if (filter == InstalledPluginListFilter.Updateable && update == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
this.DrawInstalledPlugin(plugin, i++, remoteManifest);
|
// Find the applicable remote manifest
|
||||||
|
remoteManifest = this.pluginListAvailable
|
||||||
|
.FirstOrDefault(rm => rm.InternalName == plugin.Manifest.InternalName &&
|
||||||
|
rm.RepoUrl == plugin.Manifest.RepoUrl);
|
||||||
|
}
|
||||||
|
else if (!plugin.IsDev)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.DrawInstalledPlugin(plugin, i++, remoteManifest, update);
|
||||||
|
drewAny = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (!drewAny)
|
||||||
private void DrawInstalledDevPluginList()
|
|
||||||
{
|
|
||||||
var pluginList = this.pluginListInstalled
|
|
||||||
.Where(plugin => plugin.IsDev)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (pluginList.Count == 0)
|
|
||||||
{
|
{
|
||||||
ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoInstalled);
|
var text = filter switch
|
||||||
return;
|
{
|
||||||
}
|
InstalledPluginListFilter.None => Locs.TabBody_NoPluginsInstalled,
|
||||||
|
InstalledPluginListFilter.Testing => Locs.TabBody_NoPluginsTesting,
|
||||||
|
InstalledPluginListFilter.Updateable => Locs.TabBody_NoPluginsUpdateable,
|
||||||
|
InstalledPluginListFilter.Dev => Locs.TabBody_NoPluginsDev,
|
||||||
|
_ => throw new ArgumentException(null, nameof(filter)),
|
||||||
|
};
|
||||||
|
|
||||||
|
ImGuiHelpers.ScaledDummy(60);
|
||||||
|
|
||||||
var filteredList = pluginList
|
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudGrey))
|
||||||
.Where(plugin => !this.IsManifestFiltered(plugin.Manifest))
|
{
|
||||||
.ToList();
|
foreach (var line in text.Split('\n'))
|
||||||
|
{
|
||||||
if (filteredList.Count == 0)
|
ImGuiHelpers.CenteredText(line);
|
||||||
{
|
}
|
||||||
ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching);
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var i = 0;
|
|
||||||
foreach (var plugin in filteredList)
|
|
||||||
{
|
|
||||||
this.DrawInstalledPlugin(plugin, i++, null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1349,29 +1373,29 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var groupIdx = 0; groupIdx < this.categoryManager.GroupList.Length; groupIdx++)
|
foreach (var groupInfo in this.categoryManager.GroupList)
|
||||||
{
|
{
|
||||||
var groupInfo = this.categoryManager.GroupList[groupIdx];
|
|
||||||
var canShowGroup = (groupInfo.GroupKind != PluginCategoryManager.GroupKind.DevTools) || this.hasDevPlugins;
|
var canShowGroup = (groupInfo.GroupKind != PluginCategoryManager.GroupKind.DevTools) || this.hasDevPlugins;
|
||||||
if (!canShowGroup)
|
if (!canShowGroup)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SetNextItemOpen(groupIdx == this.categoryManager.CurrentGroupIdx);
|
var isCurrent = groupInfo.GroupKind == this.categoryManager.CurrentGroupKind;
|
||||||
if (ImGui.CollapsingHeader(groupInfo.Name, groupIdx == this.categoryManager.CurrentGroupIdx ? ImGuiTreeNodeFlags.OpenOnDoubleClick : ImGuiTreeNodeFlags.None))
|
ImGui.SetNextItemOpen(isCurrent);
|
||||||
|
if (ImGui.CollapsingHeader(groupInfo.Name, isCurrent ? ImGuiTreeNodeFlags.OpenOnDoubleClick : ImGuiTreeNodeFlags.None))
|
||||||
{
|
{
|
||||||
if (this.categoryManager.CurrentGroupIdx != groupIdx)
|
if (!isCurrent)
|
||||||
{
|
{
|
||||||
this.categoryManager.CurrentGroupIdx = groupIdx;
|
this.categoryManager.CurrentGroupKind = groupInfo.GroupKind;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Indent();
|
ImGui.Indent();
|
||||||
var categoryItemSize = new Vector2(ImGui.GetContentRegionAvail().X - (5 * ImGuiHelpers.GlobalScale), ImGui.GetTextLineHeight());
|
var categoryItemSize = new Vector2(ImGui.GetContentRegionAvail().X - (5 * ImGuiHelpers.GlobalScale), ImGui.GetTextLineHeight());
|
||||||
for (var categoryIdx = 0; categoryIdx < groupInfo.Categories.Count; categoryIdx++)
|
foreach (var categoryKind in groupInfo.Categories)
|
||||||
{
|
{
|
||||||
var categoryInfo = Array.Find(this.categoryManager.CategoryList, x => x.CategoryId == groupInfo.Categories[categoryIdx]);
|
var categoryInfo = this.categoryManager.CategoryList.First(x => x.CategoryKind == categoryKind);
|
||||||
|
|
||||||
switch (categoryInfo.Condition)
|
switch (categoryInfo.Condition)
|
||||||
{
|
{
|
||||||
case PluginCategoryManager.CategoryInfo.AppearCondition.None:
|
case PluginCategoryManager.CategoryInfo.AppearCondition.None:
|
||||||
|
|
@ -1385,15 +1409,15 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasSearchHighlight = this.categoryManager.IsCategoryHighlighted(categoryInfo.CategoryId);
|
var hasSearchHighlight = this.categoryManager.IsCategoryHighlighted(categoryInfo.CategoryKind);
|
||||||
if (hasSearchHighlight)
|
if (hasSearchHighlight)
|
||||||
{
|
{
|
||||||
ImGui.PushStyleColor(ImGuiCol.Text, colorSearchHighlight);
|
ImGui.PushStyleColor(ImGuiCol.Text, colorSearchHighlight);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.Selectable(categoryInfo.Name, this.categoryManager.CurrentCategoryIdx == categoryIdx, ImGuiSelectableFlags.None, categoryItemSize))
|
if (ImGui.Selectable(categoryInfo.Name, this.categoryManager.CurrentCategoryKind == categoryKind, ImGuiSelectableFlags.None, categoryItemSize))
|
||||||
{
|
{
|
||||||
this.categoryManager.CurrentCategoryIdx = categoryIdx;
|
this.categoryManager.CurrentCategoryKind = categoryKind;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasSearchHighlight)
|
if (hasSearchHighlight)
|
||||||
|
|
@ -1403,11 +1427,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Unindent();
|
ImGui.Unindent();
|
||||||
|
ImGuiHelpers.ScaledDummy(5);
|
||||||
if (groupIdx != this.categoryManager.GroupList.Length - 1)
|
|
||||||
{
|
|
||||||
ImGuiHelpers.ScaledDummy(5);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1443,7 +1463,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
|
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ImGuiHelpers.ScaledVector2(1, 3));
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ImGuiHelpers.ScaledVector2(1, 3));
|
||||||
|
|
||||||
var groupInfo = this.categoryManager.GroupList[this.categoryManager.CurrentGroupIdx];
|
var groupInfo = this.categoryManager.CurrentGroup;
|
||||||
if (this.categoryManager.IsContentDirty)
|
if (this.categoryManager.IsContentDirty)
|
||||||
{
|
{
|
||||||
// reset opened list of collapsibles when switching between categories
|
// reset opened list of collapsibles when switching between categories
|
||||||
|
|
@ -1460,53 +1480,65 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
{
|
{
|
||||||
case PluginCategoryManager.GroupKind.DevTools:
|
case PluginCategoryManager.GroupKind.DevTools:
|
||||||
// this one is never sorted and remains in hardcoded order from group ctor
|
// this one is never sorted and remains in hardcoded order from group ctor
|
||||||
switch (this.categoryManager.CurrentCategoryIdx)
|
switch (this.categoryManager.CurrentCategoryKind)
|
||||||
{
|
{
|
||||||
case 0:
|
case PluginCategoryManager.CategoryKind.DevInstalled:
|
||||||
this.DrawInstalledDevPluginList();
|
this.DrawInstalledPluginList(InstalledPluginListFilter.Dev);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1:
|
case PluginCategoryManager.CategoryKind.IconTester:
|
||||||
this.DrawImageTester();
|
this.DrawImageTester();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// umm, there's nothing else, keep handled set and just skip drawing...
|
ImGui.TextUnformatted("You found a mysterious category. Please keep it to yourself.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case PluginCategoryManager.GroupKind.Installed:
|
case PluginCategoryManager.GroupKind.Installed:
|
||||||
switch (this.categoryManager.CurrentCategoryIdx)
|
switch (this.categoryManager.CurrentCategoryKind)
|
||||||
{
|
{
|
||||||
case 0:
|
case PluginCategoryManager.CategoryKind.All:
|
||||||
this.DrawInstalledPluginList(false);
|
this.DrawInstalledPluginList(InstalledPluginListFilter.None);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1:
|
case PluginCategoryManager.CategoryKind.IsTesting:
|
||||||
this.DrawInstalledPluginList(true);
|
this.DrawInstalledPluginList(InstalledPluginListFilter.Testing);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PluginCategoryManager.CategoryKind.UpdateablePlugins:
|
||||||
|
this.DrawInstalledPluginList(InstalledPluginListFilter.Updateable);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case PluginCategoryManager.CategoryKind.PluginProfiles:
|
||||||
this.profileManagerWidget.Draw();
|
this.profileManagerWidget.Draw();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
ImGui.TextUnformatted("You found a secret category. Please feel a sense of pride and accomplishment.");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case PluginCategoryManager.GroupKind.Changelog:
|
case PluginCategoryManager.GroupKind.Changelog:
|
||||||
switch (this.categoryManager.CurrentCategoryIdx)
|
switch (this.categoryManager.CurrentCategoryKind)
|
||||||
{
|
{
|
||||||
case 0:
|
case PluginCategoryManager.CategoryKind.All:
|
||||||
this.DrawChangelogList(true, true);
|
this.DrawChangelogList(true, true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1:
|
case PluginCategoryManager.CategoryKind.DalamudChangelogs:
|
||||||
this.DrawChangelogList(true, false);
|
this.DrawChangelogList(true, false);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case PluginCategoryManager.CategoryKind.PluginChangelogs:
|
||||||
this.DrawChangelogList(false, true);
|
this.DrawChangelogList(false, true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
ImGui.TextUnformatted("You found a quiet category. Please don't wake it up.");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
@ -2358,7 +2390,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawInstalledPlugin(LocalPlugin plugin, int index, RemotePluginManifest? remoteManifest, bool showInstalled = false)
|
private void DrawInstalledPlugin(LocalPlugin plugin, int index, RemotePluginManifest? remoteManifest, AvailablePluginUpdate? availablePluginUpdate, bool showInstalled = false)
|
||||||
{
|
{
|
||||||
var configuration = Service<DalamudConfiguration>.Get();
|
var configuration = Service<DalamudConfiguration>.Get();
|
||||||
var commandManager = Service<CommandManager>.Get();
|
var commandManager = Service<CommandManager>.Get();
|
||||||
|
|
@ -2417,8 +2449,6 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
trouble = true;
|
trouble = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var availablePluginUpdate = this.pluginListUpdatable.FirstOrDefault(up => up.InstalledPlugin == plugin);
|
|
||||||
|
|
||||||
// Dev plugins can never update
|
// Dev plugins can never update
|
||||||
if (plugin.IsDev)
|
if (plugin.IsDev)
|
||||||
availablePluginUpdate = null;
|
availablePluginUpdate = null;
|
||||||
|
|
@ -3576,7 +3606,7 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(this.searchText))
|
if (string.IsNullOrEmpty(this.searchText))
|
||||||
{
|
{
|
||||||
this.categoryManager.SetCategoryHighlightsForPlugins(null);
|
this.categoryManager.SetCategoryHighlightsForPlugins(Array.Empty<RemotePluginManifest>());
|
||||||
|
|
||||||
// Reset here for good measure, as we're returning from a search
|
// Reset here for good measure, as we're returning from a search
|
||||||
this.openPluginCollapsibles.Clear();
|
this.openPluginCollapsibles.Clear();
|
||||||
|
|
@ -3717,7 +3747,16 @@ internal class PluginInstallerWindow : Window, IDisposable
|
||||||
public static string TabBody_DownloadFailed => Loc.Localize("InstallerDownloadFailed", "Download failed.");
|
public static string TabBody_DownloadFailed => Loc.Localize("InstallerDownloadFailed", "Download failed.");
|
||||||
|
|
||||||
public static string TabBody_SafeMode => Loc.Localize("InstallerSafeMode", "Dalamud is running in Plugin Safe Mode, restart to activate plugins.");
|
public static string TabBody_SafeMode => Loc.Localize("InstallerSafeMode", "Dalamud is running in Plugin Safe Mode, restart to activate plugins.");
|
||||||
|
|
||||||
|
public static string TabBody_NoPluginsTesting => Loc.Localize("InstallerNoPluginsTesting", "You aren't testing any plugins at the moment!\nYou can opt in to testing versions in the plugin context menu.");
|
||||||
|
|
||||||
|
public static string TabBody_NoPluginsInstalled =>
|
||||||
|
string.Format(Loc.Localize("InstallerNoPluginsInstalled", "You don't have any plugins installed yet!\nYou can install them from the \"{0}\" tab."), PluginCategoryManager.Locs.Category_All);
|
||||||
|
|
||||||
|
public static string TabBody_NoPluginsUpdateable => Loc.Localize("InstallerNoPluginsUpdate", "No plugins have updates available at the moment.");
|
||||||
|
|
||||||
|
public static string TabBody_NoPluginsDev => Loc.Localize("InstallerNoPluginsDev", "You don't have any dev plugins. Add them some the settings.");
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Search text
|
#region Search text
|
||||||
|
|
|
||||||
|
|
@ -11,3 +11,13 @@ SetActiveWindow
|
||||||
HWND_TOPMOST
|
HWND_TOPMOST
|
||||||
HWND_NOTOPMOST
|
HWND_NOTOPMOST
|
||||||
SET_WINDOW_POS_FLAGS
|
SET_WINDOW_POS_FLAGS
|
||||||
|
|
||||||
|
OpenClipboard
|
||||||
|
SetClipboardData
|
||||||
|
CloseClipboard
|
||||||
|
DROPFILES
|
||||||
|
CLIPBOARD_FORMAT
|
||||||
|
GlobalAlloc
|
||||||
|
GlobalLock
|
||||||
|
GlobalUnlock
|
||||||
|
GLOBAL_ALLOC_FLAGS
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ using Dalamud.Game.ClientState;
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.ImGuiNotification;
|
using Dalamud.Interface.ImGuiNotification;
|
||||||
|
using Dalamud.Interface.ImGuiNotification.EventArgs;
|
||||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||||
using Dalamud.Interface.Internal;
|
using Dalamud.Interface.Internal;
|
||||||
using Dalamud.Interface.Internal.DesignSystem;
|
using Dalamud.Interface.Internal.DesignSystem;
|
||||||
|
|
@ -39,7 +40,13 @@ internal class AutoUpdateManager : IServiceType
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Time we should wait between scheduled update checks.
|
/// Time we should wait between scheduled update checks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly TimeSpan TimeBetweenUpdateChecks = TimeSpan.FromHours(1.5);
|
private static readonly TimeSpan TimeBetweenUpdateChecks = TimeSpan.FromHours(2);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Time we should wait between scheduled update checks if the user has dismissed the notification,
|
||||||
|
/// instead of updating. We don't want to spam the user with notifications.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly TimeSpan TimeBetweenUpdateChecksIfDismissed = TimeSpan.FromHours(12);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Time we should wait after unblocking to nag the user.
|
/// Time we should wait after unblocking to nag the user.
|
||||||
|
|
@ -62,12 +69,13 @@ internal class AutoUpdateManager : IServiceType
|
||||||
private readonly IConsoleVariable<bool> isDryRun;
|
private readonly IConsoleVariable<bool> isDryRun;
|
||||||
|
|
||||||
private DateTime? loginTime;
|
private DateTime? loginTime;
|
||||||
private DateTime? lastUpdateCheckTime;
|
private DateTime? nextUpdateCheckTime;
|
||||||
private DateTime? unblockedSince;
|
private DateTime? unblockedSince;
|
||||||
|
|
||||||
private bool hasStartedInitialUpdateThisSession;
|
private bool hasStartedInitialUpdateThisSession;
|
||||||
|
|
||||||
private IActiveNotification? updateNotification;
|
private IActiveNotification? updateNotification;
|
||||||
|
private bool notificationHasStartedUpdate; // Used to track if the user has started an update from the notification.
|
||||||
|
|
||||||
private Task? autoUpdateTask;
|
private Task? autoUpdateTask;
|
||||||
|
|
||||||
|
|
@ -95,7 +103,7 @@ internal class AutoUpdateManager : IServiceType
|
||||||
});
|
});
|
||||||
console.AddCommand("dalamud.autoupdate.force_check", "Force a check for updates", () =>
|
console.AddCommand("dalamud.autoupdate.force_check", "Force a check for updates", () =>
|
||||||
{
|
{
|
||||||
this.lastUpdateCheckTime = DateTime.Now - TimeBetweenUpdateChecks;
|
this.nextUpdateCheckTime = DateTime.Now + TimeSpan.FromSeconds(5);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -127,6 +135,17 @@ internal class AutoUpdateManager : IServiceType
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(behavior), behavior, null),
|
_ => throw new ArgumentOutOfRangeException(nameof(behavior), behavior, null),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void DrawOpenInstallerNotificationButton(bool primary, PluginInstallerOpenKind kind, IActiveNotification notification)
|
||||||
|
{
|
||||||
|
if (primary ?
|
||||||
|
DalamudComponents.PrimaryButton(Locs.NotificationButtonOpenPluginInstaller) :
|
||||||
|
DalamudComponents.SecondaryButton(Locs.NotificationButtonOpenPluginInstaller))
|
||||||
|
{
|
||||||
|
Service<DalamudInterface>.Get().OpenPluginInstallerTo(kind);
|
||||||
|
notification.DismissNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnUpdate(IFramework framework)
|
private void OnUpdate(IFramework framework)
|
||||||
{
|
{
|
||||||
|
|
@ -170,11 +189,9 @@ internal class AutoUpdateManager : IServiceType
|
||||||
// the only time we actually install updates automatically.
|
// the only time we actually install updates automatically.
|
||||||
if (!this.hasStartedInitialUpdateThisSession && DateTime.Now > this.loginTime.Value.Add(UpdateTimeAfterLogin))
|
if (!this.hasStartedInitialUpdateThisSession && DateTime.Now > this.loginTime.Value.Add(UpdateTimeAfterLogin))
|
||||||
{
|
{
|
||||||
this.lastUpdateCheckTime = DateTime.Now;
|
|
||||||
this.hasStartedInitialUpdateThisSession = true;
|
this.hasStartedInitialUpdateThisSession = true;
|
||||||
|
|
||||||
var currentlyUpdatablePlugins = this.GetAvailablePluginUpdates(DecideUpdateListingRestriction(behavior));
|
var currentlyUpdatablePlugins = this.GetAvailablePluginUpdates(DecideUpdateListingRestriction(behavior));
|
||||||
|
|
||||||
if (currentlyUpdatablePlugins.Count == 0)
|
if (currentlyUpdatablePlugins.Count == 0)
|
||||||
{
|
{
|
||||||
this.IsAutoUpdateComplete = true;
|
this.IsAutoUpdateComplete = true;
|
||||||
|
|
@ -186,12 +203,13 @@ internal class AutoUpdateManager : IServiceType
|
||||||
if (behavior == AutoUpdateBehavior.OnlyNotify)
|
if (behavior == AutoUpdateBehavior.OnlyNotify)
|
||||||
{
|
{
|
||||||
// List all plugins in the notification
|
// List all plugins in the notification
|
||||||
Log.Verbose("Ran initial update, notifying for {Num} plugins", currentlyUpdatablePlugins.Count);
|
Log.Verbose("Running initial auto-update, notifying for {Num} plugins", currentlyUpdatablePlugins.Count);
|
||||||
this.NotifyUpdatesAreAvailable(currentlyUpdatablePlugins);
|
this.NotifyUpdatesAreAvailable(currentlyUpdatablePlugins);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Verbose("Ran initial update, updating {Num} plugins", currentlyUpdatablePlugins.Count);
|
Log.Verbose("Running initial auto-update, updating {Num} plugins", currentlyUpdatablePlugins.Count);
|
||||||
|
this.notificationHasStartedUpdate = true;
|
||||||
this.KickOffAutoUpdates(currentlyUpdatablePlugins);
|
this.KickOffAutoUpdates(currentlyUpdatablePlugins);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -199,10 +217,13 @@ internal class AutoUpdateManager : IServiceType
|
||||||
// 2. Continuously check for updates while the game is running. We run these every once in a while and
|
// 2. Continuously check for updates while the game is running. We run these every once in a while and
|
||||||
// will only show a notification here that lets people start the update or open the installer.
|
// will only show a notification here that lets people start the update or open the installer.
|
||||||
if (this.config.CheckPeriodicallyForUpdates &&
|
if (this.config.CheckPeriodicallyForUpdates &&
|
||||||
this.lastUpdateCheckTime != null &&
|
this.nextUpdateCheckTime != null &&
|
||||||
DateTime.Now - this.lastUpdateCheckTime > TimeBetweenUpdateChecks &&
|
DateTime.Now > this.nextUpdateCheckTime &&
|
||||||
this.updateNotification == null)
|
this.updateNotification == null)
|
||||||
{
|
{
|
||||||
|
this.nextUpdateCheckTime = null;
|
||||||
|
|
||||||
|
Log.Verbose("Starting periodic update check");
|
||||||
this.pluginManager.ReloadPluginMastersAsync()
|
this.pluginManager.ReloadPluginMastersAsync()
|
||||||
.ContinueWith(
|
.ContinueWith(
|
||||||
t =>
|
t =>
|
||||||
|
|
@ -216,8 +237,6 @@ internal class AutoUpdateManager : IServiceType
|
||||||
this.GetAvailablePluginUpdates(
|
this.GetAvailablePluginUpdates(
|
||||||
DecideUpdateListingRestriction(behavior)));
|
DecideUpdateListingRestriction(behavior)));
|
||||||
});
|
});
|
||||||
|
|
||||||
this.lastUpdateCheckTime = DateTime.Now;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -226,16 +245,34 @@ internal class AutoUpdateManager : IServiceType
|
||||||
if (this.updateNotification != null)
|
if (this.updateNotification != null)
|
||||||
throw new InvalidOperationException("Already showing a notification");
|
throw new InvalidOperationException("Already showing a notification");
|
||||||
|
|
||||||
|
if (this.notificationHasStartedUpdate)
|
||||||
|
throw new InvalidOperationException("Lost track of notification state");
|
||||||
|
|
||||||
this.updateNotification = this.notificationManager.AddNotification(notification);
|
this.updateNotification = this.notificationManager.AddNotification(notification);
|
||||||
this.updateNotification.Dismiss += _ => this.updateNotification = null;
|
this.updateNotification.Dismiss += _ =>
|
||||||
|
{
|
||||||
|
this.updateNotification = null;
|
||||||
|
|
||||||
|
// If the user just clicked off the notification, we don't want to bother them again for quite a while.
|
||||||
|
if (this.notificationHasStartedUpdate)
|
||||||
|
{
|
||||||
|
this.nextUpdateCheckTime = DateTime.Now + TimeBetweenUpdateChecks;
|
||||||
|
Log.Verbose("User started update, next check at {Time}", this.nextUpdateCheckTime);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.nextUpdateCheckTime = DateTime.Now + TimeBetweenUpdateChecksIfDismissed;
|
||||||
|
Log.Verbose("User dismissed update notification, next check at {Time}", this.nextUpdateCheckTime);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return this.updateNotification!;
|
return this.updateNotification!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void KickOffAutoUpdates(ICollection<AvailablePluginUpdate> updatablePlugins)
|
private void KickOffAutoUpdates(ICollection<AvailablePluginUpdate> updatablePlugins, IActiveNotification? notification = null)
|
||||||
{
|
{
|
||||||
this.autoUpdateTask =
|
this.autoUpdateTask =
|
||||||
Task.Run(() => this.RunAutoUpdates(updatablePlugins))
|
Task.Run(() => this.RunAutoUpdates(updatablePlugins, notification))
|
||||||
.ContinueWith(t =>
|
.ContinueWith(t =>
|
||||||
{
|
{
|
||||||
if (t.IsFaulted)
|
if (t.IsFaulted)
|
||||||
|
|
@ -252,31 +289,29 @@ internal class AutoUpdateManager : IServiceType
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RunAutoUpdates(ICollection<AvailablePluginUpdate> updatablePlugins)
|
private async Task RunAutoUpdates(ICollection<AvailablePluginUpdate> updatablePlugins, IActiveNotification? notification = null)
|
||||||
{
|
{
|
||||||
Log.Information("Found {UpdatablePluginsCount} plugins to update", updatablePlugins.Count);
|
Log.Information("Found {UpdatablePluginsCount} plugins to update", updatablePlugins.Count);
|
||||||
|
|
||||||
if (updatablePlugins.Count == 0)
|
if (updatablePlugins.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var notification = this.GetBaseNotification(new Notification
|
notification ??= this.GetBaseNotification(new Notification());
|
||||||
{
|
notification.Title = Locs.NotificationTitleUpdatingPlugins;
|
||||||
Title = Locs.NotificationTitleUpdatingPlugins,
|
notification.Content = Locs.NotificationContentPreparingToUpdate(updatablePlugins.Count);
|
||||||
Content = Locs.NotificationContentPreparingToUpdate(updatablePlugins.Count),
|
notification.Type = NotificationType.Info;
|
||||||
Type = NotificationType.Info,
|
notification.InitialDuration = TimeSpan.MaxValue;
|
||||||
InitialDuration = TimeSpan.MaxValue,
|
notification.ShowIndeterminateIfNoExpiry = false;
|
||||||
ShowIndeterminateIfNoExpiry = false,
|
notification.UserDismissable = false;
|
||||||
UserDismissable = false,
|
notification.Progress = 0;
|
||||||
Progress = 0,
|
notification.Icon = INotificationIcon.From(FontAwesomeIcon.Download);
|
||||||
Icon = INotificationIcon.From(FontAwesomeIcon.Download),
|
notification.Minimized = false;
|
||||||
Minimized = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
var progress = new Progress<PluginManager.PluginUpdateProgress>();
|
var progress = new Progress<PluginManager.PluginUpdateProgress>();
|
||||||
progress.ProgressChanged += (_, progress) =>
|
progress.ProgressChanged += (_, updateProgress) =>
|
||||||
{
|
{
|
||||||
notification.Content = Locs.NotificationContentUpdating(progress.CurrentPluginManifest.Name);
|
notification.Content = Locs.NotificationContentUpdating(updateProgress.CurrentPluginManifest.Name);
|
||||||
notification.Progress = (float)progress.PluginsProcessed / progress.TotalPlugins;
|
notification.Progress = (float)updateProgress.PluginsProcessed / updateProgress.TotalPlugins;
|
||||||
};
|
};
|
||||||
|
|
||||||
var pluginStates = await this.pluginManager.UpdatePluginsAsync(updatablePlugins, this.isDryRun.Value, true, progress);
|
var pluginStates = await this.pluginManager.UpdatePluginsAsync(updatablePlugins, this.isDryRun.Value, true, progress);
|
||||||
|
|
@ -288,11 +323,7 @@ internal class AutoUpdateManager : IServiceType
|
||||||
notification.DrawActions += _ =>
|
notification.DrawActions += _ =>
|
||||||
{
|
{
|
||||||
ImGuiHelpers.ScaledDummy(2);
|
ImGuiHelpers.ScaledDummy(2);
|
||||||
if (DalamudComponents.PrimaryButton(Locs.NotificationButtonOpenPluginInstaller))
|
DrawOpenInstallerNotificationButton(true, PluginInstallerOpenKind.InstalledPlugins, notification);
|
||||||
{
|
|
||||||
Service<DalamudInterface>.Get().OpenPluginInstaller();
|
|
||||||
notification.DismissNow();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the notification to show the final state
|
// Update the notification to show the final state
|
||||||
|
|
@ -328,6 +359,8 @@ internal class AutoUpdateManager : IServiceType
|
||||||
{
|
{
|
||||||
if (updatablePlugins.Count == 0)
|
if (updatablePlugins.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
this.notificationHasStartedUpdate = false;
|
||||||
|
|
||||||
var notification = this.GetBaseNotification(new Notification
|
var notification = this.GetBaseNotification(new Notification
|
||||||
{
|
{
|
||||||
|
|
@ -340,23 +373,22 @@ internal class AutoUpdateManager : IServiceType
|
||||||
Icon = INotificationIcon.From(FontAwesomeIcon.Download),
|
Icon = INotificationIcon.From(FontAwesomeIcon.Download),
|
||||||
});
|
});
|
||||||
|
|
||||||
notification.DrawActions += _ =>
|
void DrawNotificationContent(INotificationDrawArgs args)
|
||||||
{
|
{
|
||||||
ImGuiHelpers.ScaledDummy(2);
|
ImGuiHelpers.ScaledDummy(2);
|
||||||
|
|
||||||
if (DalamudComponents.PrimaryButton(Locs.NotificationButtonUpdate))
|
if (DalamudComponents.PrimaryButton(Locs.NotificationButtonUpdate))
|
||||||
{
|
{
|
||||||
this.KickOffAutoUpdates(updatablePlugins);
|
notification.DrawActions -= DrawNotificationContent;
|
||||||
notification.DismissNow();
|
this.KickOffAutoUpdates(updatablePlugins, notification);
|
||||||
|
this.notificationHasStartedUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (DalamudComponents.SecondaryButton(Locs.NotificationButtonOpenPluginInstaller))
|
DrawOpenInstallerNotificationButton(false, PluginInstallerOpenKind.UpdateablePlugins, notification);
|
||||||
{
|
}
|
||||||
Service<DalamudInterface>.Get().OpenPluginInstaller();
|
|
||||||
notification.DismissNow();
|
notification.DrawActions += DrawNotificationContent;
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<AvailablePluginUpdate> GetAvailablePluginUpdates(UpdateListingRestriction restriction)
|
private List<AvailablePluginUpdate> GetAvailablePluginUpdates(UpdateListingRestriction restriction)
|
||||||
|
|
@ -440,18 +472,24 @@ internal class AutoUpdateManager : IServiceType
|
||||||
public static string NotificationContentUpdatesFailedMinimized => Loc.Localize("AutoUpdateUpdatesFailedContentMinimized", "Plugins failed to update.");
|
public static string NotificationContentUpdatesFailedMinimized => Loc.Localize("AutoUpdateUpdatesFailedContentMinimized", "Plugins failed to update.");
|
||||||
|
|
||||||
public static string NotificationContentUpdatesAvailable(int numUpdates)
|
public static string NotificationContentUpdatesAvailable(int numUpdates)
|
||||||
=> string.Format(Loc.Localize("AutoUpdateUpdatesAvailableContent", "There are {0} plugins that can be updated."), numUpdates);
|
=> numUpdates == 1 ?
|
||||||
|
Loc.Localize("AutoUpdateUpdatesAvailableContentSingular", "There is a plugin that can be updated.") :
|
||||||
|
string.Format(Loc.Localize("AutoUpdateUpdatesAvailableContentPlural", "There are {0} plugins that can be updated."), numUpdates);
|
||||||
|
|
||||||
public static string NotificationContentUpdatesAvailableMinimized(int numUpdates)
|
public static string NotificationContentUpdatesAvailableMinimized(int numUpdates)
|
||||||
=> string.Format(Loc.Localize("AutoUpdateUpdatesAvailableContent", "{0} updates available."), numUpdates);
|
=> numUpdates == 1 ?
|
||||||
|
Loc.Localize("AutoUpdateUpdatesAvailableContentMinimizedSingular", "1 plugin update available") :
|
||||||
|
string.Format(Loc.Localize("AutoUpdateUpdatesAvailableContentMinimizedPlural", "{0} plugin updates available"), numUpdates);
|
||||||
|
|
||||||
public static string NotificationContentPreparingToUpdate(int numPlugins)
|
public static string NotificationContentPreparingToUpdate(int numPlugins)
|
||||||
=> string.Format(Loc.Localize("AutoUpdatePreparingToUpdate", "Preparing to update {0} plugins..."), numPlugins);
|
=> numPlugins == 1 ?
|
||||||
|
Loc.Localize("AutoUpdatePreparingToUpdateSingular", "Preparing to update 1 plugin...") :
|
||||||
|
string.Format(Loc.Localize("AutoUpdatePreparingToUpdatePlural", "Preparing to update {0} plugins..."), numPlugins);
|
||||||
|
|
||||||
public static string NotificationContentUpdating(string name)
|
public static string NotificationContentUpdating(string name)
|
||||||
=> string.Format(Loc.Localize("AutoUpdateUpdating", "Updating {0}..."), name);
|
=> string.Format(Loc.Localize("AutoUpdateUpdating", "Updating {0}..."), name);
|
||||||
|
|
||||||
public static string NotificationContentFailedPlugins(IEnumerable<string> failedPlugins)
|
public static string NotificationContentFailedPlugins(IEnumerable<string> failedPlugins)
|
||||||
=> string.Format(Loc.Localize("AutoUpdateFailedPlugins", "Failed plugins: {0}"), string.Join(", ", failedPlugins));
|
=> string.Format(Loc.Localize("AutoUpdateFailedPlugins", "Failed plugin(s): {0}"), string.Join(", ", failedPlugins));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1003,9 +1003,6 @@ internal class PluginManager : IInternalDisposableService
|
||||||
if (plugin.InstalledPlugin.IsDev)
|
if (plugin.InstalledPlugin.IsDev)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!plugin.InstalledPlugin.IsWantedByAnyProfile)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (plugin.InstalledPlugin.Manifest.ScheduledForDeletion)
|
if (plugin.InstalledPlugin.Manifest.ScheduledForDeletion)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -483,6 +483,7 @@ internal class LocalPlugin : IDisposable
|
||||||
{
|
{
|
||||||
case PluginState.Unloaded:
|
case PluginState.Unloaded:
|
||||||
throw new InvalidPluginOperationException($"Unable to unload {this.Name}, already unloaded");
|
throw new InvalidPluginOperationException($"Unable to unload {this.Name}, already unloaded");
|
||||||
|
case PluginState.DependencyResolutionFailed:
|
||||||
case PluginState.UnloadError:
|
case PluginState.UnloadError:
|
||||||
if (!this.IsDev)
|
if (!this.IsDev)
|
||||||
{
|
{
|
||||||
|
|
@ -501,31 +502,42 @@ internal class LocalPlugin : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
this.State = PluginState.Unloading;
|
this.State = PluginState.Unloading;
|
||||||
Log.Information($"Unloading {this.DllFile.Name}");
|
Log.Information("Unloading {PluginName}", this.InternalName);
|
||||||
|
|
||||||
if (this.manifest.CanUnloadAsync || framework == null)
|
try
|
||||||
this.instance?.Dispose();
|
|
||||||
else
|
|
||||||
await framework.RunOnFrameworkThread(() => this.instance?.Dispose());
|
|
||||||
|
|
||||||
this.instance = null;
|
|
||||||
this.UnloadAndDisposeState();
|
|
||||||
|
|
||||||
if (!reloading)
|
|
||||||
{
|
{
|
||||||
if (waitBeforeLoaderDispose && this.loader != null)
|
if (this.manifest.CanUnloadAsync || framework == null)
|
||||||
await Task.Delay(configuration.PluginWaitBeforeFree ?? PluginManager.PluginWaitBeforeFreeDefault);
|
this.instance?.Dispose();
|
||||||
this.loader?.Dispose();
|
else
|
||||||
this.loader = null;
|
await framework.RunOnFrameworkThread(() => this.instance?.Dispose());
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
this.State = PluginState.UnloadError;
|
||||||
|
Log.Error(e, "Could not unload {PluginName}, error in plugin dispose", this.InternalName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
this.instance = null;
|
||||||
|
this.UnloadAndDisposeState();
|
||||||
|
|
||||||
|
if (!reloading)
|
||||||
|
{
|
||||||
|
if (waitBeforeLoaderDispose && this.loader != null)
|
||||||
|
await Task.Delay(configuration.PluginWaitBeforeFree ?? PluginManager.PluginWaitBeforeFreeDefault);
|
||||||
|
this.loader?.Dispose();
|
||||||
|
this.loader = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.State = PluginState.Unloaded;
|
this.State = PluginState.Unloaded;
|
||||||
Log.Information($"Finished unloading {this.DllFile.Name}");
|
Log.Information("Finished unloading {PluginName}", this.InternalName);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
this.State = PluginState.UnloadError;
|
this.State = PluginState.UnloadError;
|
||||||
Log.Error(ex, $"Error while unloading {this.Name}");
|
Log.Error(ex, "Error while unloading {PluginName}", this.InternalName);
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,11 @@ using Lumina.Excel.GeneratedSheets;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using TerraFX.Interop.Windows;
|
using TerraFX.Interop.Windows;
|
||||||
using Windows.Win32.Storage.FileSystem;
|
using Windows.Win32.Storage.FileSystem;
|
||||||
|
using Windows.Win32.System.Memory;
|
||||||
|
using Windows.Win32.System.Ole;
|
||||||
|
|
||||||
|
using HWND = Windows.Win32.Foundation.HWND;
|
||||||
|
using Win32_PInvoke = Windows.Win32.PInvoke;
|
||||||
|
|
||||||
namespace Dalamud.Utility;
|
namespace Dalamud.Utility;
|
||||||
|
|
||||||
|
|
@ -769,6 +774,69 @@ public static class Util
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copy files to the clipboard as if they were copied in Explorer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="paths">Full paths to files to be copied.</param>
|
||||||
|
/// <returns>Returns true on success.</returns>
|
||||||
|
internal static unsafe bool CopyFilesToClipboard(IEnumerable<string> paths)
|
||||||
|
{
|
||||||
|
var pathBytes = paths
|
||||||
|
.Select(Encoding.Unicode.GetBytes)
|
||||||
|
.ToArray();
|
||||||
|
var pathBytesSize = pathBytes
|
||||||
|
.Select(bytes => bytes.Length)
|
||||||
|
.Sum();
|
||||||
|
var sizeWithTerminators = pathBytesSize + (pathBytes.Length * 2);
|
||||||
|
|
||||||
|
var dropFilesSize = sizeof(DROPFILES);
|
||||||
|
var hGlobal = Win32_PInvoke.GlobalAlloc_SafeHandle(
|
||||||
|
GLOBAL_ALLOC_FLAGS.GHND,
|
||||||
|
// struct size + size of encoded strings + null terminator for each
|
||||||
|
// string + two null terminators for end of list
|
||||||
|
(uint)(dropFilesSize + sizeWithTerminators + 4));
|
||||||
|
var dropFiles = (DROPFILES*)Win32_PInvoke.GlobalLock(hGlobal);
|
||||||
|
|
||||||
|
*dropFiles = default;
|
||||||
|
dropFiles->fWide = true;
|
||||||
|
dropFiles->pFiles = (uint)dropFilesSize;
|
||||||
|
|
||||||
|
var pathLoc = (byte*)((nint)dropFiles + dropFilesSize);
|
||||||
|
foreach (var bytes in pathBytes)
|
||||||
|
{
|
||||||
|
// copy the encoded strings
|
||||||
|
for (var i = 0; i < bytes.Length; i++)
|
||||||
|
{
|
||||||
|
pathLoc![i] = bytes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// null terminate
|
||||||
|
pathLoc![bytes.Length] = 0;
|
||||||
|
pathLoc[bytes.Length + 1] = 0;
|
||||||
|
pathLoc += bytes.Length + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// double null terminator for end of list
|
||||||
|
for (var i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
pathLoc![i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Win32_PInvoke.GlobalUnlock(hGlobal);
|
||||||
|
|
||||||
|
if (Win32_PInvoke.OpenClipboard(HWND.Null))
|
||||||
|
{
|
||||||
|
Win32_PInvoke.SetClipboardData(
|
||||||
|
(uint)CLIPBOARD_FORMAT.CF_HDROP,
|
||||||
|
hGlobal);
|
||||||
|
Win32_PInvoke.CloseClipboard();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
hGlobal.Dispose();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static void ShowSpanProperty(ulong addr, IList<string> path, PropertyInfo p, object obj)
|
private static void ShowSpanProperty(ulong addr, IList<string> path, PropertyInfo p, object obj)
|
||||||
{
|
{
|
||||||
var objType = obj.GetType();
|
var objType = obj.GetType();
|
||||||
|
|
|
||||||
|
|
@ -965,7 +965,7 @@ int main() {
|
||||||
const TASKDIALOG_BUTTON buttons[]{
|
const TASKDIALOG_BUTTON buttons[]{
|
||||||
{IdButtonRestart, L"Restart\nRestart the game with the above-selected option."},
|
{IdButtonRestart, L"Restart\nRestart the game with the above-selected option."},
|
||||||
{IdButtonSaveTsPack, L"Save Troubleshooting Info\nSave a .tspack file containing information about this crash for analysis."},
|
{IdButtonSaveTsPack, L"Save Troubleshooting Info\nSave a .tspack file containing information about this crash for analysis."},
|
||||||
{IdButtonExit, L"Exit\nExit the game."},
|
{IdButtonExit, L"Exit\nExit without doing anything."},
|
||||||
};
|
};
|
||||||
|
|
||||||
config.cbSize = sizeof(config);
|
config.cbSize = sizeof(config);
|
||||||
|
|
@ -1060,12 +1060,21 @@ int main() {
|
||||||
pProgressDialog->Release();
|
pProgressDialog->Release();
|
||||||
pProgressDialog = NULL;
|
pProgressDialog = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto kill_game = [&] { TerminateProcess(g_hProcess, exinfo.ExceptionRecord.ExceptionCode); };
|
||||||
|
|
||||||
if (shutup) {
|
if (shutup) {
|
||||||
TerminateProcess(g_hProcess, exinfo.ExceptionRecord.ExceptionCode);
|
kill_game();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !_DEBUG
|
||||||
|
// In release mode, we can't resume the game, so just kill it. It's not safe to keep it running, as we
|
||||||
|
// don't know what state it's in and it may have crashed off-thread.
|
||||||
|
// Additionally, if the main thread crashed, Windows will show the ANR dialog, which will block our dialog.
|
||||||
|
kill_game();
|
||||||
|
#endif
|
||||||
|
|
||||||
int nButtonPressed = 0, nRadioButton = 0;
|
int nButtonPressed = 0, nRadioButton = 0;
|
||||||
if (FAILED(TaskDialogIndirect(&config, &nButtonPressed, &nRadioButton, nullptr))) {
|
if (FAILED(TaskDialogIndirect(&config, &nButtonPressed, &nRadioButton, nullptr))) {
|
||||||
SetEvent(exinfo.hEventHandle);
|
SetEvent(exinfo.hEventHandle);
|
||||||
|
|
@ -1073,7 +1082,7 @@ int main() {
|
||||||
switch (nButtonPressed) {
|
switch (nButtonPressed) {
|
||||||
case IdButtonRestart:
|
case IdButtonRestart:
|
||||||
{
|
{
|
||||||
TerminateProcess(g_hProcess, exinfo.ExceptionRecord.ExceptionCode);
|
kill_game();
|
||||||
restart_game_using_injector(nRadioButton, *launcherArgs);
|
restart_game_using_injector(nRadioButton, *launcherArgs);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -1081,7 +1090,7 @@ int main() {
|
||||||
if (attemptResume)
|
if (attemptResume)
|
||||||
SetEvent(exinfo.hEventHandle);
|
SetEvent(exinfo.hEventHandle);
|
||||||
else
|
else
|
||||||
TerminateProcess(g_hProcess, exinfo.ExceptionRecord.ExceptionCode);
|
kill_game();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue