From 3f5418132d9a92f1651298371a13bdc9fd79999f Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 11 Feb 2026 05:51:00 -0600 Subject: [PATCH] [CmdPal] Fix context menu command items (#45499) maintainers: we talked about this at length on Teams > Seems like adding `IExtendedAttributesProvider` onto `CommandItem` is what broke this. I don't know why. I'm not gonna pretend to understand the cswinrt voodoo that's causing it to pick `IEAP` as the leaf interface instead of `ICommandContextItem`. drive by: fix the sample project on ARM _resurrected from #45329 because spellbot killed that PR_ --- .../cmdpal/ext/SamplePagesExtension/Program.cs | 18 ++++++++++++------ .../SamplePagesExtension.csproj | 1 + .../CommandItem.cs | 8 +++++++- .../FallbackCommandItem.cs | 4 ++-- .../ListItem.cs | 4 ++-- 5 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/modules/cmdpal/ext/SamplePagesExtension/Program.cs b/src/modules/cmdpal/ext/SamplePagesExtension/Program.cs index 5b099633c8..781f3b2897 100644 --- a/src/modules/cmdpal/ext/SamplePagesExtension/Program.cs +++ b/src/modules/cmdpal/ext/SamplePagesExtension/Program.cs @@ -1,10 +1,12 @@ -// Copyright (c) Microsoft Corporation +// 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; using System.Threading; using Microsoft.CommandPalette.Extensions; +using Shmuelie.WinRTServer; +using Shmuelie.WinRTServer.CsWinRT; namespace SamplePagesExtension; @@ -15,18 +17,22 @@ public class Program { 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(); + + 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. - server.RegisterExtension(() => extensionInstance); + SampleExtension extensionInstance = new(extensionDisposedEvent); + server.RegisterClass(() => extensionInstance); + 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. + // Since we have single instance of the extension object, we exit as soon as it is disposed. extensionDisposedEvent.WaitOne(); + server.Stop(); + server.UnsafeDispose(); } else { diff --git a/src/modules/cmdpal/ext/SamplePagesExtension/SamplePagesExtension.csproj b/src/modules/cmdpal/ext/SamplePagesExtension/SamplePagesExtension.csproj index b9a8acc29d..59ae78737f 100644 --- a/src/modules/cmdpal/ext/SamplePagesExtension/SamplePagesExtension.csproj +++ b/src/modules/cmdpal/ext/SamplePagesExtension/SamplePagesExtension.csproj @@ -34,6 +34,7 @@ + all diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/CommandItem.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/CommandItem.cs index 1d80e1442e..fc2de06548 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/CommandItem.cs +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/CommandItem.cs @@ -8,8 +8,14 @@ using WinRT; namespace Microsoft.CommandPalette.Extensions.Toolkit; -public partial class CommandItem : BaseObservable, ICommandItem, IExtendedAttributesProvider +public partial class CommandItem : BaseObservable, ICommandItem { + // NOTE TO MAINTAINERS: Do NOT implement `IExtendedAttributesProvider` here + // directly. Instead, implement it in derived classes like `ListItem` where + // appropriate. + // + // Putting it directly here will cause out-of-proc extensions to fail to + // load the context menu commands, for unknown CsWinRT reasons. private readonly PropertySet _extendedAttributes = new(); private ICommand? _command; diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/FallbackCommandItem.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/FallbackCommandItem.cs index 0e14ce570f..db36e26992 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/FallbackCommandItem.cs +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/FallbackCommandItem.cs @@ -1,10 +1,10 @@ -// Copyright (c) Microsoft Corporation +// 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.CommandPalette.Extensions.Toolkit; -public partial class FallbackCommandItem : CommandItem, IFallbackCommandItem, IFallbackHandler, IFallbackCommandItem2 +public partial class FallbackCommandItem : CommandItem, IFallbackCommandItem, IFallbackHandler, IFallbackCommandItem2, IExtendedAttributesProvider { private readonly IFallbackHandler? _fallbackHandler; diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ListItem.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ListItem.cs index 52cef6e99a..d9bafa67ad 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ListItem.cs +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/ListItem.cs @@ -1,10 +1,10 @@ -// Copyright (c) Microsoft Corporation +// 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.CommandPalette.Extensions.Toolkit; -public partial class ListItem : CommandItem, IListItem +public partial class ListItem : CommandItem, IListItem, IExtendedAttributesProvider { public virtual ITag[] Tags { get; set => SetProperty(ref field, value); } = [];