CmdPal: Fix SUI crash ; Allow extensions to be disabled (#38040)

Both `TopLevelCommandItemWrapper` and `TopLevelViewModel` were really the same thing. The latter was from an earlier prototype, and the former is a more correct, safer abstraction. We really should have only ever used the former, but alas, we only used it for the SUI, and it piggy-backed off the latter, and that meant the latter's bugs became the former's.


tldr: I made the icon access safe in the SUI. 

And while I was doing this, because we now have a cleaner VM abstraction here in the host, we can actually cleanly disable extensions, because the `CommandProviderWrapper` knows which `ViewModel`s it made. 

Closes https://github.com/zadjii-msft/PowerToys/issues/426
Closes https://github.com/zadjii-msft/PowerToys/issues/478
Closes https://github.com/zadjii-msft/PowerToys/issues/577
This commit is contained in:
Mike Griese
2025-03-20 15:36:10 -05:00
committed by GitHub
parent 57cbcc2c3e
commit 14919dff10
34 changed files with 590 additions and 528 deletions

View File

@@ -6,6 +6,8 @@ using ManagedCommon;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Windows.Foundation;
namespace Microsoft.CmdPal.UI.ViewModels;
@@ -20,9 +22,9 @@ public sealed class CommandProviderWrapper
private readonly TaskScheduler _taskScheduler;
public ICommandItem[] TopLevelItems { get; private set; } = [];
public TopLevelViewModel[] TopLevelItems { get; private set; } = [];
public IFallbackCommandItem[] FallbackItems { get; private set; } = [];
public TopLevelViewModel[] FallbackItems { get; private set; } = [];
public string DisplayName { get; private set; } = string.Empty;
@@ -38,7 +40,13 @@ public sealed class CommandProviderWrapper
public CommandSettingsViewModel? Settings { get; private set; }
public string ProviderId => $"{Extension?.PackageFamilyName ?? string.Empty}/{Id}";
public string ProviderId
{
get
{
return string.IsNullOrEmpty(Extension?.ExtensionUniqueId) ? Id : Extension.ExtensionUniqueId;
}
}
public CommandProviderWrapper(ICommandProvider provider, TaskScheduler mainThread)
{
@@ -105,13 +113,25 @@ public sealed class CommandProviderWrapper
isValid = true;
}
public async Task LoadTopLevelCommands()
private ProviderSettings GetProviderSettings(SettingsModel settings)
{
return settings.GetProviderSettings(this);
}
public async Task LoadTopLevelCommands(IServiceProvider serviceProvider, WeakReference<IPageContext> pageContext)
{
if (!isValid)
{
return;
}
var settings = serviceProvider.GetService<SettingsModel>()!;
if (!GetProviderSettings(settings).IsEnabled)
{
return;
}
ICommandItem[]? commands = null;
IFallbackCommandItem[]? fallbacks = null;
@@ -119,7 +139,7 @@ public sealed class CommandProviderWrapper
{
var model = _commandProvider.Unsafe!;
var t = new Task<ICommandItem[]>(model.TopLevelCommands);
Task<ICommandItem[]> t = new(model.TopLevelCommands);
t.Start();
commands = await t.ConfigureAwait(false);
@@ -134,6 +154,8 @@ public sealed class CommandProviderWrapper
Settings = new(model.Settings, this, _taskScheduler);
Settings.InitializeProperties();
InitializeCommands(commands, fallbacks, serviceProvider, pageContext);
Logger.LogDebug($"Loaded commands from {DisplayName} ({ProviderId})");
}
catch (Exception e)
@@ -142,15 +164,33 @@ public sealed class CommandProviderWrapper
Logger.LogError($"Extension was {Extension!.PackageFamilyName}");
Logger.LogError(e.ToString());
}
}
private void InitializeCommands(ICommandItem[] commands, IFallbackCommandItem[] fallbacks, IServiceProvider serviceProvider, WeakReference<IPageContext> pageContext)
{
var settings = serviceProvider.GetService<SettingsModel>()!;
Func<ICommandItem?, bool, TopLevelViewModel> makeAndAdd = (ICommandItem? i, bool fallback) =>
{
CommandItemViewModel commandItemViewModel = new(new(i), pageContext);
TopLevelViewModel topLevelViewModel = new(commandItemViewModel, fallback, ExtensionHost, ProviderId, settings, serviceProvider);
topLevelViewModel.ItemViewModel.SlowInitializeProperties();
return topLevelViewModel;
};
if (commands != null)
{
TopLevelItems = commands;
TopLevelItems = commands
.Select(c => makeAndAdd(c, false))
.ToArray();
}
if (fallbacks != null)
{
FallbackItems = fallbacks;
FallbackItems = fallbacks
.Select(c => makeAndAdd(c, true))
.ToArray();
}
}