mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-20 18:20:27 +01:00
Compare commits
25 Commits
async-cpp-
...
dev/migrie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
323c9a7b23 | ||
|
|
29e13df9cb | ||
|
|
13b7ff1315 | ||
|
|
9e20d014aa | ||
|
|
8268c58ab5 | ||
|
|
37c7e3bbb8 | ||
|
|
cc4fd8f8ae | ||
|
|
e7dd3acf51 | ||
|
|
a78ce3843a | ||
|
|
b1432c7f54 | ||
|
|
62c2c60654 | ||
|
|
c5db9d2bd6 | ||
|
|
9ecd374574 | ||
|
|
dd1a60c9f6 | ||
|
|
f3588e7f70 | ||
|
|
940e71f2a8 | ||
|
|
36f85218e1 | ||
|
|
3ed3bb6a81 | ||
|
|
906602090d | ||
|
|
73bc438042 | ||
|
|
88111e2fbd | ||
|
|
7eaa701920 | ||
|
|
95cfdbb93d | ||
|
|
c198ceaa1e | ||
|
|
2990aad9fd |
@@ -56,7 +56,7 @@
|
||||
TODO: in Common.Dotnet.CsWinRT.props, on upgrade, verify RemoveCsWinRTPackageAnalyzer is no longer needed.
|
||||
This is present due to a bug in CsWinRT where WPF projects cause the analyzer to fail.
|
||||
-->
|
||||
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.3.0-prerelease.250720.1" />
|
||||
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
|
||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
|
||||
@@ -75,6 +75,7 @@
|
||||
<PackageVersion Include="SharpCompress" Version="0.37.2" />
|
||||
<!-- Don't update SkiaSharp.Views.WinUI to version 3.* branch as this brakes the HexBox control in Registry Preview. -->
|
||||
<PackageVersion Include="SkiaSharp.Views.WinUI" Version="2.88.9" />
|
||||
<PackageVersion Include="Shmuelie.WinRTServer" Version="2.1.1" />
|
||||
<PackageVersion Include="StreamJsonRpc" Version="2.21.69" />
|
||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
|
||||
<!-- Package System.CodeDom added as a hack for being able to exclude the runtime assets so they don't conflict with 8.0.1. This is a dependency of System.Management but the 8.0.1 version wasn't published to nuget. -->
|
||||
|
||||
@@ -1553,6 +1553,7 @@ SOFTWARE.
|
||||
- ScipBe.Common.Office.OneNote 3.0.1
|
||||
- SharpCompress 0.37.2
|
||||
- SkiaSharp.Views.WinUI 2.88.9
|
||||
- Shmuelie.WinRTServer 2.1.1
|
||||
- StreamJsonRpc 2.21.69
|
||||
- StyleCop.Analyzers 1.2.0-beta.556
|
||||
- System.CodeDom 9.0.8
|
||||
|
||||
@@ -38,9 +38,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<!-- this may need to be removed on future CsWinRT upgrades-->
|
||||
<Target Name="RemoveCsWinRTPackageAnalyzer" BeforeTargets="CoreCompile">
|
||||
<!-- <Target Name="RemoveCsWinRTPackageAnalyzer" BeforeTargets="CoreCompile">
|
||||
<ItemGroup>
|
||||
<Analyzer Remove="@(Analyzer)" Condition="%(Analyzer.NuGetPackageId) == 'Microsoft.Windows.CsWinRT'" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Target> -->
|
||||
</Project>
|
||||
|
||||
@@ -29,6 +29,15 @@ public partial class CommandViewModel : ExtensionObjectViewModel
|
||||
|
||||
public IconInfoViewModel Icon { get; private set; }
|
||||
|
||||
// UNDER NO CIRCUMSTANCES MAY SOMEONE WRITE TO THIS DICTIONARY.
|
||||
// This is our copy of the data from the extension.
|
||||
// Adding values to it does not add to the extension.
|
||||
// Modifying it will not modify the extension
|
||||
// (except it might, if the dictionary was passed by ref)
|
||||
private Dictionary<string, ExtensionObject<object>>? _properties;
|
||||
|
||||
public IReadOnlyDictionary<string, ExtensionObject<object>>? Properties => _properties?.AsReadOnly();
|
||||
|
||||
public CommandViewModel(ICommand? command, WeakReference<IPageContext> pageContext)
|
||||
: base(pageContext)
|
||||
{
|
||||
@@ -80,6 +89,11 @@ public partial class CommandViewModel : ExtensionObjectViewModel
|
||||
UpdateProperty(nameof(Icon));
|
||||
}
|
||||
|
||||
if (model is IExtendedAttributesProvider command2)
|
||||
{
|
||||
UpdatePropertiesFromExtension(command2);
|
||||
}
|
||||
|
||||
model.PropChanged += Model_PropChanged;
|
||||
}
|
||||
|
||||
@@ -112,6 +126,10 @@ public partial class CommandViewModel : ExtensionObjectViewModel
|
||||
var iconInfo = model.Icon;
|
||||
Icon = new(iconInfo);
|
||||
Icon.InitializeProperties();
|
||||
break;
|
||||
case nameof(_properties):
|
||||
UpdatePropertiesFromExtension(model as IExtendedAttributesProvider);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -130,4 +148,26 @@ public partial class CommandViewModel : ExtensionObjectViewModel
|
||||
model.PropChanged -= Model_PropChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePropertiesFromExtension(IExtendedAttributesProvider? model)
|
||||
{
|
||||
var propertiesFromExtension = model?.GetProperties();
|
||||
if (propertiesFromExtension == null)
|
||||
{
|
||||
_properties = null;
|
||||
return;
|
||||
}
|
||||
|
||||
_properties = [];
|
||||
|
||||
// COPY the properties into us.
|
||||
// The IDictionary that was passed to us may be marshalled by-ref or by-value, we _don't know_.
|
||||
//
|
||||
// If it's by-ref, the values are arbitrary objects that are out-of-proc.
|
||||
// If it's bu-value, then everything is in-proc, and we can't mutate the data.
|
||||
foreach (var property in propertiesFromExtension)
|
||||
{
|
||||
_properties.Add(property.Key, new(property.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ public partial class IconDataViewModel : ObservableObject, IIconData
|
||||
|
||||
IRandomAccessStreamReference? IIconData.Data => Data.Unsafe;
|
||||
|
||||
public string? FontFamily { get; private set; }
|
||||
|
||||
public IconDataViewModel(IIconData? icon)
|
||||
{
|
||||
_model = new(icon);
|
||||
@@ -43,5 +45,22 @@ public partial class IconDataViewModel : ObservableObject, IIconData
|
||||
|
||||
Icon = model.Icon;
|
||||
Data = new(model.Data);
|
||||
|
||||
if (model is IExtendedAttributesProvider icon2)
|
||||
{
|
||||
var props = icon2.GetProperties();
|
||||
|
||||
// From Raymond Chen:
|
||||
// Make sure you don't try do do something like
|
||||
// icon2.GetProperties().TryGetValue("awesomeKey", out var awesomeValue);
|
||||
// icon2.GetProperties().TryGetValue("slackerKey", out var slackerValue);
|
||||
// because each call to GetProperties() is a cross process hop, and if you
|
||||
// marshal-by-value the property set, then you don't want to throw it away and
|
||||
// re-marshal it for every property. MAKE SURE YOU CACHE IT.
|
||||
if (props?.TryGetValue("FontFamily", out var family) ?? false)
|
||||
{
|
||||
FontFamily = family as string;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +153,11 @@ public sealed class CommandProviderWrapper
|
||||
// On a BG thread here
|
||||
fallbacks = model.FallbackCommands();
|
||||
|
||||
if (model is ICommandProvider2 two)
|
||||
{
|
||||
UnsafePreCacheApiAdditions(two);
|
||||
}
|
||||
|
||||
Id = model.Id;
|
||||
DisplayName = model.DisplayName;
|
||||
Icon = new(model.Icon);
|
||||
@@ -203,6 +208,19 @@ public sealed class CommandProviderWrapper
|
||||
}
|
||||
}
|
||||
|
||||
private void UnsafePreCacheApiAdditions(ICommandProvider2 provider)
|
||||
{
|
||||
var apiExtensions = provider.GetApiExtensionStubs();
|
||||
Logger.LogDebug($"Provider supports {apiExtensions.Length} extensions");
|
||||
foreach (var a in apiExtensions)
|
||||
{
|
||||
if (a is IExtendedAttributesProvider command2)
|
||||
{
|
||||
Logger.LogDebug($"{ProviderId}: Found an IExtendedAttributesProvider");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj) => obj is CommandProviderWrapper wrapper && isValid == wrapper.isValid;
|
||||
|
||||
public override int GetHashCode() => _commandProvider.GetHashCode();
|
||||
|
||||
@@ -126,6 +126,10 @@ public class ExtensionWrapper : IExtensionWrapper
|
||||
// We'll just return out nothing.
|
||||
return;
|
||||
}
|
||||
else if (hr.Value != 0)
|
||||
{
|
||||
Logger.LogError($"Failed to find {ExtensionDisplayName}: {hr.Value} ({hr.Value:X8})");
|
||||
}
|
||||
|
||||
// Marshal.ThrowExceptionForHR(hr);
|
||||
_extensionObject = MarshalInterface<IExtension>.FromAbi((nint)extensionPtr);
|
||||
|
||||
@@ -315,7 +315,7 @@ public partial class TopLevelCommandManager : ObservableObject,
|
||||
Logger.LogDebug($"Starting {extension.PackageFullName}");
|
||||
try
|
||||
{
|
||||
await extension.StartExtensionAsync().WaitAsync(TimeSpan.FromSeconds(10));
|
||||
await extension.StartExtensionAsync().WaitAsync(TimeSpan.FromSeconds(30));
|
||||
return new CommandProviderWrapper(extension, _taskScheduler);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -329,7 +329,7 @@ public partial class TopLevelCommandManager : ObservableObject,
|
||||
{
|
||||
try
|
||||
{
|
||||
return await LoadTopLevelCommandsFromProvider(wrapper!).WaitAsync(TimeSpan.FromSeconds(10));
|
||||
return await LoadTopLevelCommandsFromProvider(wrapper!).WaitAsync(TimeSpan.FromSeconds(30));
|
||||
}
|
||||
catch (TimeoutException)
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@ public sealed class IconCacheService(DispatcherQueue dispatcherQueue)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(icon.Icon))
|
||||
{
|
||||
var source = IconPathConverter.IconSourceMUX(icon.Icon, false);
|
||||
var source = IconPathConverter.IconSourceMUX(icon.Icon, false, icon.FontFamily);
|
||||
return source;
|
||||
}
|
||||
else if (icon.Data is not null)
|
||||
|
||||
@@ -158,7 +158,7 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
// Return Value:
|
||||
// - An IconElement with its IconSource set, if possible.
|
||||
template<typename TIconSource>
|
||||
TIconSource _getIconSource(const winrt::hstring& iconPath, bool monochrome, const int targetSize)
|
||||
TIconSource _getIconSource(const winrt::hstring& iconPath, bool monochrome, const winrt::hstring& fontFamily, const int targetSize)
|
||||
{
|
||||
TIconSource iconSource{ nullptr };
|
||||
|
||||
@@ -187,6 +187,11 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
{
|
||||
icon.FontFamily(winrt::Microsoft::UI::Xaml::Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" });
|
||||
}
|
||||
else if (!fontFamily.empty())
|
||||
{
|
||||
icon.FontFamily(winrt::Microsoft::UI::Xaml::Media::FontFamily{ fontFamily });
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Note: you _do_ need to manually set the font here.
|
||||
@@ -225,9 +230,9 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
// return _getIconSource<Windows::UI::Xaml::Controls::IconSource>(path, false);
|
||||
// }
|
||||
|
||||
static Microsoft::UI::Xaml::Controls::IconSource _IconSourceMUX(const hstring& path, bool monochrome, const int targetSize)
|
||||
static Microsoft::UI::Xaml::Controls::IconSource _IconSourceMUX(const hstring& path, bool monochrome, const winrt::hstring& fontFamily, const int targetSize)
|
||||
{
|
||||
return _getIconSource<Microsoft::UI::Xaml::Controls::IconSource>(path, monochrome, targetSize);
|
||||
return _getIconSource<Microsoft::UI::Xaml::Controls::IconSource>(path, monochrome, fontFamily, targetSize);
|
||||
}
|
||||
|
||||
static SoftwareBitmap _convertToSoftwareBitmap(HICON hicon,
|
||||
@@ -343,13 +348,14 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
|
||||
MUX::Controls::IconSource IconPathConverter::IconSourceMUX(const winrt::hstring& iconPath,
|
||||
const bool monochrome,
|
||||
const winrt::hstring& fontFamily,
|
||||
const int targetSize)
|
||||
{
|
||||
std::wstring_view iconPathWithoutIndex;
|
||||
const auto indexOpt = _getIconIndex(iconPath, iconPathWithoutIndex);
|
||||
if (!indexOpt.has_value())
|
||||
{
|
||||
return _IconSourceMUX(iconPath, monochrome, targetSize);
|
||||
return _IconSourceMUX(iconPath, monochrome, fontFamily, targetSize);
|
||||
}
|
||||
|
||||
const auto bitmapSource = _getImageIconSourceForBinary(iconPathWithoutIndex, indexOpt.value(), targetSize);
|
||||
@@ -369,7 +375,7 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
const auto indexOpt = _getIconIndex(iconPath, iconPathWithoutIndex);
|
||||
if (!indexOpt.has_value())
|
||||
{
|
||||
auto source = IconSourceMUX(iconPath, false, targetSize);
|
||||
auto source = IconSourceMUX(iconPath, false, L"", targetSize);
|
||||
Microsoft::UI::Xaml::Controls::IconSourceElement icon;
|
||||
icon.IconSource(source);
|
||||
return icon;
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
||||
|
||||
//static Windows::UI::Xaml::Controls::IconElement IconWUX(const winrt::hstring& iconPath);
|
||||
//static Windows::UI::Xaml::Controls::IconSource IconSourceWUX(const winrt::hstring& iconPath);
|
||||
static Microsoft::UI::Xaml::Controls::IconSource IconSourceMUX(const winrt::hstring& iconPath, bool convertToGrayscale, const int targetSize=24);
|
||||
static Microsoft::UI::Xaml::Controls::IconSource IconSourceMUX(const winrt::hstring& iconPath, bool convertToGrayscale, const winrt::hstring& fontFamily, const int targetSize=24);
|
||||
static Microsoft::UI::Xaml::Controls::IconElement IconMUX(const winrt::hstring& iconPath);
|
||||
static Microsoft::UI::Xaml::Controls::IconElement IconMUX(const winrt::hstring& iconPath, const int targetSize);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Microsoft.Terminal.UI
|
||||
{
|
||||
// static Windows.UI.Xaml.Controls.IconElement IconWUX(String path);
|
||||
// static Windows.UI.Xaml.Controls.IconSource IconSourceWUX(String path);
|
||||
static Microsoft.UI.Xaml.Controls.IconSource IconSourceMUX(String path, Boolean convertToGrayscale);
|
||||
static Microsoft.UI.Xaml.Controls.IconSource IconSourceMUX(String path, Boolean convertToGrayscale, String fontFamily);
|
||||
static Microsoft.UI.Xaml.Controls.IconElement IconMUX(String path);
|
||||
static Microsoft.UI.Xaml.Controls.IconElement IconMUX(String path, Int32 targetSize);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
author: Mike Griese
|
||||
created on: 2024-07-19
|
||||
last updated: 2025-03-10
|
||||
last updated: 2025-08-08
|
||||
issue id: n/a
|
||||
---
|
||||
|
||||
@@ -1410,8 +1410,8 @@ interface IDetailsLink requires IDetailsData {
|
||||
Windows.Foundation.Uri Link { get; };
|
||||
String Text { get; };
|
||||
}
|
||||
interface IDetailsCommand requires IDetailsData {
|
||||
ICommand Command { get; };
|
||||
interface IDetailsCommands requires IDetailsData {
|
||||
ICommand[] Commands { get; };
|
||||
}
|
||||
[uuid("58070392-02bb-4e89-9beb-47ceb8c3d741")]
|
||||
interface IDetailsSeparator requires IDetailsData {}
|
||||
@@ -1936,6 +1936,115 @@ When displaying a page:
|
||||
* The title will be `IPage.Title ?? ICommand.Name`
|
||||
* The icon will be `ICommand.Icon`
|
||||
|
||||
## Addenda I: API additions (ICommandProvider2)
|
||||
|
||||
In experiments with extending our API, we've found some quirks with the way
|
||||
that we use WinRT's metadata-based marshalling (MBM). Typically, you'd add
|
||||
another contract version, add the new runtimeclass under the new contract
|
||||
version, and then have the client app just check if that contract is available.
|
||||
|
||||
However, we're not using `runtimeclass`es that are exposed from the extensions.
|
||||
Everything is being transferred over MBM, based on the
|
||||
`Microsoft.CommandPalette.Extensions.winmd`. And out-of-proc MBM has some
|
||||
limitations. You can essentially only have a linear chain of requires for
|
||||
extension interfaces.
|
||||
|
||||
> E.g. if it implements `IWidget2` and `IWidget2 requires IWidget`, and the object's `GetRuntimeClassName` gives `IWidget2`, we know to look at `IWidget2` directly and `IWidget` due to requires.
|
||||
>
|
||||
> The unfortunate thing for the developer experience when authoring an extension with cppwinrt/CsWinRT implementations of interfaces, is they implement each interface separately. So the `IInspectable::GetRuntimeClassName` method inherited by `Interface1` gives `"Interface1"` and the method inherited by `Interface2` gives `"Interface2"`.
|
||||
>
|
||||
> Only one of these interfaces can be what the object responds to with a QI for `IInspectable`, and that's the implementation that MBM calls.
|
||||
|
||||
That means we can't just add another interface easily. But what we can do:
|
||||
|
||||
> It might be possible to prefill the cache with the interfaces in question by
|
||||
> marshaling objects that implement each of the interfaces in a way that
|
||||
> registration-free MBM can work with.
|
||||
>
|
||||
> E.g. to keep it simple, marshal an
|
||||
> instance of a separate implementation class per interface that "implements"
|
||||
> each interface
|
||||
|
||||
So that's exactly what we're going to do, because it works. As an example,
|
||||
we're going to add the following interface to our API:
|
||||
|
||||
```csharp
|
||||
interface IExtendedAttributesProvider
|
||||
{
|
||||
Windows.Foundation.Collections.IMap<String, Object> GetProperties();
|
||||
};
|
||||
|
||||
interface ICommandProvider2 requires ICommandProvider
|
||||
{
|
||||
Object[] GetApiExtensionStubs();
|
||||
};
|
||||
```
|
||||
|
||||
`IExtendedAttributesProvider` is just a simple interface, indicating that there's some
|
||||
property bag of additional values that the host could read. We're starting with
|
||||
this, because it's a helpful tool for us to add arbitrary properties to object
|
||||
in an experimental fashion. We can continue to add more things we read from
|
||||
this property set, without breaking the ABI.
|
||||
|
||||
As an example, `ICommand` proves uniquely challenging to extend, because it has
|
||||
both the `IInvokableCommand` and `IPage` family trees of interfaces which
|
||||
extend from it. Typically, it would be impossible for a class to be defined as
|
||||
|
||||
```cs
|
||||
class MyCommandWithProperties : IInvokableCommand, IExtendedAttributesProvider { ... }
|
||||
```
|
||||
|
||||
because Command Palette would only ever see the _first_ interface
|
||||
(`IInvokableCommand`) via MBM, and would never be able to check if an extension
|
||||
object was an `IExtendedAttributesProvider`. But a class defined like
|
||||
|
||||
```cs
|
||||
class CommandWithOnlyProperties : IExtendedAttributesProvider { ... }
|
||||
```
|
||||
|
||||
will populate the WinRT type cache in Command Palette with the type information
|
||||
for `ICommandWithProperties`. In fact, if Command Palette has the
|
||||
`IExtendedAttributesProvider` type info in it's cache, and then later receives a new
|
||||
`MyCommandWithProperties` object, it'll actually be able to know that
|
||||
`MyCommandWithProperties` is an `IExtendedAttributesProvider`. WinRT is just weird
|
||||
like that some times.
|
||||
|
||||
`ICommandProvider2` is where the magic happens. This is a _linear_ addition to
|
||||
`ICommandProvider`, which merely adds a method to return a set of objects.
|
||||
Extensions can implement that method, by returning out stub implementations of
|
||||
all the future additions to the API that we may add. In so doing, CmdPal will
|
||||
be able to ask each extension for these stubs, pre-load the type cache for each
|
||||
extension, and then never have to worry in the future.
|
||||
|
||||
As an example:
|
||||
|
||||
```cs
|
||||
public partial class SamplePagesCommandsProvider : CommandProvider, ICommandProvider2 {
|
||||
public SamplePagesCommandsProvider() {
|
||||
DisplayName = "Sample Pages Commands";
|
||||
Icon = new IconInfo("\uE82D");
|
||||
}
|
||||
public override ICommandItem[] TopLevelCommands() {
|
||||
return [
|
||||
new CommandItem(new SamplesListPage()) { Title = "Sample Pages", Subtitle = "View example commands" },
|
||||
];
|
||||
}
|
||||
|
||||
// Here is where we enable support for future additions to the API
|
||||
public object[] GetApiExtensionStubs() {
|
||||
return [new SupportCommandsWithProperties()];
|
||||
}
|
||||
private sealed partial class SupportCommandsWithProperties : IExtendedAttributesProvider {
|
||||
public IDictionary<string, object>? GetProperties() => null;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Fortunately, we can put all of that (`GetApiExtensionStubs`,
|
||||
`SupportCommandsWithProperties`) directly in `Toolkit.CommandProvider`, so
|
||||
developers won't have to do anything. The toolkit will just do the right thing
|
||||
for them.
|
||||
|
||||
## Class diagram
|
||||
|
||||
@@ -2210,6 +2319,8 @@ this prevents us from being able to use `[contract]` attributes to add to the
|
||||
interfaces. We'll instead need to rely on the tried-and-true method of adding a
|
||||
`IFoo2` when we want to add methods to `IFoo`.
|
||||
|
||||
[Addenda I](#addenda-i-api-additions-icommandprovider2) talks a little more on some of the challenges with adding more APIs.
|
||||
|
||||
[^1]: In this example, as in other places, I've referenced a
|
||||
`Microsoft.DevPal.Extensions.InvokableCommand` class, as the base for that action.
|
||||
Our SDK will include partial class implementations for interfaces like
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
// 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.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Foundation;
|
||||
using Windows.System;
|
||||
using Windows.Win32;
|
||||
|
||||
@@ -169,6 +172,131 @@ internal sealed partial class SampleListPage : ListPage
|
||||
{
|
||||
Title = "Get the name of the Foreground window",
|
||||
},
|
||||
|
||||
new ListItem(new CommandWithProperties())
|
||||
{
|
||||
Title = "I have properties",
|
||||
},
|
||||
new ListItem(new OtherCommandWithProperties())
|
||||
{
|
||||
Title = "I also have properties",
|
||||
},
|
||||
|
||||
// new ListItem(new NoOpCommand() { Icon = new(new OneFontIconData("\u0027", "Wingdings")), Name = "one" }) { Title = "OneFontIconData", Tags = [new Tag("DynamicallyAccessedMembers"), new Tag("internal sealed"), new Tag("in samples")] },
|
||||
// new ListItem(new NoOpCommand() { Icon = new(new TwoFontIconData("\u0028", "Wingdings")), Name = "two" }) { Title = "TwoFontIconData", Tags = [new Tag(" "), new Tag("internal sealed"), new Tag("in samples")] },
|
||||
// new ListItem(new NoOpCommand() { Icon = new(new ThreeFontIconData("\u0029", "Wingdings")), Name = "three" }) { Title = "ThreeFontIconData", Tags = [new Tag("DynamicallyAccessedMembers"), new Tag("PUBLIC sealed"), new Tag("in samples")] },
|
||||
];
|
||||
}
|
||||
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
|
||||
internal sealed partial class CommandWithProperties : InvokableCommand, IExtendedAttributesProvider
|
||||
{
|
||||
private FontIconData _icon = new("\u0026", "Wingdings");
|
||||
|
||||
public override IconInfo Icon => new(_icon, _icon);
|
||||
|
||||
public override string Name => "Whatever";
|
||||
|
||||
public IDictionary<string, object> GetProperties() => new Dictionary<string, object>()
|
||||
{
|
||||
{ "Foo", "bar" },
|
||||
{ "Secret", 42 },
|
||||
{ "hmm?", null },
|
||||
};
|
||||
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Microsoft.CommandPalette.Extensions.Toolkit.FontIconData))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(IDictionary<string, object>))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Dictionary<string, object>))]
|
||||
public static void PreserveTypes()
|
||||
{
|
||||
}
|
||||
|
||||
public CommandWithProperties()
|
||||
{
|
||||
PreserveTypes();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed partial class OtherCommandWithProperties : IExtendedAttributesProvider, IInvokableCommand
|
||||
{
|
||||
public string Name => "Whatever 2";
|
||||
|
||||
public IIconInfo Icon => new IconInfo("\uF146");
|
||||
|
||||
public string Id => string.Empty;
|
||||
|
||||
public event TypedEventHandler<object, IPropChangedEventArgs> PropChanged;
|
||||
|
||||
public ICommandResult Invoke(object sender)
|
||||
{
|
||||
PropChanged?.Invoke(this, new PropChangedEventArgs(nameof(Name)));
|
||||
return CommandResult.ShowToast("whoop");
|
||||
}
|
||||
|
||||
public IDictionary<string, object> GetProperties() => new Dictionary<string, object>()
|
||||
{
|
||||
{ "yo", "dog" },
|
||||
{ "Secret", 12345 },
|
||||
{ "hmm?", null },
|
||||
};
|
||||
}
|
||||
|
||||
// [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
|
||||
// internal sealed partial class OneFontIconData : IconData, IExtendedAttributesProvider
|
||||
// {
|
||||
// private string FontFamily { get; set; }
|
||||
|
||||
// private readonly IDictionary<string, object> _properties;
|
||||
|
||||
// public IDictionary<string, object> GetProperties() => _properties;
|
||||
|
||||
// public OneFontIconData(string glyph, string fontFamily)
|
||||
// : base(glyph)
|
||||
// {
|
||||
// FontFamily = fontFamily;
|
||||
// _properties = new Dictionary<string, object>()
|
||||
// {
|
||||
// { "FontFamily", FontFamily },
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
|
||||
// internal sealed partial class TwoFontIconData : IconData, IExtendedAttributesProvider
|
||||
// {
|
||||
// private string FontFamily { get; set; }
|
||||
|
||||
// private readonly IDictionary<string, object> _properties;
|
||||
|
||||
// public IDictionary<string, object> GetProperties() => _properties;
|
||||
|
||||
// public TwoFontIconData(string glyph, string fontFamily)
|
||||
// : base(glyph)
|
||||
// {
|
||||
// FontFamily = fontFamily;
|
||||
// _properties = new Dictionary<string, object>()
|
||||
// {
|
||||
// { "FontFamily", FontFamily },
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
|
||||
// [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
|
||||
// public sealed partial class ThreeFontIconData : IconData, IExtendedAttributesProvider
|
||||
// {
|
||||
// private string FontFamily { get; set; }
|
||||
|
||||
// private readonly IDictionary<string, object> _properties;
|
||||
|
||||
// public IDictionary<string, object> GetProperties() => _properties;
|
||||
|
||||
// public ThreeFontIconData(string glyph, string fontFamily)
|
||||
// : base(glyph)
|
||||
// {
|
||||
// FontFamily = fontFamily;
|
||||
// _properties = new Dictionary<string, object>()
|
||||
// {
|
||||
// { "FontFamily", FontFamily },
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -4,33 +4,92 @@
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Shmuelie.WinRTServer;
|
||||
using Shmuelie.WinRTServer.CsWinRT;
|
||||
using WinRT;
|
||||
|
||||
namespace SamplePagesExtension;
|
||||
|
||||
public class Program
|
||||
{
|
||||
private static ManualResetEvent _comServerEventHandle = new(false);
|
||||
|
||||
[MTAThread]
|
||||
[DynamicWindowsRuntimeCast(typeof(IExtension))]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
if (args.Length > 0 && args[0] == "-RegisterProcessAsComServer")
|
||||
{
|
||||
using ExtensionServer server = new();
|
||||
var extensionDisposedEvent = new ManualResetEvent(false);
|
||||
var extensionInstance = new SampleExtension(extensionDisposedEvent);
|
||||
global::Shmuelie.WinRTServer.ComServer server = new();
|
||||
|
||||
// We are instantiating an extension instance once above, and returning it every time the callback in RegisterExtension below is called.
|
||||
// This makes sure that only one instance of SampleExtension is alive, which is returned every time the host asks for the IExtension object.
|
||||
// If you want to instantiate a new instance each time the host asks, create the new instance inside the delegate.
|
||||
server.RegisterExtension(() => extensionInstance);
|
||||
// ManualResetEvent extensionDisposedEvent = new(false);
|
||||
|
||||
//// We are instantiating an extension instance once above, and returning it every time the callback in RegisterExtension below is called.
|
||||
//// This makes sure that only one instance of SampleExtension is alive, which is returned every time the host asks for the IExtension object.
|
||||
//// If you want to instantiate a new instance each time the host asks, create the new instance inside the delegate.
|
||||
// SampleExtension extensionInstance = new(extensionDisposedEvent);
|
||||
// server.RegisterClass<SampleExtension, IExtension>(() => extensionInstance);
|
||||
RegisterExtension<SampleExtension, IExtension>(
|
||||
server,
|
||||
(EventWaitHandle handle) =>
|
||||
{
|
||||
SampleExtension extension = new();
|
||||
|
||||
extension.Disposed += (s, e) =>
|
||||
{
|
||||
handle.Set();
|
||||
};
|
||||
return extension;
|
||||
});
|
||||
|
||||
server.Start();
|
||||
|
||||
// This will make the main thread wait until the event is signalled by the extension class.
|
||||
// Since we have a single instance of the extension object, we exit as soon as it is disposed.
|
||||
extensionDisposedEvent.WaitOne();
|
||||
_comServerEventHandle.WaitOne();
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Not being launched as a Extension... exiting.");
|
||||
}
|
||||
}
|
||||
|
||||
[DynamicWindowsRuntimeCast(typeof(IExtension))]
|
||||
public static void RegisterExtension<TExtension, TInterface>(
|
||||
ComServer server,
|
||||
Func<EventWaitHandle, TExtension> factory)
|
||||
where TExtension : class, TInterface, new()
|
||||
{
|
||||
TExtension extension = null;
|
||||
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
_comServerEventHandle.WaitOne();
|
||||
extension = null;
|
||||
});
|
||||
|
||||
server.RegisterClass<TExtension, TInterface>(() =>
|
||||
{
|
||||
if (extension == null)
|
||||
{
|
||||
extension = factory(_comServerEventHandle);
|
||||
}
|
||||
|
||||
return extension!;
|
||||
});
|
||||
|
||||
// Shmuelie.WinRTServer.CsWinRT.ComServerExtensions.RegisterClass<TExtension, TInterface>(
|
||||
// server,
|
||||
// () =>
|
||||
// {
|
||||
// if (extension == null)
|
||||
// {
|
||||
// extension = factory(_comServerEventHandle);
|
||||
// }
|
||||
|
||||
// return extension!;
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"SamplePagesExtension (Package)": {
|
||||
"commandName": "MsixPackage",
|
||||
"doNotLaunchApp": true,
|
||||
"nativeDebugging": true
|
||||
"nativeDebugging": false
|
||||
},
|
||||
"SamplePagesExtension (Unpackaged)": {
|
||||
"commandName": "Project"
|
||||
|
||||
@@ -4,23 +4,26 @@
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace SamplePagesExtension;
|
||||
|
||||
[ComVisible(true)]
|
||||
// [ComVisible(true)]
|
||||
// [ComDefaultInterface(typeof(IExtension))]
|
||||
[Guid("6112D28D-6341-45C8-92C3-83ED55853A9F")]
|
||||
[ComDefaultInterface(typeof(IExtension))]
|
||||
public sealed partial class SampleExtension : IExtension, IDisposable
|
||||
|
||||
// [global::WinRT.WinRTExposedType]
|
||||
public sealed partial class SampleExtension : IExtension, IDisposable// , IDynamicInterfaceCastable
|
||||
{
|
||||
private readonly ManualResetEvent _extensionDisposedEvent;
|
||||
private bool disposed;
|
||||
|
||||
public event TypedEventHandler<IExtension, object> Disposed;
|
||||
|
||||
private readonly SamplePagesCommandsProvider _provider = new();
|
||||
|
||||
public SampleExtension(ManualResetEvent extensionDisposedEvent)
|
||||
public SampleExtension()
|
||||
{
|
||||
this._extensionDisposedEvent = extensionDisposedEvent;
|
||||
}
|
||||
|
||||
public object GetProvider(ProviderType providerType)
|
||||
@@ -36,6 +39,19 @@ public sealed partial class SampleExtension : IExtension, IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this._extensionDisposedEvent.Set();
|
||||
if (!disposed)
|
||||
{
|
||||
Disposed?.Invoke(this, null);
|
||||
_provider.Dispose();
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// public bool IsInterfaceImplemented(RuntimeTypeHandle interfaceType, bool throwIfNotImplemented)
|
||||
// {
|
||||
// Debug.WriteLine($"{interfaceType}");
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// public RuntimeTypeHandle GetInterfaceImplementation(RuntimeTypeHandle interfaceType) => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,39 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<!-- <Import Project="..\..\..\..\Common.Dotnet.PrepareGeneratedFolder.targets" />
|
||||
<PropertyGroup>
|
||||
<WindowsSdkPackageVersion>10.0.26100.68-preview</WindowsSdkPackageVersion>
|
||||
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
|
||||
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
|
||||
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<NoWarn></NoWarn>
|
||||
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
|
||||
<WarningsNotAsErrors>CA1824;CA1416;CA1720;CA1859;CA2263;CA2022;MVVMTK0045;MVVMTK0049</WarningsNotAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>portable</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<DefineConstants>RELEASE;TRACE</DefineConstants>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup> -->
|
||||
|
||||
|
||||
<!-- ####################################################### -->
|
||||
|
||||
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
@@ -11,7 +45,6 @@
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPalExtensions\$(RootNamespace)</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<GenerateAppxPackageOnBuild>true</GenerateAppxPackageOnBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -38,6 +71,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Shmuelie.WinRTServer" />
|
||||
</ItemGroup>
|
||||
<!--
|
||||
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
|
||||
@@ -64,6 +98,7 @@
|
||||
<PropertyGroup>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<WarningsNotAsErrors>IL2104;$(WarningsNotAsErrors)</WarningsNotAsErrors>
|
||||
<PublishAot>true</PublishAot>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
public abstract partial class CommandProvider : ICommandProvider
|
||||
public abstract partial class CommandProvider : ICommandProvider, ICommandProvider2
|
||||
{
|
||||
public virtual string Id { get; protected set; } = string.Empty;
|
||||
|
||||
@@ -47,4 +47,26 @@ public abstract partial class CommandProvider : ICommandProvider
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is used to manually populate the WinRT type cache in CmdPal with
|
||||
/// any interfaces that might not follow a straight linear path of requires.
|
||||
///
|
||||
/// You don't need to call this as an extension author.
|
||||
/// </summary>
|
||||
/// <returns>an array of objects that implement all the leaf interfaces we support</returns>
|
||||
public object[] GetApiExtensionStubs()
|
||||
{
|
||||
return [new SupportCommandsWithProperties()];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A stub class which implements IExtendedAttributesProvider. Just marshalling this
|
||||
/// across the ABI will be enough for CmdPal to store IExtendedAttributesProvider in
|
||||
/// its type cache.
|
||||
/// </summary>
|
||||
private sealed partial class SupportCommandsWithProperties : IExtendedAttributesProvider
|
||||
{
|
||||
public IDictionary<string, object>? GetProperties() => null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// 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.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an icon that is a font glyph.
|
||||
/// This is used for icons that are defined by a specific font face,
|
||||
/// such as Wingdings.
|
||||
///
|
||||
/// Note that Command Palette will default to using the Segoe Fluent Icons,
|
||||
/// Segoe MDL2 Assets font for glyphs in the Segoe UI Symbol range, or Segoe
|
||||
/// UI for any other glyphs. This class is only needed if you want a non-Segoe
|
||||
/// font icon.
|
||||
/// </summary>
|
||||
///
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
|
||||
public partial class FontIconData : IconData, IExtendedAttributesProvider
|
||||
{
|
||||
private string FontFamily { get; set; }
|
||||
|
||||
private readonly IDictionary<string, object> _properties;
|
||||
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Dictionary<string, object>))]
|
||||
public IDictionary<string, object>? GetProperties() => _properties;
|
||||
|
||||
public FontIconData(string glyph, string fontFamily)
|
||||
: base(glyph)
|
||||
{
|
||||
FontFamily = fontFamily;
|
||||
_properties = new Dictionary<string, object>()
|
||||
{
|
||||
{ "FontFamily", FontFamily },
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,12 @@ public partial class IconInfo : IIconInfo
|
||||
Dark = dark;
|
||||
}
|
||||
|
||||
public IconInfo(IconData icon)
|
||||
{
|
||||
Light = icon;
|
||||
Dark = icon;
|
||||
}
|
||||
|
||||
internal IconInfo()
|
||||
: this(string.Empty)
|
||||
{
|
||||
|
||||
@@ -364,5 +364,17 @@ namespace Microsoft.CommandPalette.Extensions
|
||||
void InitializeWithHost(IExtensionHost host);
|
||||
};
|
||||
|
||||
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
||||
interface IExtendedAttributesProvider
|
||||
{
|
||||
Windows.Foundation.Collections.IMap<String, Object> GetProperties();
|
||||
};
|
||||
|
||||
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
||||
interface ICommandProvider2 requires ICommandProvider
|
||||
{
|
||||
Object[] GetApiExtensionStubs();
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user