From fa4bc0a39738d453f213da12b186010ae0193ff6 Mon Sep 17 00:00:00 2001 From: Michael Jolley Date: Thu, 29 Jan 2026 21:42:53 -0600 Subject: [PATCH] Working on DI --- .../Services/IExtensionService.cs | 37 -- .../CommandPaletteHost.cs | 2 +- .../CommandProviderWrapper.cs | 2 +- .../Models/BuiltInExtensionService.cs | 295 ++++++++++++ .../Models/ExtensionService.cs | 452 ------------------ .../Models/ExtensionWrapper.cs | 2 +- .../Models/WinRTExtensionService.cs | 9 + .../ProviderSettingsViewModel.cs | 3 +- .../Services/IExtensionService.cs | 28 ++ .../Services/IExtensionWrapper.cs | 5 +- .../TopLevelCommandManager.cs | 392 +++------------ .../cmdpal/Microsoft.CmdPal.UI/App.xaml.cs | 3 +- .../Microsoft.CmdPal.UI/MainWindow.xaml.cs | 12 +- .../PowerToysRootPageService.cs | 40 +- 14 files changed, 439 insertions(+), 843 deletions(-) delete mode 100644 src/modules/cmdpal/Microsoft.CmdPal.Common/Services/IExtensionService.cs create mode 100644 src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/BuiltInExtensionService.cs delete mode 100644 src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionService.cs create mode 100644 src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/WinRTExtensionService.cs create mode 100644 src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/IExtensionService.cs rename src/modules/cmdpal/{Microsoft.CmdPal.Common => Microsoft.CmdPal.UI.ViewModels}/Services/IExtensionWrapper.cs (96%) diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Common/Services/IExtensionService.cs b/src/modules/cmdpal/Microsoft.CmdPal.Common/Services/IExtensionService.cs deleted file mode 100644 index 72ea470e54..0000000000 --- a/src/modules/cmdpal/Microsoft.CmdPal.Common/Services/IExtensionService.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using System.Threading.Tasks; -using Windows.Foundation; - -namespace Microsoft.CmdPal.Common.Services; - -public interface IExtensionService -{ - Task> GetInstalledExtensionsAsync(bool includeDisabledExtensions = false); - - // Task> GetInstalledHomeWidgetPackageFamilyNamesAsync(bool includeDisabledExtensions = false); - Task> GetInstalledExtensionsAsync(Microsoft.CommandPalette.Extensions.ProviderType providerType, bool includeDisabledExtensions = false); - - IExtensionWrapper? GetInstalledExtension(string extensionUniqueId); - - Task SignalStopExtensionsAsync(); - - event TypedEventHandler>? OnExtensionAdded; - - event TypedEventHandler>? OnExtensionRemoved; - - void EnableExtension(string extensionUniqueId); - - void DisableExtension(string extensionUniqueId); - - ///// - ///// Gets a boolean indicating whether the extension was disabled due to the corresponding Windows optional feature - ///// being absent from the machine or in an unknown state. - ///// - ///// The out of proc extension object - ///// True only if the extension was disabled. False otherwise. - // public Task DisableExtensionIfWindowsFeatureNotAvailable(IExtensionWrapper extension); -} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandPaletteHost.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandPaletteHost.cs index 08d7959252..a6f926e6ec 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandPaletteHost.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandPaletteHost.cs @@ -2,7 +2,7 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.CmdPal.Common.Services; +using Microsoft.CmdPal.UI.ViewModels.Services; using Microsoft.CommandPalette.Extensions; using Microsoft.Extensions.Logging; diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandProviderWrapper.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandProviderWrapper.cs index 9f34dc7eeb..3dec4eed0b 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandProviderWrapper.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandProviderWrapper.cs @@ -2,8 +2,8 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.CmdPal.Common.Services; using Microsoft.CmdPal.UI.ViewModels.Models; +using Microsoft.CmdPal.UI.ViewModels.Services; using Microsoft.CommandPalette.Extensions; using Microsoft.Extensions.Logging; diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/BuiltInExtensionService.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/BuiltInExtensionService.cs new file mode 100644 index 0000000000..db9740b629 --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/BuiltInExtensionService.cs @@ -0,0 +1,295 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using Microsoft.CmdPal.UI.ViewModels.Services; +using Microsoft.CommandPalette.Extensions; +using Microsoft.Extensions.Logging; +using Windows.Foundation; + +namespace Microsoft.CmdPal.UI.ViewModels.Models; + +public partial class BuiltInExtensionService : IExtensionService, IDisposable +{ + public event TypedEventHandler>? OnCommandProviderAdded; + + public event TypedEventHandler>? OnCommandProviderRemoved; + + public event TypedEventHandler>? OnCommandsAdded; + + public event TypedEventHandler>? OnCommandsRemoved; + + private readonly ILogger _logger; + private readonly TaskScheduler _taskScheduler; + private readonly HotkeyManager _hotkeyManager; + private readonly AliasManager _aliasManager; + private readonly SettingsService _settingsService; + + private readonly IEnumerable _commandProviders; + private readonly Lock _commandProvidersLock = new(); + + private readonly List _builtInCommandWrappers = []; + private readonly SemaphoreSlim _getBuiltInCommandWrappersLock = new(1, 1); + + private readonly List _enabledBuiltInCommandWrappers = []; + private readonly SemaphoreSlim _getEnabledBuiltInCommandWrappersLock = new(1, 1); + + private readonly List _topLevelCommands = []; + private readonly SemaphoreSlim _getTopLevelCommandsLock = new(1, 1); + + private WeakReference? _weakPageContext; + + private bool isLoaded; + + public BuiltInExtensionService( + IEnumerable commandProviders, + TaskScheduler taskScheduler, + HotkeyManager hotkeyManager, + AliasManager aliasManager, + SettingsService settingsService, + ILogger logger) + { + _logger = logger; + _taskScheduler = taskScheduler; + _hotkeyManager = hotkeyManager; + _aliasManager = aliasManager; + _settingsService = settingsService; + _commandProviders = commandProviders; + } + + public async Task> GetCommandProviderWrappersAsync(WeakReference weakPageContext, bool includeDisabledExtensions = false) + { + _weakPageContext = weakPageContext; + + if (!isLoaded) + { + await LoadBuiltInsAsync(); + } + + if (includeDisabledExtensions) + { + await _getBuiltInCommandWrappersLock.WaitAsync(); + try + { + return _builtInCommandWrappers; + } + finally + { + _getBuiltInCommandWrappersLock.Release(); + } + } + else + { + await _getEnabledBuiltInCommandWrappersLock.WaitAsync(); + try + { + return _enabledBuiltInCommandWrappers; + } + finally + { + _getEnabledBuiltInCommandWrappersLock.Release(); + } + } + } + + public async Task> GetTopLevelCommandsAsync() + { + await _getTopLevelCommandsLock.WaitAsync(); + try + { + return _topLevelCommands; + } + finally + { + _getTopLevelCommandsLock.Release(); + } + } + + public async Task SignalStopExtensionsAsync() + { + // We're buil-in. There's no stopping us. + } + + public async Task EnableProviderAsync(string providerId) + { + await _getEnabledBuiltInCommandWrappersLock.WaitAsync(); + try + { + if (_enabledBuiltInCommandWrappers.Any(wrapper => wrapper.Id.Equals(providerId, StringComparison.Ordinal))) + { + return; + } + } + finally + { + _getEnabledBuiltInCommandWrappersLock.Release(); + } + + await _getBuiltInCommandWrappersLock.WaitAsync(); + try + { + CommandProviderWrapper? wrapper = _builtInCommandWrappers.FirstOrDefault(wrapper => wrapper.Id.Equals(providerId, StringComparison.Ordinal)); + + if (wrapper != null) + { + await _getEnabledBuiltInCommandWrappersLock.WaitAsync(); + try + { + _enabledBuiltInCommandWrappers.Add(wrapper); + } + finally + { + _getEnabledBuiltInCommandWrappersLock.Release(); + } + + var commands = await LoadTopLevelCommandsFromProvider(wrapper); + await _getTopLevelCommandsLock.WaitAsync(); + try + { + foreach (var c in commands) + { + _topLevelCommands.Add(c); + } + } + finally + { + _getTopLevelCommandsLock.Release(); + } + + OnCommandsAdded?.Invoke(wrapper, commands); + } + } + finally + { + _getBuiltInCommandWrappersLock.Release(); + } + } + + public async Task DisableProviderAsync(string providerId) + { + await _getEnabledBuiltInCommandWrappersLock.WaitAsync(); + try + { + var wrapper = _enabledBuiltInCommandWrappers.FirstOrDefault(wrapper => wrapper.Id.Equals(providerId, StringComparison.Ordinal)); + + if (wrapper != null) + { + _enabledBuiltInCommandWrappers.Remove(wrapper); + + await _getTopLevelCommandsLock.WaitAsync(); + try + { + var commands = _topLevelCommands.Where(command => command.CommandProviderId.Equals(wrapper.Id, StringComparison.Ordinal)); + foreach (var c in commands) + { + _topLevelCommands.Remove(c); + } + + OnCommandsRemoved?.Invoke(wrapper, commands); + } + finally + { + _getTopLevelCommandsLock.Release(); + } + } + } + finally + { + _getEnabledBuiltInCommandWrappersLock.Release(); + } + } + + private async Task LoadBuiltInsAsync() + { + var s = new Stopwatch(); + s.Start(); + + var builtInProviders = _commandProviders; + foreach (var provider in builtInProviders) + { + CommandProviderWrapper wrapper = new(provider, _taskScheduler, _hotkeyManager, _aliasManager, _logger); + lock (_getBuiltInCommandWrappersLock) + { + _builtInCommandWrappers.Add(wrapper); + } + + if (wrapper.IsActive) + { + lock (_getEnabledBuiltInCommandWrappersLock) + { + _enabledBuiltInCommandWrappers.Add(wrapper); + } + + var commands = await LoadTopLevelCommandsFromProvider(wrapper); + lock (_getTopLevelCommandsLock) + { + foreach (var c in commands) + { + _topLevelCommands.Add(c); + } + } + } + } + + s.Stop(); + + isLoaded = true; + + Log_LoadingBuiltInsTook(s.ElapsedMilliseconds); + } + + private async Task> LoadTopLevelCommandsFromProvider(CommandProviderWrapper commandProvider) + { + await commandProvider.LoadTopLevelCommands(_settingsService, _weakPageContext!); + + var commands = await Task.Factory.StartNew( + () => + { + List commands = []; + foreach (var item in commandProvider.TopLevelItems) + { + commands.Add(item); + } + + foreach (var item in commandProvider.FallbackItems) + { + if (item.IsEnabled) + { + commands.Add(item); + } + } + + return commands; + }, + CancellationToken.None, + TaskCreationOptions.None, + _taskScheduler); + + commandProvider.CommandsChanged -= CommandProvider_CommandsChanged; + commandProvider.CommandsChanged += CommandProvider_CommandsChanged; + + return commands; + } + + private void CommandProvider_CommandsChanged(CommandProviderWrapper commandProviderWrapper, IItemsChangedEventArgs args) + { + } + + public void Dispose() + { + _getBuiltInCommandWrappersLock.Dispose(); + _getEnabledBuiltInCommandWrappersLock.Dispose(); + _getTopLevelCommandsLock.Dispose(); + GC.SuppressFinalize(this); + } + + private void Test() + { + OnCommandProviderAdded?.Invoke(null!, null!); + OnCommandProviderRemoved?.Invoke(null!, null!); + } + + [LoggerMessage(Level = LogLevel.Debug, Message = "Loading built-ins took {elapsedMs}ms")] + partial void Log_LoadingBuiltInsTook(long elapsedMs); +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionService.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionService.cs deleted file mode 100644 index 3febd19490..0000000000 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionService.cs +++ /dev/null @@ -1,452 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.CmdPal.Common.Services; -using Microsoft.CommandPalette.Extensions; -using Microsoft.Extensions.Logging; -using Windows.ApplicationModel; -using Windows.ApplicationModel.AppExtensions; -using Windows.Foundation; -using Windows.Foundation.Collections; - -namespace Microsoft.CmdPal.UI.ViewModels.Models; - -public partial class ExtensionService : IExtensionService, IDisposable -{ - public event TypedEventHandler>? OnExtensionAdded; - - public event TypedEventHandler>? OnExtensionRemoved; - - private static readonly PackageCatalog _catalog = PackageCatalog.OpenForCurrentUser(); - private static readonly Lock _lock = new(); - private readonly SemaphoreSlim _getInstalledExtensionsLock = new(1, 1); - private readonly SemaphoreSlim _getInstalledWidgetsLock = new(1, 1); - private readonly ILogger _logger; - - // private readonly ILocalSettingsService _localSettingsService; - private bool _disposedValue; - - private const string CreateInstanceProperty = "CreateInstance"; - private const string ClassIdProperty = "@ClassId"; - - private static readonly List _installedExtensions = []; - private static readonly List _enabledExtensions = []; - - public ExtensionService(ILogger logger) - { - _logger = logger; - - _catalog.PackageInstalling += Catalog_PackageInstalling; - _catalog.PackageUninstalling += Catalog_PackageUninstalling; - _catalog.PackageUpdating += Catalog_PackageUpdating; - - //// These two were an investigation into getting updates when a package - //// gets redeployed from VS. Neither get raised (nor do the above) - //// _catalog.PackageStatusChanged += Catalog_PackageStatusChanged; - //// _catalog.PackageStaging += Catalog_PackageStaging; - // _localSettingsService = settingsService; - } - - private void Catalog_PackageInstalling(PackageCatalog sender, PackageInstallingEventArgs args) - { - if (args.IsComplete) - { - lock (_lock) - { - InstallPackageUnderLock(args.Package); - } - } - } - - private void Catalog_PackageUninstalling(PackageCatalog sender, PackageUninstallingEventArgs args) - { - if (args.IsComplete) - { - lock (_lock) - { - UninstallPackageUnderLock(args.Package); - } - } - } - - private void Catalog_PackageUpdating(PackageCatalog sender, PackageUpdatingEventArgs args) - { - if (args.IsComplete) - { - lock (_lock) - { - // Get any extension providers that we previously had from this app - UninstallPackageUnderLock(args.TargetPackage); - - // then add the new ones. - InstallPackageUnderLock(args.TargetPackage); - } - } - } - - private void InstallPackageUnderLock(Package package) - { - var isCmdPalExtensionResult = Task.Run(() => - { - return IsValidCmdPalExtension(package); - }).Result; - var isExtension = isCmdPalExtensionResult.IsExtension; - var extension = isCmdPalExtensionResult.Extension; - if (isExtension && extension is not null) - { - Log_ExtensionInstalled(extension.DisplayName); - - Task.Run(async () => - { - await _getInstalledExtensionsLock.WaitAsync(); - try - { - var wrappers = await CreateWrappersForExtension(extension); - - UpdateExtensionsListsFromWrappers(wrappers); - - OnExtensionAdded?.Invoke(this, wrappers); - } - finally - { - _getInstalledExtensionsLock.Release(); - } - }); - } - } - - private void UninstallPackageUnderLock(Package package) - { - List removedExtensions = []; - foreach (var extension in _installedExtensions) - { - if (extension.PackageFullName == package.Id.FullName) - { - Log_ExtensionUninstalled(extension.PackageDisplayName); - - removedExtensions.Add(extension); - } - } - - Task.Run(async () => - { - await _getInstalledExtensionsLock.WaitAsync(); - try - { - _installedExtensions.RemoveAll(i => removedExtensions.Contains(i)); - _enabledExtensions.RemoveAll(i => removedExtensions.Contains(i)); - - OnExtensionRemoved?.Invoke(this, removedExtensions); - } - finally - { - _getInstalledExtensionsLock.Release(); - } - }); - } - - private static async Task IsValidCmdPalExtension(Package package) - { - var extensions = await AppExtensionCatalog.Open("com.microsoft.commandpalette").FindAllAsync(); - foreach (var extension in extensions) - { - if (package.Id?.FullName == extension.Package?.Id?.FullName) - { - var (cmdPalProvider, classId) = await GetCmdPalExtensionPropertiesAsync(extension); - - return new(cmdPalProvider is not null && classId.Count != 0, extension); - } - } - - return new(false, null); - } - - private static async Task<(IPropertySet? CmdPalProvider, List ClassIds)> GetCmdPalExtensionPropertiesAsync(AppExtension extension) - { - var classIds = new List(); - var properties = await extension.GetExtensionPropertiesAsync(); - - if (properties is null) - { - return (null, classIds); - } - - var cmdPalProvider = GetSubPropertySet(properties, "CmdPalProvider"); - if (cmdPalProvider is null) - { - return (null, classIds); - } - - var activation = GetSubPropertySet(cmdPalProvider, "Activation"); - if (activation is null) - { - return (cmdPalProvider, classIds); - } - - // Handle case where extension creates multiple instances. - classIds.AddRange(GetCreateInstanceList(activation)); - - return (cmdPalProvider, classIds); - } - - private static async Task> GetInstalledAppExtensionsAsync() => await AppExtensionCatalog.Open("com.microsoft.commandpalette").FindAllAsync(); - - public async Task> GetInstalledExtensionsAsync(bool includeDisabledExtensions = false) - { - await _getInstalledExtensionsLock.WaitAsync(); - try - { - if (_installedExtensions.Count == 0) - { - var extensions = await GetInstalledAppExtensionsAsync(); - foreach (var extension in extensions) - { - var wrappers = await CreateWrappersForExtension(extension); - UpdateExtensionsListsFromWrappers(wrappers); - } - } - - return includeDisabledExtensions ? _installedExtensions : _enabledExtensions; - } - finally - { - _getInstalledExtensionsLock.Release(); - } - } - - private static void UpdateExtensionsListsFromWrappers(List wrappers) - { - foreach (var extensionWrapper in wrappers) - { - // var localSettingsService = Application.Current.GetService(); - var extensionUniqueId = extensionWrapper.ExtensionUniqueId; - var isExtensionDisabled = false; // await localSettingsService.ReadSettingAsync(extensionUniqueId + "-ExtensionDisabled"); - - _installedExtensions.Add(extensionWrapper); - if (!isExtensionDisabled) - { - _enabledExtensions.Add(extensionWrapper); - } - - // TelemetryFactory.Get().Log( - // "Extension_ReportInstalled", - // LogLevel.Critical, - // new ReportInstalledExtensionEvent(extensionUniqueId, isEnabled: !isExtensionDisabled)); - } - } - - private async Task> CreateWrappersForExtension(AppExtension extension) - { - var (cmdPalProvider, classIds) = await GetCmdPalExtensionPropertiesAsync(extension); - - if (cmdPalProvider is null || classIds.Count == 0) - { - return []; - } - - List wrappers = []; - foreach (var classId in classIds) - { - var extensionWrapper = CreateExtensionWrapper(extension, cmdPalProvider, classId); - wrappers.Add(extensionWrapper); - } - - return wrappers; - } - - private ExtensionWrapper CreateExtensionWrapper(AppExtension extension, IPropertySet cmdPalProvider, string classId) - { - var extensionWrapper = new ExtensionWrapper(extension, classId); - - var supportedInterfaces = GetSubPropertySet(cmdPalProvider, "SupportedInterfaces"); - if (supportedInterfaces is not null) - { - foreach (var supportedInterface in supportedInterfaces) - { - ProviderType pt; - if (Enum.TryParse(supportedInterface.Key, out pt)) - { - extensionWrapper.AddProviderType(pt); - } - else - { - // log warning that extension declared unsupported extension interface - Log_UndeclaredInterfaceInExtension(extension.DisplayName, supportedInterface.Key); - } - } - } - - return extensionWrapper; - } - - public IExtensionWrapper? GetInstalledExtension(string extensionUniqueId) - { - var extension = _installedExtensions.Where(extension => extension.ExtensionUniqueId.Equals(extensionUniqueId, StringComparison.Ordinal)); - return extension.FirstOrDefault(); - } - - public async Task SignalStopExtensionsAsync() - { - var installedExtensions = await GetInstalledExtensionsAsync(); - foreach (var installedExtension in installedExtensions) - { - Log_DisposingExtension(installedExtension.ExtensionUniqueId); - try - { - if (installedExtension.IsRunning()) - { - installedExtension.SignalDispose(); - } - } - catch (Exception ex) - { - Log_FailedSendingDisposeToExtension(ex, installedExtension.ExtensionUniqueId); - } - } - } - - public async Task> GetInstalledExtensionsAsync(ProviderType providerType, bool includeDisabledExtensions = false) - { - var installedExtensions = await GetInstalledExtensionsAsync(includeDisabledExtensions); - - List filteredExtensions = []; - foreach (var installedExtension in installedExtensions) - { - if (installedExtension.HasProviderType(providerType)) - { - filteredExtensions.Add(installedExtension); - } - } - - return filteredExtensions; - } - - public void Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (!_disposedValue) - { - if (disposing) - { - _getInstalledExtensionsLock.Dispose(); - _getInstalledWidgetsLock.Dispose(); - } - - _disposedValue = true; - } - } - - private static IPropertySet? GetSubPropertySet(IPropertySet propSet, string name) => propSet.TryGetValue(name, out var value) ? value as IPropertySet : null; - - private static object[]? GetSubPropertySetArray(IPropertySet propSet, string name) => propSet.TryGetValue(name, out var value) ? value as object[] : null; - - /// - /// There are cases where the extension creates multiple COM instances. - /// - /// Activation property set object - /// List of ClassId strings associated with the activation property - private static List GetCreateInstanceList(IPropertySet activationPropSet) - { - var propSetList = new List(); - var singlePropertySet = GetSubPropertySet(activationPropSet, CreateInstanceProperty); - if (singlePropertySet is not null) - { - var classId = GetProperty(singlePropertySet, ClassIdProperty); - - // If the instance has a classId as a single string, then it's only supporting a single instance. - if (classId is not null) - { - propSetList.Add(classId); - } - } - else - { - var propertySetArray = GetSubPropertySetArray(activationPropSet, CreateInstanceProperty); - if (propertySetArray is not null) - { - foreach (var prop in propertySetArray) - { - if (prop is not IPropertySet propertySet) - { - continue; - } - - var classId = GetProperty(propertySet, ClassIdProperty); - if (classId is not null) - { - propSetList.Add(classId); - } - } - } - } - - return propSetList; - } - - private static string? GetProperty(IPropertySet propSet, string name) => propSet[name] as string; - - public void EnableExtension(string extensionUniqueId) - { - var extension = _installedExtensions.Where(extension => extension.ExtensionUniqueId.Equals(extensionUniqueId, StringComparison.Ordinal)); - _enabledExtensions.Add(extension.First()); - } - - public void DisableExtension(string extensionUniqueId) - { - var extension = _enabledExtensions.Where(extension => extension.ExtensionUniqueId.Equals(extensionUniqueId, StringComparison.Ordinal)); - _enabledExtensions.Remove(extension.First()); - } - - /* - ///// - //public async Task DisableExtensionIfWindowsFeatureNotAvailable(IExtensionWrapper extension) - //{ - // // Only attempt to disable feature if its available. - // if (IsWindowsOptionalFeatureAvailableForExtension(extension.ExtensionClassId)) - // { - // return false; - // } - // _log.Warning($"Disabling extension: '{extension.ExtensionDisplayName}' because its feature is absent or unknown"); - // // Remove extension from list of enabled extensions to prevent Dev Home from re-querying for this extension - // // for the rest of its process lifetime. - // DisableExtension(extension.ExtensionUniqueId); - // // Update the local settings so the next time the user launches Dev Home the extension will be disabled. - // await _localSettingsService.SaveSettingAsync(extension.ExtensionUniqueId + "-ExtensionDisabled", true); - // return true; - //} */ - - [LoggerMessage( - Level = LogLevel.Debug, - Message = "Installed new extension app {extensionName}")] - partial void Log_ExtensionInstalled(string extensionName); - - [LoggerMessage( - Level = LogLevel.Debug, - Message = "Uninstalled extension app {extensionPackageName}")] - partial void Log_ExtensionUninstalled(string extensionPackageName); - - [LoggerMessage( - Level = LogLevel.Debug, - Message = "Extension {extensionName} declared an unsupported interface: {InterfaceKey}")] - partial void Log_UndeclaredInterfaceInExtension(string extensionName, string interfaceKey); - - [LoggerMessage( - Level = LogLevel.Debug, - Message = "Signaling dispose to {extensionUniqueId}")] - partial void Log_DisposingExtension(string extensionUniqueId); - - [LoggerMessage( - Level = LogLevel.Error, - Message = "Failed to send dispose signal to extension {extensionUniqueId}")] - partial void Log_FailedSendingDisposeToExtension(Exception ex, string extensionUniqueId); -} - -internal record struct IsExtensionResult(bool IsExtension, AppExtension? Extension) -{ -} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionWrapper.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionWrapper.cs index 32526c6544..dbd6111426 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionWrapper.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionWrapper.cs @@ -4,7 +4,7 @@ using System.Runtime.InteropServices; using ManagedCommon; -using Microsoft.CmdPal.Common.Services; +using Microsoft.CmdPal.UI.ViewModels.Services; using Microsoft.CommandPalette.Extensions; using Windows.ApplicationModel; using Windows.ApplicationModel.AppExtensions; diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/WinRTExtensionService.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/WinRTExtensionService.cs new file mode 100644 index 0000000000..c4325ad967 --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/WinRTExtensionService.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CmdPal.UI.ViewModels.Models; + +public partial class WinRTExtensionService +{ +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ProviderSettingsViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ProviderSettingsViewModel.cs index f0e0a3269c..cde45bc135 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ProviderSettingsViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ProviderSettingsViewModel.cs @@ -5,10 +5,9 @@ using System.Diagnostics.CodeAnalysis; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; -using Microsoft.CmdPal.Common.Services; -using Microsoft.CmdPal.UI.ViewModels; using Microsoft.CmdPal.UI.ViewModels.Messages; using Microsoft.CmdPal.UI.ViewModels.Properties; +using Microsoft.CmdPal.UI.ViewModels.Services; namespace Microsoft.CmdPal.UI.ViewModels; diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/IExtensionService.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/IExtensionService.cs new file mode 100644 index 0000000000..70b52dac13 --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/IExtensionService.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.Foundation; + +namespace Microsoft.CmdPal.UI.ViewModels.Services; + +public interface IExtensionService +{ + event TypedEventHandler>? OnCommandProviderAdded; + + event TypedEventHandler>? OnCommandProviderRemoved; + + event TypedEventHandler>? OnCommandsAdded; + + event TypedEventHandler>? OnCommandsRemoved; + + Task> GetCommandProviderWrappersAsync(WeakReference weakPageContext, bool includeDisabledExtensions = false); + + Task> GetTopLevelCommandsAsync(); + + Task SignalStopExtensionsAsync(); + + Task EnableProviderAsync(string providerId); + + Task DisableProviderAsync(string providerId); +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Common/Services/IExtensionWrapper.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/IExtensionWrapper.cs similarity index 96% rename from src/modules/cmdpal/Microsoft.CmdPal.Common/Services/IExtensionWrapper.cs rename to src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/IExtensionWrapper.cs index 61667366ba..f866a77860 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.Common/Services/IExtensionWrapper.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Services/IExtensionWrapper.cs @@ -2,13 +2,10 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Threading.Tasks; using Microsoft.CommandPalette.Extensions; using Windows.ApplicationModel; -namespace Microsoft.CmdPal.Common.Services; +namespace Microsoft.CmdPal.UI.ViewModels.Services; public interface IExtensionWrapper { diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelCommandManager.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelCommandManager.cs index 6b542a463d..f69d52ae17 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelCommandManager.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/TopLevelCommandManager.cs @@ -2,17 +2,13 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Immutable; using System.Collections.ObjectModel; -using System.Diagnostics; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; using Microsoft.CmdPal.Common.Helpers; -using Microsoft.CmdPal.Common.Services; using Microsoft.CmdPal.UI.ViewModels.Messages; -using Microsoft.CommandPalette.Extensions; -using Microsoft.CommandPalette.Extensions.Toolkit; +using Microsoft.CmdPal.UI.ViewModels.Services; using Microsoft.Extensions.Logging; namespace Microsoft.CmdPal.UI.ViewModels; @@ -23,30 +19,30 @@ public partial class TopLevelCommandManager : ObservableObject, IDisposable { private readonly TaskScheduler _taskScheduler; - private readonly IEnumerable _commandProviders; + private readonly IEnumerable _extensionServices; private readonly ILogger _logger; private readonly SettingsService _settingsService; - private readonly IExtensionService _extensionService; - private readonly List _builtInCommands = []; - private readonly List _extensionCommandProviders = []; - private readonly Lock _commandProvidersLock = new(); + private readonly List _commandProviderWrappers = []; + private readonly Lock _commandProviderWrappersLock = new(); + + private readonly List _commandProviderCommands = []; + private readonly Lock _commandProviderCommandsLock = new(); + private readonly SupersedingAsyncGate _reloadCommandsGate; TaskScheduler IPageContext.Scheduler => _taskScheduler; public TopLevelCommandManager( - IEnumerable commandProviders, + IEnumerable extensionServices, TaskScheduler taskScheduler, SettingsService settingsService, - IExtensionService extensionService, ILogger logger) { this._logger = logger; - _commandProviders = commandProviders; + _extensionServices = extensionServices; _taskScheduler = taskScheduler; _settingsService = settingsService; - _extensionService = extensionService; WeakReferenceMessenger.Default.Register(this); _reloadCommandsGate = new(ReloadAllCommandsAsyncCore); } @@ -60,158 +56,80 @@ public partial class TopLevelCommandManager : ObservableObject, { get { - lock (_commandProvidersLock) + lock (_commandProviderWrappersLock) { - return _builtInCommands.Concat(_extensionCommandProviders).ToList(); + return _commandProviderWrappers.ToList(); } } } - public async Task LoadBuiltinsAsync() + [RelayCommand] + public async Task LoadExtensionsAsync() { - var s = new Stopwatch(); - s.Start(); - - lock (_commandProvidersLock) + lock (_commandProviderWrappersLock) { - _builtInCommands.Clear(); + _commandProviderWrappers.Clear(); } - // Load built-In commands first. These are all in-proc, and - // owned by our ServiceProvider. - var builtInCommands = _commandProviders; - foreach (var provider in builtInCommands) - { - CommandProviderWrapper wrapper = new(provider, _taskScheduler, _hotkeyManager, _aliasManager, _logger); - lock (_commandProvidersLock) - { - _builtInCommands.Add(wrapper); - } + var weakSelf = new WeakReference(this); - var commands = await LoadTopLevelCommandsFromProvider(wrapper); - lock (TopLevelCommands) + foreach (var extensionService in _extensionServices) + { + // extensionService.OnCommandProviderAdded -= ExtensionService_OnProviderAdded; + // extensionService.OnCommandProviderRemoved -= ExtensionService_OnProviderRemoved; + extensionService.OnCommandsAdded -= ExtensionService_OnCommandsAdded; + extensionService.OnCommandsRemoved -= ExtensionService_OnCommandsRemoved; + + _ = Task.Run(async () => { - foreach (var c in commands) + var providers = await extensionService.GetCommandProviderWrappersAsync(weakSelf); + lock (_commandProviderWrappersLock) { - TopLevelCommands.Add(c); + _commandProviderWrappers.AddRange(providers); } - } + + var commands = await extensionService.GetTopLevelCommandsAsync(); + lock (_commandProviderCommandsLock) + { + _commandProviderCommands.AddRange(commands); + } + }); + + // extensionService.OnCommandProviderAdded += ExtensionService_OnProviderAdded; + // extensionService.OnCommandProviderRemoved += ExtensionService_OnProviderRemoved; + extensionService.OnCommandsAdded += ExtensionService_OnCommandsAdded; + extensionService.OnCommandsRemoved += ExtensionService_OnCommandsRemoved; } - s.Stop(); + IsLoading = false; - Log_LoadingBuiltInsTook(s.ElapsedMilliseconds); - - return true; + // Send on the current thread; receivers should marshal to UI if needed + WeakReferenceMessenger.Default.Send(); } - // May be called from a background thread - private async Task> LoadTopLevelCommandsFromProvider(CommandProviderWrapper commandProvider) + private void ExtensionService_OnCommandsAdded(CommandProviderWrapper commandProviderWrapper, IEnumerable commands) { - WeakReference weakSelf = new(this); - - await commandProvider.LoadTopLevelCommands(_settingsService, weakSelf); - - var commands = await Task.Factory.StartNew( - () => - { - List commands = []; - foreach (var item in commandProvider.TopLevelItems) - { - commands.Add(item); - } - - foreach (var item in commandProvider.FallbackItems) - { - if (item.IsEnabled) - { - commands.Add(item); - } - } - - return commands; - }, - CancellationToken.None, - TaskCreationOptions.None, - _taskScheduler); - - commandProvider.CommandsChanged -= CommandProvider_CommandsChanged; - commandProvider.CommandsChanged += CommandProvider_CommandsChanged; - - return commands; + // _ = Task.Run(async () => + // { + // await Task.Factory.StartNew( + // () => + // { + // lock (TopLevelCommands) + // { + // foreach (var command in commands) + // { + // TopLevelCommands.Add(command); + // } + // } + // }, + // CancellationToken.None, + // TaskCreationOptions.None, + // _taskScheduler); + // }); } - // By all accounts, we're already on a background thread (the COM call - // to handle the event shouldn't be on the main thread.). But just to - // be sure we don't block the caller, hop off this thread - private void CommandProvider_CommandsChanged(CommandProviderWrapper sender, IItemsChangedEventArgs args) => - _ = Task.Run(async () => await UpdateCommandsForProvider(sender, args)); - - /// - /// Called when a command provider raises its ItemsChanged event. We'll - /// remove the old commands from the top-level list and try to put the new - /// ones in the same place in the list. - /// - /// The provider who's commands changed - /// the ItemsChangedEvent the provider raised - /// an awaitable task - private async Task UpdateCommandsForProvider(CommandProviderWrapper sender, IItemsChangedEventArgs args) + private void ExtensionService_OnCommandsRemoved(CommandProviderWrapper commandProviderWrapper, IEnumerable commands) { - WeakReference weakSelf = new(this); - await sender.LoadTopLevelCommands(_settingsService, weakSelf); - - List newItems = [.. sender.TopLevelItems]; - foreach (var i in sender.FallbackItems) - { - if (i.IsEnabled) - { - newItems.Add(i); - } - } - - // modify the TopLevelCommands under shared lock; event if we clone it, we don't want - // TopLevelCommands to get modified while we're working on it. Otherwise, we might - // out clone would be stale at the end of this method. - lock (TopLevelCommands) - { - // Work on a clone of the list, so that we can just do one atomic - // update to the actual observable list at the end - // TODO: just added a lock around all of this anyway, but keeping the clone - // while looking on some other ways to improve this; can be removed later. - List clone = [.. TopLevelCommands]; - - var startIndex = FindIndexForFirstProviderItem(clone, sender.ProviderId); - clone.RemoveAll(item => item.CommandProviderId == sender.ProviderId); - clone.InsertRange(startIndex, newItems); - - ListHelpers.InPlaceUpdateList(TopLevelCommands, clone); - } - - return; - - static int FindIndexForFirstProviderItem(List topLevelItems, string providerId) - { - // Tricky: all Commands from a single provider get added to the - // top-level list all together, in a row. So if we find just the first - // one, we can slice it out and insert the new ones there. - for (var i = 0; i < topLevelItems.Count; i++) - { - var wrapper = topLevelItems[i]; - try - { - if (providerId == wrapper.CommandProviderId) - { - return i; - } - } - catch - { - } - } - - // If we didn't find any, then we just append the new commands to the end of the list. - return topLevelItems.Count; - } } public async Task ReloadAllCommandsAsync() @@ -225,187 +143,18 @@ public partial class TopLevelCommandManager : ObservableObject, private async Task ReloadAllCommandsAsyncCore(CancellationToken cancellationToken) { IsLoading = true; - await _extensionService.SignalStopExtensionsAsync(); + + foreach (var extensionService in _extensionServices) + { + await extensionService.SignalStopExtensionsAsync(); + } lock (TopLevelCommands) { TopLevelCommands.Clear(); } - await LoadBuiltinsAsync(); - _ = Task.Run(LoadExtensionsAsync); - } - - // Load commands from our extensions. Called on a background thread. - // Currently, this - // * queries the package catalog, - // * starts all the extensions, - // * then fetches the top-level commands from them. - // TODO In the future, we'll probably abstract some of this away, to have - // separate extension tracking vs stub loading. - [RelayCommand] - public async Task LoadExtensionsAsync() - { - _extensionService.OnExtensionAdded -= ExtensionService_OnExtensionAdded; - _extensionService.OnExtensionRemoved -= ExtensionService_OnExtensionRemoved; - - var extensions = (await _extensionService.GetInstalledExtensionsAsync()).ToImmutableList(); - lock (_commandProvidersLock) - { - _extensionCommandProviders.Clear(); - } - - if (extensions is not null) - { - await StartExtensionsAndGetCommands(extensions); - } - - _extensionService.OnExtensionAdded += ExtensionService_OnExtensionAdded; - _extensionService.OnExtensionRemoved += ExtensionService_OnExtensionRemoved; - - IsLoading = false; - - // Send on the current thread; receivers should marshal to UI if needed - WeakReferenceMessenger.Default.Send(); - - return true; - } - - private void ExtensionService_OnExtensionAdded(IExtensionService sender, IEnumerable extensions) - { - // When we get an extension install event, hop off to a BG thread - _ = Task.Run(async () => - { - // for each newly installed extension, start it and get commands - // from it. One single package might have more than one - // IExtensionWrapper in it. - await StartExtensionsAndGetCommands(extensions); - }); - } - - private async Task StartExtensionsAndGetCommands(IEnumerable extensions) - { - var timer = new Stopwatch(); - timer.Start(); - - // Start all extensions in parallel - var startTasks = extensions.Select(StartExtensionWithTimeoutAsync); - - // Wait for all extensions to start - var wrappers = (await Task.WhenAll(startTasks)).Where(wrapper => wrapper is not null).Select(w => w!).ToList(); - - lock (_commandProvidersLock) - { - _extensionCommandProviders.AddRange(wrappers); - } - - // Load the commands from the providers in parallel - var loadTasks = wrappers.Select(LoadCommandsWithTimeoutAsync); - - var commandSets = (await Task.WhenAll(loadTasks)).Where(results => results is not null).Select(r => r!).ToList(); - - lock (TopLevelCommands) - { - foreach (var commands in commandSets) - { - foreach (var c in commands) - { - TopLevelCommands.Add(c); - } - } - } - - timer.Stop(); - Log_LoadingExtensionsTook(timer.ElapsedMilliseconds); - } - - private async Task StartExtensionWithTimeoutAsync(IExtensionWrapper extension) - { - Log_StartingExtension(extension.PackageFullName); - try - { - await extension.StartExtensionAsync().WaitAsync(TimeSpan.FromSeconds(10)); - return new CommandProviderWrapper(extension, _taskScheduler, _hotkeyManager, _aliasManager, _logger); - - - - - - - - - - - } - catch (Exception ex) - { - Log_FailedToStartExtension(extension.PackageFullName, ex); - return null; // Return null for failed extensions - } - } - - private async Task?> LoadCommandsWithTimeoutAsync(CommandProviderWrapper wrapper) - { - try - { - return await LoadTopLevelCommandsFromProvider(wrapper!).WaitAsync(TimeSpan.FromSeconds(10)); - } - catch (TimeoutException) - { - Log_LoadingCommandsTimedOut(wrapper!.ExtensionHost?.Extension?.PackageFullName); - } - catch (Exception ex) - { - Log_FailedToLoadCommands(wrapper!.ExtensionHost?.Extension?.PackageFullName, ex); - } - - return null; - } - - private void ExtensionService_OnExtensionRemoved(IExtensionService sender, IEnumerable extensions) - { - // When we get an extension uninstall event, hop off to a BG thread - _ = Task.Run( - async () => - { - // Then find all the top-level commands that belonged to that extension - List commandsToRemove = []; - lock (TopLevelCommands) - { - foreach (var extension in extensions) - { - foreach (var command in TopLevelCommands) - { - var host = command.ExtensionHost; - if (host?.Extension == extension) - { - commandsToRemove.Add(command); - } - } - } - } - - // Then back on the UI thread (remember, TopLevelCommands is - // Observable, so you can't touch it on the BG thread)... - await Task.Factory.StartNew( - () => - { - // ... remove all the deleted commands. - lock (TopLevelCommands) - { - if (commandsToRemove.Count != 0) - { - foreach (var deleted in commandsToRemove) - { - TopLevelCommands.Remove(deleted); - } - } - } - }, - CancellationToken.None, - TaskCreationOptions.None, - _taskScheduler); - }); + _ = Task.Run(LoadExtensionsAsync, cancellationToken); } public TopLevelViewModel? LookupCommand(string id) @@ -435,10 +184,9 @@ public partial class TopLevelCommandManager : ObservableObject, internal bool IsProviderActive(string id) { - lock (_commandProvidersLock) + lock (_commandProviderWrappersLock) { - return _builtInCommands.Any(wrapper => wrapper.Id == id && wrapper.IsActive) - || _extensionCommandProviders.Any(wrapper => wrapper.Id == id && wrapper.IsActive); + return _commandProviderWrappers.Any(wrapper => wrapper.Id == id && wrapper.IsActive); } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs index 4085abae09..8d018b2c02 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/App.xaml.cs @@ -29,7 +29,6 @@ using Microsoft.CmdPal.UI.Services; using Microsoft.CmdPal.UI.Settings; using Microsoft.CmdPal.UI.ViewModels; using Microsoft.CmdPal.UI.ViewModels.BuiltinCommands; -using Microsoft.CmdPal.UI.ViewModels.Models; using Microsoft.CmdPal.UI.ViewModels.Services; using Microsoft.CommandPalette.Extensions; using Microsoft.Extensions.DependencyInjection; @@ -130,7 +129,7 @@ public partial class App : Application private void AddCoreServices(ServiceCollection services) { // Core services - services.AddSingleton(); + // services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs index 15d8ca5aeb..4a18e54a0d 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/MainWindow.xaml.cs @@ -7,7 +7,6 @@ using System.Runtime.InteropServices; using CmdPalKeyboardService; using CommunityToolkit.Mvvm.Messaging; using Microsoft.CmdPal.Common.Helpers; -using Microsoft.CmdPal.Common.Services; using Microsoft.CmdPal.Ext.ClipboardHistory.Messages; using Microsoft.CmdPal.UI.Controls; using Microsoft.CmdPal.UI.Events; @@ -78,7 +77,7 @@ public sealed partial class MainWindow : WindowEx, private readonly ILogger _logger; private readonly SettingsService _settingsService; private readonly TrayIconService _trayIconService; - private readonly IExtensionService _extensionService; + private readonly IEnumerable _extensionServices; private bool _ignoreHotKeyWhenFullScreen = true; private bool _themeServiceInitialized; @@ -106,14 +105,14 @@ public sealed partial class MainWindow : WindowEx, SettingsService settingsService, TrayIconService trayIconService, LocalKeyboardListener localKeyboardListener, - IExtensionService extensionService, + IEnumerable extensionServices, ShellPage shellPage, ILogger logger) { InitializeComponent(); _logger = logger; _trayIconService = trayIconService; - _extensionService = extensionService; + _extensionServices = extensionServices; ViewModel = mainWindowViewModel; @@ -751,7 +750,10 @@ public sealed partial class MainWindow : WindowEx, _settingsService.SaveSettings(settings); } - _extensionService.SignalStopExtensionsAsync(); + foreach (var extensionService in _extensionServices) + { + extensionService.SignalStopExtensionsAsync(); + } _trayIconService.Destroy(); diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/PowerToysRootPageService.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/PowerToysRootPageService.cs index b598ce4498..1a0e23ae46 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/PowerToysRootPageService.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/PowerToysRootPageService.cs @@ -4,27 +4,34 @@ using System.Runtime.InteropServices; using System.Runtime.Versioning; -using ManagedCommon; -using Microsoft.CmdPal.Common.Services; using Microsoft.CmdPal.UI.ViewModels; using Microsoft.CmdPal.UI.ViewModels.MainPage; using Microsoft.CmdPal.UI.ViewModels.Services; using Microsoft.CommandPalette.Extensions; +using Microsoft.Extensions.Logging; +using Windows.Win32.Foundation; using WinRT; // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. namespace Microsoft.CmdPal.UI; -internal sealed class PowerToysRootPageService : IRootPageService +internal sealed partial class PowerToysRootPageService : IRootPageService { private readonly TopLevelCommandManager _tlcManager; + private readonly ILogger _logger; private IExtensionWrapper? _activeExtension; private Lazy _mainListPage; - public PowerToysRootPageService(TopLevelCommandManager topLevelCommandManager, SettingsService settingsService, AliasManager aliasManager, AppStateService appStateService) + public PowerToysRootPageService( + TopLevelCommandManager topLevelCommandManager, + SettingsService settingsService, + AliasManager aliasManager, + AppStateService appStateService, + ILogger logger) { + _logger = logger; _tlcManager = topLevelCommandManager; _mainListPage = new Lazy(() => @@ -35,7 +42,7 @@ internal sealed class PowerToysRootPageService : IRootPageService public async Task PreLoadAsync() { - await _tlcManager.LoadBuiltinsAsync(); + await _tlcManager.LoadExtensionsAsync(); } public Microsoft.CommandPalette.Extensions.IPage GetRootPage() @@ -45,14 +52,6 @@ internal sealed class PowerToysRootPageService : IRootPageService public async Task PostLoadRootPageAsync() { - // After loading built-ins, and starting navigation, kick off a thread to load extensions. - _tlcManager.LoadExtensionsCommand.Execute(null); - - await _tlcManager.LoadExtensionsCommand.ExecutionTask!; - if (_tlcManager.LoadExtensionsCommand.ExecutionTask.Status != TaskStatus.RanToCompletion) - { - // TODO: Handle failure case - } } private void OnPerformTopLevelCommand(object? context) @@ -66,8 +65,7 @@ internal sealed class PowerToysRootPageService : IRootPageService } catch (Exception ex) { - Logger.LogError("Failed to update history in PowerToysRootPageService"); - Logger.LogError(ex.ToString()); + Log_FailedToUpdateHistory(ex); } } @@ -108,7 +106,7 @@ internal sealed class PowerToysRootPageService : IRootPageService var hr = Native.CoAllowSetForegroundWindow(intPtr); if (hr != 0) { - Logger.LogWarning($"Error giving foreground rights: 0x{hr.Value:X8}"); + Log_FailureToGiveForegroundRights(hr); } } } @@ -138,4 +136,14 @@ internal sealed class PowerToysRootPageService : IRootPageService [SupportedOSPlatform("windows5.0")] internal static extern unsafe global::Windows.Win32.Foundation.HRESULT CoAllowSetForegroundWindow(nint pUnk, [Optional] void* lpvReserved); } + + [LoggerMessage( + Level = LogLevel.Error, + Message = "Failed to update history after performing top-level command.")] + partial void Log_FailedToUpdateHistory(Exception ex); + + [LoggerMessage( + Level = LogLevel.Error, + Message = "Error giving foreground rights: 0x{hr.Value:X8}")] + partial void Log_FailureToGiveForegroundRights(HRESULT hr); }