mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-14 08:16:50 +01:00
Use the actual devhome code for loading extensions (#8)
As in title. I'm adding a `ExtensionObject<T>` object too. It doesn't _enforce_ anything. We can use it to wrap up things we get from extensions. You get the object back out with `.Unsafe`, which is a mental clue "this object might not live in this process". It'll at least give us a better clue of all the places where accessing the object might not totally be safe. Also fixes a bug that makes cmdpal a bit more resilient to an extension dying and being reloaded. Just go to all apps & back, and presto, reload.
This commit is contained in:
@@ -3,24 +3,12 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using ABI.System;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.Windows.CommandPalette.Extensions;
|
||||
using Microsoft.Windows.CommandPalette.Extensions.Helpers;
|
||||
using Windows.Foundation;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace SpongebotExtension;
|
||||
|
||||
@@ -98,15 +86,10 @@ public class SpongebotPage : Microsoft.Windows.CommandPalette.Extensions.Helpers
|
||||
var content = new System.Net.Http.FormUrlEncodedContent(bodyObj);
|
||||
var resp = await client.PostAsync("https://api.imgflip.com/caption_image", content);
|
||||
var respBody = await resp.Content.ReadAsStringAsync();
|
||||
// dynamic r = JsonSerializer.Deserialize(respBody);
|
||||
var response = JsonNode.Parse(respBody);
|
||||
|
||||
// var url = r?.data?.url;
|
||||
var url = response["data"]?["url"]?.ToString() ?? "";
|
||||
|
||||
// var encodedMessage = JsonEncodedText.Encode(Message).ToString();
|
||||
// var encodedUrl = JsonEncodedText.Encode(url).ToString();
|
||||
//
|
||||
var body = $$"""
|
||||
SpongeBot says:
|
||||

|
||||
@@ -144,19 +127,6 @@ internal sealed class SpongebotCommandsProvider : ICommandProvider
|
||||
};
|
||||
return [ listItem ];
|
||||
}
|
||||
|
||||
// public IAsyncOperation<IReadOnlyList<ICommand>> GetCommands()
|
||||
// {
|
||||
// var spongeCommand = new SpongeDynamicCommandHost() ;
|
||||
// var settingsCommand = new SettingsCommand() ;
|
||||
// ICommand command = (File.Exists(SettingsCommand.StateJsonPath())?
|
||||
// spongeCommand : settingsCommand);
|
||||
// var list = new List<ICommand>()
|
||||
// {
|
||||
// command
|
||||
// };
|
||||
// return Task.FromResult(list as IReadOnlyList<ICommand>).AsAsyncOperation();
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -18,4 +18,8 @@
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\extensionsdk\Microsoft.Windows.CommandPalette.Extensions.Helpers\Microsoft.CmdPal.Extensions.Helpers.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.CmdPal.Common.Services;
|
||||
|
||||
public interface IExtensionService
|
||||
{
|
||||
Task<IEnumerable<IExtensionWrapper>> GetInstalledExtensionsAsync(bool includeDisabledExtensions = false);
|
||||
|
||||
// Task<IEnumerable<string>> GetInstalledHomeWidgetPackageFamilyNamesAsync(bool includeDisabledExtensions = false);
|
||||
|
||||
Task<IEnumerable<IExtensionWrapper>> GetInstalledExtensionsAsync(Microsoft.Windows.CommandPalette.Extensions.ProviderType providerType, bool includeDisabledExtensions = false);
|
||||
|
||||
IExtensionWrapper? GetInstalledExtension(string extensionUniqueId);
|
||||
|
||||
Task SignalStopExtensionsAsync();
|
||||
|
||||
public event EventHandler OnExtensionsChanged;
|
||||
|
||||
public void EnableExtension(string extensionUniqueId);
|
||||
|
||||
public void DisableExtension(string extensionUniqueId);
|
||||
|
||||
///// <summary>
|
||||
///// 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.
|
||||
///// </summary>
|
||||
///// <param name="extension">The out of proc extension object</param>
|
||||
///// <returns>True only if the extension was disabled. False otherwise.</returns>
|
||||
//public Task<bool> DisableExtensionIfWindowsFeatureNotAvailable(IExtensionWrapper extension);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Windows.CommandPalette.Extensions;
|
||||
using Windows.ApplicationModel;
|
||||
|
||||
namespace Microsoft.CmdPal.Common.Services;
|
||||
|
||||
public interface IExtensionWrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the DisplayName of the package as mentioned in the manifest
|
||||
/// </summary>
|
||||
string PackageDisplayName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets DisplayName of the extension as mentioned in the manifest
|
||||
/// </summary>
|
||||
string ExtensionDisplayName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets PackageFullName of the extension
|
||||
/// </summary>
|
||||
string PackageFullName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets PackageFamilyName of the extension
|
||||
/// </summary>
|
||||
string PackageFamilyName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets Publisher of the extension
|
||||
/// </summary>
|
||||
string Publisher { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets class id (GUID) of the extension class (which implements IExtension) as mentioned in the manifest
|
||||
/// </summary>
|
||||
string ExtensionClassId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the date on which the application package was installed or last updated.
|
||||
/// </summary>
|
||||
DateTimeOffset InstalledDate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the PackageVersion of the extension
|
||||
/// </summary>
|
||||
PackageVersion Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Unique Id for the extension
|
||||
/// </summary>
|
||||
public string ExtensionUniqueId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether we have a reference to the extension process and we are able to call methods on the interface.
|
||||
/// </summary>
|
||||
/// <returns>Whether we have a reference to the extension process and we are able to call methods on the interface.</returns>
|
||||
bool IsRunning();
|
||||
|
||||
/// <summary>
|
||||
/// Starts the extension if not running
|
||||
/// </summary>
|
||||
/// <returns>An awaitable task</returns>
|
||||
Task StartExtensionAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Signals the extension to dispose itself and removes the reference to the extension com object
|
||||
/// </summary>
|
||||
void SignalDispose();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying instance of IExtension
|
||||
/// </summary>
|
||||
/// <returns>Instance of IExtension</returns>
|
||||
IExtension? GetExtensionObject();
|
||||
|
||||
/// <summary>
|
||||
/// Tells the wrapper that the extension implements the given provider
|
||||
/// </summary>
|
||||
/// <param name="providerType">The type of provider to be added</param>
|
||||
void AddProviderType(ProviderType providerType);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the given provider was added through `AddProviderType` method
|
||||
/// </summary>
|
||||
/// <param name="providerType">The type of the provider to be checked for</param>
|
||||
/// <returns>Whether the given provider was added through `AddProviderType` method</returns>
|
||||
bool HasProviderType(ProviderType providerType);
|
||||
|
||||
/// <summary>
|
||||
/// Starts the extension if not running and gets the provider from the underlying IExtension object
|
||||
/// Can be null if not found
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of provider</typeparam>
|
||||
/// <returns>Nullable instance of the provider</returns>
|
||||
Task<T?> GetProviderAsync<T>()
|
||||
where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// Starts the extension if not running and gets a list of providers of type T from the underlying IExtension object.
|
||||
/// If no providers are found, returns an empty list.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of provider</typeparam>
|
||||
/// <returns>Nullable instance of the provider</returns>
|
||||
Task<IEnumerable<T>> GetListOfProvidersAsync<T>()
|
||||
where T : class;
|
||||
}
|
||||
@@ -25,6 +25,7 @@ using Windows.ApplicationModel;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using Microsoft.Windows.CommandPalette.Services;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
@@ -38,7 +39,7 @@ public partial class App : Application, IApp
|
||||
private Window? window;
|
||||
public Window? AppWindow
|
||||
{
|
||||
get { return window; }
|
||||
get => window;
|
||||
private set { }
|
||||
}
|
||||
|
||||
@@ -79,6 +80,7 @@ public partial class App : Application, IApp
|
||||
|
||||
// Core Services
|
||||
services.AddSingleton<IFileService, FileService>();
|
||||
services.AddSingleton<IExtensionService, ExtensionService>();
|
||||
|
||||
//// Main window: Allow access to the main window
|
||||
//// from anywhere in the application.
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel.AppExtensions;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace DeveloperCommandPalette;
|
||||
internal sealed class ExtensionLoader
|
||||
{
|
||||
|
||||
private const string CreateInstanceProperty = "CreateInstance";
|
||||
private const string ClassIdProperty = "@ClassId";
|
||||
private static IPropertySet? GetSubPropertySet(IPropertySet propSet, string name)
|
||||
{
|
||||
return propSet.TryGetValue(name, out var value) ? value as IPropertySet : null;
|
||||
}
|
||||
|
||||
private static object[]? GetSubPropertySetArray(IPropertySet propSet, string name)
|
||||
{
|
||||
return propSet.TryGetValue(name, out var value) ? value as object[] : null;
|
||||
}
|
||||
|
||||
private static string? GetProperty(IPropertySet propSet, string name)
|
||||
{
|
||||
return propSet[name] as string;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// There are cases where the extension creates multiple COM instances.
|
||||
/// </summary>
|
||||
/// <param name="activationPropSet">Activation property set object</param>
|
||||
/// <returns>List of ClassId strings associated with the activation property</returns>
|
||||
private static List<string> GetCreateInstanceList(IPropertySet activationPropSet)
|
||||
{
|
||||
var propSetList = new List<string>();
|
||||
var singlePropertySet = GetSubPropertySet(activationPropSet, CreateInstanceProperty);
|
||||
if (singlePropertySet != 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 != null)
|
||||
{
|
||||
propSetList.Add(classId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var propertySetArray = GetSubPropertySetArray(activationPropSet, CreateInstanceProperty);
|
||||
if (propertySetArray != null)
|
||||
{
|
||||
foreach (var prop in propertySetArray)
|
||||
{
|
||||
if (prop is not IPropertySet propertySet)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var classId = GetProperty(propertySet, ClassIdProperty);
|
||||
if (classId != null)
|
||||
{
|
||||
propSetList.Add(classId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return propSetList;
|
||||
}
|
||||
|
||||
|
||||
public static async Task<(IPropertySet?, List<string>)> GetExtensionPropertiesAsync(AppExtension extension)
|
||||
{
|
||||
var classIds = new List<string>();
|
||||
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<bool> IsValidExtension(Package package)
|
||||
{
|
||||
var extensions = await AppExtensionCatalog.Open("com.microsoft.windows.commandpalette").FindAllAsync();
|
||||
foreach (var extension in extensions)
|
||||
{
|
||||
if (package.Id?.FullName == extension.Package?.Id?.FullName)
|
||||
{
|
||||
var (CmdPalProvider, classId) = await GetExtensionPropertiesAsync(extension);
|
||||
return CmdPalProvider != null && classId.Count != 0;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -5,28 +5,46 @@ using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.Windows.CommandPalette.Extensions;
|
||||
using System.ComponentModel;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using CmdPal.Models;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace DeveloperCommandPalette;
|
||||
|
||||
public sealed class ListItemViewModel : INotifyPropertyChanged, IDisposable
|
||||
{
|
||||
private readonly DispatcherQueue DispatcherQueue;
|
||||
internal IListItem ListItem { get; init; }
|
||||
internal ExtensionObject<IListItem> ListItem { get; init; }
|
||||
internal string Title { get; private set; }
|
||||
internal string Subtitle { get; private set; }
|
||||
internal string Icon { get; private set; }
|
||||
|
||||
internal Lazy<DetailsViewModel?> _Details;
|
||||
internal DetailsViewModel? Details => _Details.Value;
|
||||
internal IFallbackHandler? FallbackHandler => this.ListItem.FallbackHandler;
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
internal ICommand DefaultAction => ListItem.Command;
|
||||
internal ICommand? DefaultAction { get {try{ return ListItem.Unsafe.Command;} catch (COMException){return null;}}}
|
||||
|
||||
internal bool CanInvoke => DefaultAction != null && DefaultAction is IInvokableCommand or IPage;
|
||||
internal IconElement IcoElement => Microsoft.Terminal.UI.IconPathConverter.IconMUX(Icon);
|
||||
|
||||
private IEnumerable<ICommandContextItem> contextActions => ListItem.MoreCommands == null ? [] : ListItem.MoreCommands.Where(i => i is ICommandContextItem).Select(i=> (ICommandContextItem)i);
|
||||
private IEnumerable<ICommandContextItem> contextActions
|
||||
{
|
||||
get {
|
||||
try
|
||||
{
|
||||
var item = ListItem.Unsafe;
|
||||
return item.MoreCommands == null ?
|
||||
[] :
|
||||
item.MoreCommands.Where(i => i is ICommandContextItem).Select(i => (ICommandContextItem)i);
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
/* log something */
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
internal bool HasMoreCommands => contextActions.Any();
|
||||
|
||||
internal TagViewModel[] Tags = [];
|
||||
@@ -36,16 +54,25 @@ public sealed class ListItemViewModel : INotifyPropertyChanged, IDisposable
|
||||
{
|
||||
get
|
||||
{
|
||||
var l = contextActions.Select(a => new ContextItemViewModel(a)).ToList();
|
||||
l.Insert(0, new(DefaultAction));
|
||||
return l;
|
||||
try
|
||||
{
|
||||
var l = contextActions.Select(a => new ContextItemViewModel(a)).ToList();
|
||||
var def = DefaultAction;
|
||||
if (def!=null) l.Insert(0, new(def));
|
||||
return l;
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
/* log something */
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ListItemViewModel(IListItem model)
|
||||
{
|
||||
model.PropChanged += ListItem_PropertyChanged;
|
||||
this.ListItem = model;
|
||||
this.ListItem = new(model);
|
||||
this.Title = model.Title;
|
||||
this.Subtitle = model.Subtitle;
|
||||
this.Icon = model.Command.Icon.Icon;
|
||||
@@ -54,41 +81,58 @@ public sealed class ListItemViewModel : INotifyPropertyChanged, IDisposable
|
||||
this.Tags = model.Tags.Select(t => new TagViewModel(t)).ToArray();
|
||||
}
|
||||
|
||||
this._Details = new(() => model.Details != null ? new(this.ListItem.Details) : null);
|
||||
this._Details = new(() => {
|
||||
try
|
||||
{
|
||||
var item = this.ListItem.Unsafe;
|
||||
return item.Details != null ? new(item.Details) : null;
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
/* log something */
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
this.DispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
}
|
||||
|
||||
private void ListItem_PropertyChanged(object sender, Microsoft.Windows.CommandPalette.Extensions.PropChangedEventArgs args)
|
||||
{
|
||||
switch (args.PropertyName)
|
||||
{
|
||||
case "Name":
|
||||
case nameof(Title):
|
||||
{
|
||||
this.Title = ListItem.Title;
|
||||
}
|
||||
break;
|
||||
case nameof(Subtitle):
|
||||
{
|
||||
this.Subtitle = ListItem.Subtitle;
|
||||
}
|
||||
break;
|
||||
case "MoreCommands":
|
||||
{
|
||||
BubbleXamlPropertyChanged(nameof(HasMoreCommands));
|
||||
BubbleXamlPropertyChanged(nameof(ContextActions));
|
||||
}
|
||||
break;
|
||||
case nameof(Icon):
|
||||
{
|
||||
this.Icon = ListItem.Command.Icon.Icon;
|
||||
BubbleXamlPropertyChanged(nameof(IcoElement));
|
||||
}
|
||||
break;
|
||||
}
|
||||
BubbleXamlPropertyChanged(args.PropertyName);
|
||||
try{
|
||||
var item = ListItem.Unsafe;
|
||||
switch (args.PropertyName)
|
||||
{
|
||||
case "Name":
|
||||
case nameof(Title):
|
||||
{
|
||||
this.Title = item.Title;
|
||||
}
|
||||
break;
|
||||
case nameof(Subtitle):
|
||||
{
|
||||
this.Subtitle = item.Subtitle;
|
||||
}
|
||||
break;
|
||||
case "MoreCommands":
|
||||
{
|
||||
BubbleXamlPropertyChanged(nameof(HasMoreCommands));
|
||||
BubbleXamlPropertyChanged(nameof(ContextActions));
|
||||
}
|
||||
break;
|
||||
case nameof(Icon):
|
||||
{
|
||||
this.Icon = item.Command.Icon.Icon;
|
||||
BubbleXamlPropertyChanged(nameof(IcoElement));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
BubbleXamlPropertyChanged(args.PropertyName);
|
||||
|
||||
} catch (COMException) {
|
||||
/* log something */
|
||||
}
|
||||
}
|
||||
|
||||
private void BubbleXamlPropertyChanged(string propertyName)
|
||||
@@ -106,7 +150,10 @@ public sealed class ListItemViewModel : INotifyPropertyChanged, IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.ListItem.PropChanged -= ListItem_PropertyChanged;
|
||||
|
||||
try{
|
||||
this.ListItem.Unsafe.PropChanged -= ListItem_PropertyChanged;
|
||||
} catch (COMException) {
|
||||
/* log something */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Microsoft.Windows.CommandPalette.Extensions.Helpers;
|
||||
using Microsoft.Windows.CommandPalette.Extensions;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace DeveloperCommandPalette;
|
||||
|
||||
@@ -90,7 +91,7 @@ public sealed class NoOpAction : InvokableCommand
|
||||
{
|
||||
public override ICommandResult Invoke() { return ActionResult.KeepOpen(); }
|
||||
}
|
||||
public sealed class ErrorListItem : ListItem
|
||||
public sealed class ErrorListItem : Microsoft.Windows.CommandPalette.Extensions.Helpers.ListItem
|
||||
{
|
||||
public ErrorListItem(Exception ex) : base(new NoOpAction()) {
|
||||
this.Title = "Error in extension:";
|
||||
@@ -198,9 +199,20 @@ public sealed class ListPageViewModel : PageViewModel
|
||||
|
||||
//// TODO! Probably bad that this turns list view models into listitems back to NEW view models
|
||||
//return ListHelpers.FilterList(Items.Select(vm => vm.ListItem), Query).Select(li => new ListItemViewModel(li)).ToList();
|
||||
var allFilteredItems = ListHelpers.FilterList(Items.SelectMany(section => section).Select(vm => vm.ListItem), Query).Select(li => new ListItemViewModel(li));
|
||||
var newSection = new SectionInfoList(null, allFilteredItems);
|
||||
return [newSection];
|
||||
try{
|
||||
var allFilteredItems = ListHelpers.FilterList(
|
||||
Items
|
||||
.SelectMany(section => section)
|
||||
.Select(vm => vm.ListItem.Unsafe),
|
||||
Query).Select(li => new ListItemViewModel(li)
|
||||
);
|
||||
var newSection = new SectionInfoList(null, allFilteredItems);
|
||||
return [newSection];
|
||||
}
|
||||
catch (COMException ex)
|
||||
{
|
||||
return [new SectionInfoList(null, [new ListItemViewModel(new ErrorListItem(ex))])];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Windows.CommandPalette.Extensions.Helpers;
|
||||
using Microsoft.Windows.CommandPalette.Extensions;
|
||||
using CmdPal.Models;
|
||||
|
||||
namespace DeveloperCommandPalette;
|
||||
|
||||
@@ -58,7 +59,7 @@ public sealed class RecentsListSection : ListSection, INotifyCollectionChanged
|
||||
var apps = _mainViewModel.Recent;
|
||||
foreach (var app in apps)
|
||||
{
|
||||
_Items.Add(new MainListItem(app));
|
||||
_Items.Add(new MainListItem(app.Unsafe)); // we know these are all local
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,7 +83,7 @@ public sealed class MainListSection : ISection, INotifyCollectionChanged
|
||||
// * Just the top-level actions (if there's no query)
|
||||
// * OR the top-level actions AND the apps (if there's a query)
|
||||
private IEnumerable<IListItem> itemsToEnumerate =>
|
||||
_Items.Where(i => i != null && (!_mainViewModel.Recent.Contains(i.Item)));
|
||||
_Items.Where(i => i != null && (!_mainViewModel.IsRecentCommand(i)));
|
||||
|
||||
// Watch out future me!
|
||||
//
|
||||
@@ -98,7 +99,7 @@ public sealed class MainListSection : ISection, INotifyCollectionChanged
|
||||
public MainListSection(MainViewModel viewModel)
|
||||
{
|
||||
this._mainViewModel = viewModel;
|
||||
_Items = new(_mainViewModel.TopLevelCommands.Select(a => new MainListItem(a)));
|
||||
_Items = new(_mainViewModel.TopLevelCommands.Select(w=>w.Unsafe).Where(li=>li!=null).Select(li => new MainListItem(li!)));
|
||||
_Items.CollectionChanged += Bubble_CollectionChanged; ;
|
||||
}
|
||||
|
||||
@@ -195,7 +196,8 @@ public sealed class FilteredListSection : ISection, INotifyCollectionChanged
|
||||
public FilteredListSection(MainViewModel viewModel)
|
||||
{
|
||||
this._mainViewModel = viewModel;
|
||||
_Items = new(_mainViewModel.TopLevelCommands.Select(a => new MainListItem(a)));
|
||||
// TODO: We should probably just get rid of MainListItem entirely, so I'm leaveing these uncaught
|
||||
_Items = new(_mainViewModel.TopLevelCommands.Where(wrapper=>wrapper.Unsafe!=null).Select(wrapper => new MainListItem(wrapper.Unsafe!)));
|
||||
_Items.CollectionChanged += Bubble_CollectionChanged; ;
|
||||
}
|
||||
|
||||
@@ -261,19 +263,20 @@ public sealed class MainListPage : Microsoft.Windows.CommandPalette.Extensions.H
|
||||
if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null)
|
||||
{
|
||||
foreach (var item in e.NewItems)
|
||||
if (item is IListItem listItem)
|
||||
if (item is ExtensionObject<IListItem> listItem)
|
||||
{
|
||||
// Eh, it's fine to be unsafe here, we're probably tossing MainListItem
|
||||
if (!_mainViewModel.Recent.Contains(listItem))
|
||||
{
|
||||
_mainSection._Items.Add(new MainListItem(listItem));
|
||||
_mainSection._Items.Add(new MainListItem(listItem.Unsafe));
|
||||
}
|
||||
_filteredSection._Items.Add(new MainListItem(listItem));
|
||||
_filteredSection._Items.Add(new MainListItem(listItem.Unsafe));
|
||||
}
|
||||
}
|
||||
else if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null)
|
||||
{
|
||||
foreach (var item in e.OldItems)
|
||||
if (item is IListItem listItem)
|
||||
if (item is ExtensionObject<IListItem> listItem)
|
||||
{
|
||||
foreach (var mainListItem in _mainSection._Items) // MainListItem
|
||||
if (mainListItem.Item == listItem)
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using CmdPal.Models;
|
||||
using Microsoft.CmdPal.Common.Extensions;
|
||||
using Microsoft.CmdPal.Common.Services;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.Windows.CommandPalette.Extensions;
|
||||
using Windows.ApplicationModel.AppExtensions;
|
||||
using Windows.Foundation;
|
||||
using System.Collections.ObjectModel;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using Microsoft.Windows.CommandPalette.Extensions;
|
||||
using Microsoft.Windows.CommandPalette.Extensions.Helpers;
|
||||
using Windows.Foundation;
|
||||
using Windows.Win32;
|
||||
|
||||
namespace DeveloperCommandPalette;
|
||||
@@ -19,7 +22,7 @@ public sealed class MainViewModel
|
||||
{
|
||||
internal readonly AllApps.AllAppsPage apps = new();
|
||||
internal readonly ObservableCollection<ActionsProviderWrapper> CommandsProviders = new();
|
||||
internal readonly ObservableCollection<IListItem> TopLevelCommands = [];
|
||||
internal readonly ObservableCollection<ExtensionObject<IListItem>> TopLevelCommands = [];
|
||||
|
||||
internal readonly List<ICommandProvider> _builtInCommands = [];
|
||||
|
||||
@@ -28,7 +31,9 @@ public sealed class MainViewModel
|
||||
internal bool LoadedApps;
|
||||
|
||||
public event TypedEventHandler<object, object?>? HideRequested;
|
||||
|
||||
public event TypedEventHandler<object, object?>? SummonRequested;
|
||||
|
||||
public event TypedEventHandler<object, object?>? AppsReady;
|
||||
|
||||
internal MainViewModel()
|
||||
@@ -47,10 +52,11 @@ public sealed class MainViewModel
|
||||
AppsReady?.Invoke(this, null);
|
||||
}).Start();
|
||||
}
|
||||
|
||||
public void ResetTopLevel()
|
||||
{
|
||||
TopLevelCommands.Clear();
|
||||
TopLevelCommands.Add(new ListItem(apps));
|
||||
TopLevelCommands.Add(new(new ListItem(apps)));
|
||||
}
|
||||
|
||||
internal void RequestHide()
|
||||
@@ -68,31 +74,72 @@ public sealed class MainViewModel
|
||||
{
|
||||
return title + subtitle;
|
||||
}
|
||||
|
||||
private string[] _recentCommandHashes = [];// ["SpotifySpotify", "All Apps", "GitHub Issues", "Microsoft/GithubBookmark"];
|
||||
public IEnumerable<IListItem> RecentActions => TopLevelCommands.Where(i => i != null && _recentCommandHashes.Contains(CreateHash(i.Title, i.Subtitle)));
|
||||
|
||||
public IEnumerable<IListItem> RecentActions => TopLevelCommands
|
||||
.Select(i=>i.Unsafe)
|
||||
.Where((i) => {
|
||||
if (i != null)
|
||||
{
|
||||
try{
|
||||
return _recentCommandHashes.Contains(CreateHash(i.Title, i.Subtitle));
|
||||
} catch(COMException){ return false; }
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.Select(i=>i!);
|
||||
|
||||
public IEnumerable<IListItem> AppItems => LoadedApps? apps.GetItems().First().Items : [];
|
||||
public IEnumerable<IListItem> Everything => TopLevelCommands.Concat(AppItems).Where(i => i!= null);
|
||||
public IEnumerable<IListItem> Recent => _recentCommandHashes.Select(hash => Everything.Where(i => CreateHash(i.Title, i.Subtitle) == hash ).FirstOrDefault()).Where(i => i != null).Select(i=>i!);
|
||||
|
||||
public IEnumerable<ExtensionObject<IListItem>> Everything => TopLevelCommands
|
||||
.Concat(AppItems.Select(i => new ExtensionObject<IListItem>(i)))
|
||||
.Where(i => i!= null);
|
||||
|
||||
public IEnumerable<ExtensionObject<IListItem>> Recent => _recentCommandHashes
|
||||
.Select(hash =>
|
||||
Everything
|
||||
.Where(i => {
|
||||
try {
|
||||
var o = i.Unsafe;
|
||||
return CreateHash(o.Title, o.Subtitle) == hash;
|
||||
} catch (COMException) { return false; }
|
||||
})
|
||||
.FirstOrDefault()
|
||||
)
|
||||
.Where(i => i != null)
|
||||
.Select(i=>i!);
|
||||
|
||||
public bool IsRecentCommand(MainListItem item)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var wraprer in Recent)
|
||||
{
|
||||
if (wraprer.Unsafe == item) return true;
|
||||
}
|
||||
}
|
||||
catch (COMException) { return false; }
|
||||
return false;
|
||||
}
|
||||
|
||||
internal void PushRecentAction(ICommand action)
|
||||
{
|
||||
IEnumerable<IListItem> topLevel = TopLevelCommands;
|
||||
if (LoadedApps)
|
||||
foreach (var wrapped in Everything)
|
||||
{
|
||||
topLevel = topLevel.Concat(AppItems);
|
||||
}
|
||||
|
||||
foreach (var listItem in topLevel)
|
||||
{
|
||||
if (listItem != null && listItem.Command == action)
|
||||
{
|
||||
// Found it, awesome.
|
||||
var hash = CreateHash(listItem.Title, listItem.Subtitle);
|
||||
// Remove the old one and push the new one to the front
|
||||
var recent = new List<string>([hash]).Concat(_recentCommandHashes.Where(h => h != hash)).Take(5).ToArray();
|
||||
_recentCommandHashes = recent.ToArray();
|
||||
return;
|
||||
try{
|
||||
var listItem = wrapped?.Unsafe;
|
||||
if (listItem != null && listItem.Command == action)
|
||||
{
|
||||
// Found it, awesome.
|
||||
var hash = CreateHash(listItem.Title, listItem.Subtitle);
|
||||
// Remove the old one and push the new one to the front
|
||||
var recent = new List<string>([hash]).Concat(_recentCommandHashes.Where(h => h != hash)).Take(5).ToArray();
|
||||
_recentCommandHashes = recent.ToArray();
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch(COMException){ /* log something */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,6 +150,7 @@ public sealed class MainViewModel
|
||||
public sealed partial class MainPage : Page
|
||||
{
|
||||
private string _log = "";
|
||||
|
||||
public MainViewModel ViewModel { get; } = new MainViewModel();
|
||||
|
||||
public MainPage()
|
||||
@@ -112,15 +160,26 @@ public sealed partial class MainPage : Page
|
||||
var rootListVm = new ListPageViewModel(new MainListPage(ViewModel));
|
||||
InitializePage(rootListVm);
|
||||
|
||||
|
||||
|
||||
// TODO! make this async: it was originally on Page_Loaded and was async from there
|
||||
// LoadAllCommands().Wait();
|
||||
LoadBuiltinCommandsAsync().Wait();
|
||||
|
||||
var extensionService = Application.Current.GetService<IExtensionService>();
|
||||
if (extensionService != null)
|
||||
{
|
||||
extensionService.OnExtensionsChanged += ExtensionService_OnExtensionsChanged;
|
||||
}
|
||||
|
||||
_ = LoadExtensions();
|
||||
|
||||
RootFrame.Navigate(typeof(ListPage), rootListVm, new DrillInNavigationTransitionInfo());
|
||||
}
|
||||
|
||||
private void ExtensionService_OnExtensionsChanged(object? sender, EventArgs e)
|
||||
{
|
||||
_ = LoadAllCommands();
|
||||
}
|
||||
|
||||
private void _HackyBadClearFilter()
|
||||
{
|
||||
// BODGY but I don't care, cause i'm throwing this all out
|
||||
@@ -129,7 +188,6 @@ public sealed partial class MainPage : Page
|
||||
tb.Focus(FocusState.Programmatic);
|
||||
}
|
||||
|
||||
|
||||
_ = LoadAllCommands();
|
||||
}
|
||||
private void ViewModel_SummonRequested(object sender, object? args)
|
||||
@@ -139,9 +197,11 @@ public sealed partial class MainPage : Page
|
||||
_HackyBadClearFilter();
|
||||
}
|
||||
}
|
||||
|
||||
private void Page_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
private async Task LoadAllCommands()
|
||||
{
|
||||
ViewModel.ResetTopLevel();
|
||||
@@ -151,6 +211,7 @@ public sealed partial class MainPage : Page
|
||||
_ = LoadExtensions();
|
||||
|
||||
}
|
||||
|
||||
public async Task LoadBuiltinCommandsAsync()
|
||||
{
|
||||
// Load commands from builtins
|
||||
@@ -208,10 +269,19 @@ public sealed partial class MainPage : Page
|
||||
if (!provider.IsExtension) continue;
|
||||
foreach (var item in provider.TopLevelItems)
|
||||
{
|
||||
if (action == item.Command)
|
||||
// TODO! We really need a better "SafeWrapper<T>" object that can make sure
|
||||
// that an extension object is alive when we call things on it.
|
||||
// Case in point: this. If the extension was killed while we're open, then
|
||||
// COM calls on it crash (and then we just do nothing)
|
||||
try
|
||||
{
|
||||
provider.AllowSetForeground(true);
|
||||
if (action == item.Command)
|
||||
{
|
||||
provider.AllowSetForeground(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (COMException e){ AppendLog(e.Message); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -278,31 +348,22 @@ public sealed partial class MainPage : Page
|
||||
|
||||
private async Task LoadExtensions()
|
||||
{
|
||||
if (ViewModel != null) ViewModel.LoadingExtensions = true;
|
||||
// Get extensions for us:
|
||||
AppExtensionCatalog extensionCatalog = AppExtensionCatalog.Open("com.microsoft.windows.commandpalette");
|
||||
IReadOnlyList<AppExtension> extensions = await extensionCatalog.FindAllAsync();
|
||||
foreach (var extension in extensions)
|
||||
if (ViewModel == null) return;
|
||||
|
||||
ViewModel.LoadingExtensions = true;
|
||||
|
||||
var extnService = Application.Current.GetService<IExtensionService>();
|
||||
if (extnService != null)
|
||||
{
|
||||
var name = extension.DisplayName;
|
||||
var id = extension.Id;
|
||||
var pfn = extension.Package.Id.FamilyName;
|
||||
|
||||
var (provider, classIds) = await ExtensionLoader.GetExtensionPropertiesAsync(extension);
|
||||
if (provider == null || classIds.Count == 0)
|
||||
var extensions = await extnService.GetInstalledExtensionsAsync(ProviderType.Commands, includeDisabledExtensions: false);
|
||||
foreach (var extension in extensions)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AppendLog($"Found Extension:{name}, {id}, {pfn}->");
|
||||
|
||||
foreach (var classId in classIds)
|
||||
{
|
||||
_ = LoadExtensionClassObject(extension, classId);
|
||||
if (extension == null) continue;
|
||||
await LoadActionExtensionObject(extension);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (ViewModel != null)
|
||||
{
|
||||
ViewModel.LoadingExtensions = false;
|
||||
@@ -310,16 +371,13 @@ public sealed partial class MainPage : Page
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadExtensionClassObject(AppExtension extension, string classId)
|
||||
private async Task LoadActionExtensionObject(IExtensionWrapper extension)
|
||||
{
|
||||
AppendLog($"\t{classId}");
|
||||
try
|
||||
{
|
||||
var extensionWrapper = new ExtensionWrapper(extension, classId);
|
||||
await extensionWrapper.StartExtensionAsync();
|
||||
var wrapper = new ActionsProviderWrapper(extensionWrapper);
|
||||
await extension.StartExtensionAsync();
|
||||
var wrapper = new ActionsProviderWrapper(extension);
|
||||
ViewModel.CommandsProviders.Add(wrapper);
|
||||
|
||||
await LoadTopLevelCommandsFromProvider(wrapper);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -331,11 +389,10 @@ public sealed partial class MainPage : Page
|
||||
private async Task LoadTopLevelCommandsFromProvider(ActionsProviderWrapper actionProvider)
|
||||
{
|
||||
// TODO! do this better async
|
||||
|
||||
await actionProvider.LoadTopLevelCommands().ConfigureAwait(false);
|
||||
foreach (var i in actionProvider.TopLevelItems)
|
||||
{
|
||||
ViewModel.TopLevelCommands.Add(i);
|
||||
ViewModel.TopLevelCommands.Add(new(i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,7 +443,7 @@ sealed class ActionsProviderWrapper
|
||||
public bool IsExtension => extensionWrapper != null;
|
||||
private readonly bool isValid;
|
||||
private ICommandProvider ActionProvider { get; }
|
||||
private readonly ExtensionWrapper? extensionWrapper;
|
||||
private readonly IExtensionWrapper? extensionWrapper;
|
||||
private IListItem[] _topLevelItems = [];
|
||||
public IListItem[] TopLevelItems => _topLevelItems;
|
||||
|
||||
@@ -394,7 +451,7 @@ sealed class ActionsProviderWrapper
|
||||
ActionProvider = provider;
|
||||
isValid = true;
|
||||
}
|
||||
public ActionsProviderWrapper(ExtensionWrapper extension)
|
||||
public ActionsProviderWrapper(IExtensionWrapper extension)
|
||||
{
|
||||
extensionWrapper = extension;
|
||||
var extensionImpl = extension.GetExtensionObject();
|
||||
@@ -416,8 +473,16 @@ sealed class ActionsProviderWrapper
|
||||
{
|
||||
_topLevelItems = commands;
|
||||
}
|
||||
|
||||
}
|
||||
// public async Task<bool> Ping()
|
||||
// {
|
||||
// if (!isValid) return false;
|
||||
// if (extensionWrapper != null)
|
||||
// {
|
||||
// return extensionWrapper.IsRunning();
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
public void AllowSetForeground(bool allow)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.CmdPal.Common.Services;
|
||||
using Microsoft.Windows.CommandPalette.Extensions;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.ApplicationModel.AppExtensions;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.System.Com;
|
||||
using WinRT;
|
||||
|
||||
namespace CmdPal.Models;
|
||||
|
||||
public class ExtensionObject<T> // where T : IInspectable
|
||||
{
|
||||
private readonly T _value;
|
||||
|
||||
public ExtensionObject(T value)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
// public T? Safe {
|
||||
// get {
|
||||
// try {
|
||||
// if (_value!.Equals(_value)) return _value;
|
||||
// } catch (COMException){ /* log something */ }
|
||||
// return default;
|
||||
// }
|
||||
// }
|
||||
public T Unsafe => _value;
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.CmdPal.Common.Services;
|
||||
using Microsoft.Windows.CommandPalette.Extensions;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.ApplicationModel.AppExtensions;
|
||||
@@ -9,9 +10,9 @@ using Windows.Win32;
|
||||
using Windows.Win32.System.Com;
|
||||
using WinRT;
|
||||
|
||||
namespace DeveloperCommandPalette;
|
||||
namespace CmdPal.Models;
|
||||
|
||||
public class ExtensionWrapper
|
||||
public class ExtensionWrapper : IExtensionWrapper
|
||||
{
|
||||
private const int HResultRpcServerNotRunning = -2147023174;
|
||||
|
||||
@@ -0,0 +1,365 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using CmdPal.Models;
|
||||
using Microsoft.CmdPal.Common.Contracts;
|
||||
using Microsoft.CmdPal.Common.Extensions;
|
||||
using Microsoft.CmdPal.Common.Services;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Windows.CommandPalette.Extensions;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.ApplicationModel.AppExtensions;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace Microsoft.Windows.CommandPalette.Services;
|
||||
|
||||
public class ExtensionService : IExtensionService, IDisposable
|
||||
{
|
||||
// private readonly ILogger _log = Log.ForContext("SourceContext", nameof(ExtensionService));
|
||||
|
||||
public event EventHandler OnExtensionsChanged = (_, _) => { };
|
||||
|
||||
private static readonly PackageCatalog _catalog = PackageCatalog.OpenForCurrentUser();
|
||||
private static readonly object _lock = new();
|
||||
private readonly SemaphoreSlim _getInstalledExtensionsLock = new(1, 1);
|
||||
private readonly SemaphoreSlim _getInstalledWidgetsLock = new(1, 1);
|
||||
|
||||
private readonly ILocalSettingsService _localSettingsService;
|
||||
|
||||
private bool _disposedValue;
|
||||
|
||||
private const string CreateInstanceProperty = "CreateInstance";
|
||||
private const string ClassIdProperty = "@ClassId";
|
||||
|
||||
private static readonly List<IExtensionWrapper> _installedExtensions = new();
|
||||
private static readonly List<IExtensionWrapper> _enabledExtensions = new();
|
||||
|
||||
public ExtensionService(ILocalSettingsService settingsService)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
var isCmdPalExtension = Task.Run(() =>
|
||||
{
|
||||
return IsValidCmdPalExtension(args.Package);
|
||||
}).Result;
|
||||
|
||||
if (isCmdPalExtension)
|
||||
{
|
||||
OnPackageChange(args.Package);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Catalog_PackageUninstalling(PackageCatalog sender, PackageUninstallingEventArgs args)
|
||||
{
|
||||
if (args.IsComplete)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (var extension in _installedExtensions)
|
||||
{
|
||||
if (extension.PackageFullName == args.Package.Id.FullName)
|
||||
{
|
||||
OnPackageChange(args.Package);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Catalog_PackageUpdating(PackageCatalog sender, PackageUpdatingEventArgs args)
|
||||
{
|
||||
if (args.IsComplete)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var isCmdPalExtension = Task.Run(() =>
|
||||
{
|
||||
return IsValidCmdPalExtension(args.TargetPackage);
|
||||
}).Result;
|
||||
|
||||
if (isCmdPalExtension)
|
||||
{
|
||||
OnPackageChange(args.TargetPackage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPackageChange(Package package)
|
||||
{
|
||||
_installedExtensions.Clear();
|
||||
_enabledExtensions.Clear();
|
||||
OnExtensionsChanged.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private static async Task<bool> IsValidCmdPalExtension(Package package)
|
||||
{
|
||||
var extensions = await AppExtensionCatalog.Open("com.microsoft.windows.commandpalette").FindAllAsync();
|
||||
foreach (var extension in extensions)
|
||||
{
|
||||
if (package.Id?.FullName == extension.Package?.Id?.FullName)
|
||||
{
|
||||
var (CmdPalProvider, classId) = await GetCmdPalExtensionPropertiesAsync(extension);
|
||||
return CmdPalProvider != null && classId.Count != 0;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static async Task<(IPropertySet?, List<string>)> GetCmdPalExtensionPropertiesAsync(AppExtension extension)
|
||||
{
|
||||
var classIds = new List<string>();
|
||||
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<IEnumerable<AppExtension>> GetInstalledAppExtensionsAsync()
|
||||
{
|
||||
return await AppExtensionCatalog.Open("com.microsoft.windows.commandpalette").FindAllAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<IExtensionWrapper>> GetInstalledExtensionsAsync(bool includeDisabledExtensions = false)
|
||||
{
|
||||
await _getInstalledExtensionsLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (_installedExtensions.Count == 0)
|
||||
{
|
||||
var extensions = await GetInstalledAppExtensionsAsync();
|
||||
foreach (var extension in extensions)
|
||||
{
|
||||
var (CmdPalProvider, classIds) = await GetCmdPalExtensionPropertiesAsync(extension);
|
||||
if (CmdPalProvider == null || classIds.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var classId in classIds)
|
||||
{
|
||||
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<ProviderType>(supportedInterface.Key, out pt))
|
||||
{
|
||||
extensionWrapper.AddProviderType(pt);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: throw warning or fire notification that extension declared unsupported extension interface
|
||||
// https://github.com/microsoft/DevHome/issues/617
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var localSettingsService = Application.Current.GetService<ILocalSettingsService>();
|
||||
var extensionUniqueId = extension.AppInfo.AppUserModelId + "!" + extension.Id;
|
||||
var isExtensionDisabled = await localSettingsService.ReadSettingAsync<bool>(extensionUniqueId + "-ExtensionDisabled");
|
||||
|
||||
_installedExtensions.Add(extensionWrapper);
|
||||
if (!isExtensionDisabled)
|
||||
{
|
||||
_enabledExtensions.Add(extensionWrapper);
|
||||
}
|
||||
|
||||
//TelemetryFactory.Get<ITelemetry>().Log(
|
||||
// "Extension_ReportInstalled",
|
||||
// LogLevel.Critical,
|
||||
// new ReportInstalledExtensionEvent(extensionUniqueId, isEnabled: !isExtensionDisabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return includeDisabledExtensions ? _installedExtensions : _enabledExtensions;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_getInstalledExtensionsLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (installedExtension.IsRunning())
|
||||
{
|
||||
installedExtension.SignalDispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<IExtensionWrapper>> GetInstalledExtensionsAsync(ProviderType providerType, bool includeDisabledExtensions = false)
|
||||
{
|
||||
var installedExtensions = await GetInstalledExtensionsAsync(includeDisabledExtensions);
|
||||
|
||||
List<IExtensionWrapper> filteredExtensions = new();
|
||||
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)
|
||||
{
|
||||
return propSet.TryGetValue(name, out var value) ? value as IPropertySet : null;
|
||||
}
|
||||
|
||||
private static object[]? GetSubPropertySetArray(IPropertySet propSet, string name)
|
||||
{
|
||||
return propSet.TryGetValue(name, out var value) ? value as object[] : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// There are cases where the extension creates multiple COM instances.
|
||||
/// </summary>
|
||||
/// <param name="activationPropSet">Activation property set object</param>
|
||||
/// <returns>List of ClassId strings associated with the activation property</returns>
|
||||
private static List<string> GetCreateInstanceList(IPropertySet activationPropSet)
|
||||
{
|
||||
var propSetList = new List<string>();
|
||||
var singlePropertySet = GetSubPropertySet(activationPropSet, CreateInstanceProperty);
|
||||
if (singlePropertySet != 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 != null)
|
||||
{
|
||||
propSetList.Add(classId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var propertySetArray = GetSubPropertySetArray(activationPropSet, CreateInstanceProperty);
|
||||
if (propertySetArray != null)
|
||||
{
|
||||
foreach (var prop in propertySetArray)
|
||||
{
|
||||
if (prop is not IPropertySet propertySet)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var classId = GetProperty(propertySet, ClassIdProperty);
|
||||
if (classId != null)
|
||||
{
|
||||
propSetList.Add(classId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return propSetList;
|
||||
}
|
||||
|
||||
private static string? GetProperty(IPropertySet propSet, string name)
|
||||
{
|
||||
return 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());
|
||||
}
|
||||
|
||||
///// <inheritdoc cref="IExtensionService.DisableExtensionIfWindowsFeatureNotAvailable(IExtensionWrapper)"/>
|
||||
//public async Task<bool> 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;
|
||||
//}
|
||||
}
|
||||
Reference in New Issue
Block a user