Pass FG rights to extensions when we access them (#38068)

Calling Win32 APIs from C# is usually easy! but mixing C#+WinRT+COM is dark and full of terrors

Closes https://github.com/zadjii-msft/PowerToys/issues/546


Co-authored-by: Manodasan Wignarajah <mawign@microsoft.com>
This commit is contained in:
Mike Griese
2025-03-24 06:29:40 -05:00
committed by GitHub
parent 43783d2cff
commit 4e7bd34c4d
2 changed files with 45 additions and 5 deletions

View File

@@ -2,6 +2,8 @@
// The Microsoft Corporation licenses this file to you under the MIT license. // The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using CommunityToolkit.Common; using CommunityToolkit.Common;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
@@ -13,7 +15,7 @@ using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CmdPal.UI.ViewModels.Models; using Microsoft.CmdPal.UI.ViewModels.Models;
using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Windows.Win32; using WinRT;
namespace Microsoft.CmdPal.UI.ViewModels; namespace Microsoft.CmdPal.UI.ViewModels;
@@ -148,14 +150,16 @@ public partial class ShellViewModel(IServiceProvider _serviceProvider, TaskSched
// need to handle that // need to handle that
_activeExtension = extension; _activeExtension = extension;
var extensionComObject = _activeExtension?.GetExtensionObject(); var extensionWinRtObject = _activeExtension?.GetExtensionObject();
if (extensionComObject != null) if (extensionWinRtObject != null)
{ {
try try
{ {
unsafe unsafe
{ {
var hr = PInvoke.CoAllowSetForegroundWindow(extensionComObject); var winrtObj = (IWinRTObject)extensionWinRtObject;
var intPtr = winrtObj.NativeObject.ThisPtr;
var hr = Native.CoAllowSetForegroundWindow(intPtr);
if (hr != 0) if (hr != 0)
{ {
Logger.LogWarning($"Error giving foreground rights: 0x{hr.Value:X8}"); Logger.LogWarning($"Error giving foreground rights: 0x{hr.Value:X8}");
@@ -174,4 +178,18 @@ public partial class ShellViewModel(IServiceProvider _serviceProvider, TaskSched
{ {
SetActiveExtension(null); SetActiveExtension(null);
} }
// You may ask yourself, why aren't we using CsWin32 for this?
// The CsWin32 projected version includes some object marshalling, like so:
//
// HRESULT CoAllowSetForegroundWindow([MarshalAs(UnmanagedType.IUnknown)] object pUnk,...)
//
// And if you do it like that, then the IForegroundTransfer interface isn't marshalled correctly
internal sealed class Native
{
[DllImport("OLE32.dll", ExactSpelling = true)]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
[SupportedOSPlatform("windows5.0")]
internal static extern unsafe global::Windows.Win32.Foundation.HRESULT CoAllowSetForegroundWindow(nint pUnk, [Optional] void* lpvReserved);
}
} }

View File

@@ -130,14 +130,36 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
// Or the command may be a stub. Future us problem. // Or the command may be a stub. Future us problem.
try try
{ {
// In the case that we're coming from a top-level command, the
// current page's host is the global instance. We only really want
// to use that as the host of last resort.
var pageHost = ViewModel.CurrentPage?.ExtensionHost; var pageHost = ViewModel.CurrentPage?.ExtensionHost;
if (pageHost == CommandPaletteHost.Instance)
{
pageHost = null;
}
var messageHost = message.ExtensionHost; var messageHost = message.ExtensionHost;
// Use the host from the current page if it has one, else use the // Use the host from the current page if it has one, else use the
// one specified in the PerformMessage for a top-level command, // one specified in the PerformMessage for a top-level command,
// else just use the global one. // else just use the global one.
var host = pageHost ?? messageHost ?? CommandPaletteHost.Instance; CommandPaletteHost host;
// Top level items can come through without a Extension set on the
// message. In that case, the `Context` is actually the
// TopLevelViewModel itself, and we can use that to get at the
// extension object.
extension = pageHost?.Extension ?? messageHost?.Extension ?? null; extension = pageHost?.Extension ?? messageHost?.Extension ?? null;
if (extension == null && message.Context is TopLevelViewModel topLevelViewModel)
{
extension = topLevelViewModel.ExtensionHost?.Extension;
host = pageHost ?? messageHost ?? topLevelViewModel?.ExtensionHost ?? CommandPaletteHost.Instance;
}
else
{
host = pageHost ?? messageHost ?? CommandPaletteHost.Instance;
}
if (extension != null) if (extension != null)
{ {