mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 10:46:33 +02:00
Added basic support for Windows App Actions. (#39927)
## Summary of the Pull Request Adds basic support for finding, listing, and executing Windows App Actions on files found by the Microsoft.CmdPal.Ext.Indexer extension. ## PR Checklist - [X] **Closes:** #39926 - [X] **Communication:** I've discussed this with core contributors already. If work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [X] **Localization:** All end user facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx ## Detailed Description of the Pull Request / Additional comments We also update cswin32 to stable version. <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed Validated that it doesn't show on older versions of Windows (<26100 insiders) and that it does work on newer version that have the App Actions runtime. --------- Co-authored-by: Gordon Lam (SH) <yeelam@microsoft.com> Co-authored-by: Mike Griese <migrie@microsoft.com> Co-authored-by: Leilei Zhang <leilzh@microsoft.com>
This commit is contained in:
committed by
GitHub
parent
c83be3e74c
commit
4737ec987e
@@ -221,11 +221,10 @@ public class UWPApplication : IProgram
|
||||
var capacity = 1024U;
|
||||
PWSTR outBuffer = new PWSTR((char*)(void*)Marshal.AllocHGlobal((int)capacity * sizeof(char)));
|
||||
var source = $"@{{{packageFullName}? {parsed}}}";
|
||||
void* reserved = null;
|
||||
|
||||
try
|
||||
{
|
||||
PInvoke.SHLoadIndirectString(source, outBuffer, capacity, ref reserved).ThrowOnFailure();
|
||||
PInvoke.SHLoadIndirectString(source, outBuffer.AsSpan()).ThrowOnFailure();
|
||||
|
||||
var loaded = outBuffer.ToString();
|
||||
return string.IsNullOrEmpty(loaded) ? string.Empty : loaded;
|
||||
@@ -235,7 +234,7 @@ public class UWPApplication : IProgram
|
||||
try
|
||||
{
|
||||
var sourceFallback = $"@{{{packageFullName}?{parsedFallback}}}";
|
||||
PInvoke.SHLoadIndirectString(sourceFallback, outBuffer, capacity, ref reserved).ThrowOnFailure();
|
||||
PInvoke.SHLoadIndirectString(sourceFallback, outBuffer.AsSpan()).ThrowOnFailure();
|
||||
var loaded = outBuffer.ToString();
|
||||
return string.IsNullOrEmpty(loaded) ? string.Empty : loaded;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// 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.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Native;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.System.Com;
|
||||
using WinRT;
|
||||
using WinRT.Interop;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer.Data;
|
||||
|
||||
internal static class ActionRuntimeFactory
|
||||
{
|
||||
private const string ActionRuntimeClsidStr = "C36FEF7E-35F3-4192-9F2C-AF1FD425FB85";
|
||||
|
||||
// typeof(Windows.AI.Actions.IActionRuntime).GUID
|
||||
private static readonly Guid IActionRuntimeIID = Guid.Parse("206EFA2C-C909-508A-B4B0-9482BE96DB9C");
|
||||
|
||||
public static unsafe global::Windows.AI.Actions.ActionRuntime CreateActionRuntime()
|
||||
{
|
||||
IntPtr abiPtr = default;
|
||||
try
|
||||
{
|
||||
Guid classId = Guid.Parse(ActionRuntimeClsidStr);
|
||||
Guid iid = IActionRuntimeIID;
|
||||
|
||||
var hresult = NativeMethods.CoCreateInstance(ref Unsafe.AsRef(in classId), IntPtr.Zero, NativeHelpers.CLSCTXLOCALSERVER, ref iid, out abiPtr);
|
||||
Marshal.ThrowExceptionForHR((int)hresult);
|
||||
|
||||
return MarshalInterface<global::Windows.AI.Actions.ActionRuntime>.FromAbi(abiPtr);
|
||||
}
|
||||
finally
|
||||
{
|
||||
MarshalInspectable<object>.DisposeAbi(abiPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
@@ -0,0 +1,43 @@
|
||||
// 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.Tasks;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.AI.Actions.Hosting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer.Commands;
|
||||
|
||||
internal sealed partial class ExecuteActionCommand : InvokableCommand
|
||||
{
|
||||
private readonly ActionInstance actionInstance;
|
||||
|
||||
internal ExecuteActionCommand(ActionInstance actionInstance)
|
||||
{
|
||||
this.actionInstance = actionInstance;
|
||||
this.Name = actionInstance.DisplayInfo.Description;
|
||||
this.Icon = new IconInfo(actionInstance.Definition.IconFullPath);
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
var task = Task.Run(InvokeAsync);
|
||||
task.Wait();
|
||||
|
||||
return task.Result;
|
||||
}
|
||||
|
||||
private async Task<CommandResult> InvokeAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await actionInstance.InvokeAsync();
|
||||
return CommandResult.GoHome();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return CommandResult.ShowToast("Failed to invoke action " + actionInstance.Definition.Id + ": " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,11 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Commands;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Pages;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Foundation.Metadata;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer.Data;
|
||||
|
||||
@@ -38,9 +41,24 @@ internal sealed partial class IndexerListItem : ListItem
|
||||
}
|
||||
}
|
||||
|
||||
MoreCommands = [
|
||||
IContextItem[] moreCommands = [
|
||||
..context,
|
||||
new CommandContextItem(new OpenWithCommand(indexerItem)),
|
||||
new CommandContextItem(new OpenWithCommand(indexerItem))];
|
||||
|
||||
if (ApiInformation.IsApiContractPresent("Windows.AI.Actions.ActionsContract", 4))
|
||||
{
|
||||
var actionsListContextItem = new ActionsListContextItem(indexerItem.FullPath);
|
||||
if (actionsListContextItem.AnyActions())
|
||||
{
|
||||
moreCommands = [
|
||||
.. moreCommands,
|
||||
actionsListContextItem
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
MoreCommands = [
|
||||
.. moreCommands,
|
||||
new CommandContextItem(new ShowFileInFolderCommand(indexerItem.FullPath) { Name = Resources.Indexer_Command_ShowInFolder }),
|
||||
new CommandContextItem(new CopyPathCommand(indexerItem)),
|
||||
new CommandContextItem(new OpenInConsoleCommand(indexerItem)),
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
<Content Update="Assets\FileExplorer.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Actions.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\FileExplorer.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
||||
@@ -11,6 +11,7 @@ public sealed partial class NativeHelpers
|
||||
public const uint SEEMASKINVOKEIDLIST = 12;
|
||||
|
||||
public const uint CLSCTXINPROCALL = 0x17;
|
||||
public const uint CLSCTXLOCALSERVER = 0x4;
|
||||
|
||||
public struct PropertyKeys
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"$schema": "https://aka.ms/CsWin32.schema.json",
|
||||
"allowMarshaling": false,
|
||||
"emitSingleFile": false,
|
||||
"public": true
|
||||
"comInterop": {
|
||||
"preserveSigMethods": [ "*" ]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Commands;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Data;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.AI.Actions;
|
||||
using Windows.System;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer.Pages;
|
||||
|
||||
internal sealed partial class ActionsListContextItem : CommandContextItem
|
||||
{
|
||||
private readonly string fullPath;
|
||||
private readonly List<CommandContextItem> actions = [];
|
||||
private static readonly Lock UpdateMoreCommandsLock = new();
|
||||
private static ActionRuntime actionRuntime;
|
||||
|
||||
public ActionsListContextItem(string fullPath)
|
||||
: base(new NoOpCommand())
|
||||
{
|
||||
Title = Resources.Indexer_Command_Actions;
|
||||
Icon = Icons.Actions;
|
||||
RequestedShortcut = KeyChordHelpers.FromModifiers(alt: true, vkey: VirtualKey.A);
|
||||
this.fullPath = fullPath;
|
||||
UpdateMoreCommands();
|
||||
}
|
||||
|
||||
public bool AnyActions() => actions.Count != 0;
|
||||
|
||||
private void ActionCatalog_Changed(global::Windows.AI.Actions.Hosting.ActionCatalog sender, object args)
|
||||
{
|
||||
UpdateMoreCommands();
|
||||
}
|
||||
|
||||
private void UpdateMoreCommands()
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (UpdateMoreCommandsLock)
|
||||
{
|
||||
if (actionRuntime == null)
|
||||
{
|
||||
actionRuntime = ActionRuntimeFactory.CreateActionRuntime();
|
||||
Task.Delay(500).Wait();
|
||||
}
|
||||
|
||||
actionRuntime.ActionCatalog.Changed -= ActionCatalog_Changed;
|
||||
actionRuntime.ActionCatalog.Changed += ActionCatalog_Changed;
|
||||
}
|
||||
|
||||
var extension = System.IO.Path.GetExtension(fullPath).ToLower(CultureInfo.InvariantCulture);
|
||||
ActionEntity entity = null;
|
||||
if (extension != null)
|
||||
{
|
||||
if (extension == ".jpg" || extension == ".jpeg" || extension == ".png")
|
||||
{
|
||||
entity = actionRuntime.EntityFactory.CreatePhotoEntity(fullPath);
|
||||
}
|
||||
else if (extension == ".docx" || extension == ".doc" || extension == ".pdf" || extension == ".txt")
|
||||
{
|
||||
entity = actionRuntime.EntityFactory.CreateDocumentEntity(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (entity == null)
|
||||
{
|
||||
entity = actionRuntime.EntityFactory.CreateFileEntity(fullPath);
|
||||
}
|
||||
|
||||
lock (actions)
|
||||
{
|
||||
actions.Clear();
|
||||
foreach (var actionInstance in actionRuntime.ActionCatalog.GetActionsForInputs([entity]))
|
||||
{
|
||||
actions.Add(new CommandContextItem(new ExecuteActionCommand(actionInstance)));
|
||||
}
|
||||
|
||||
MoreCommands = [.. actions];
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
actionRuntime = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ internal sealed class Icons
|
||||
|
||||
internal static IconInfo FileExplorer { get; } = IconHelpers.FromRelativePath("Assets\\FileExplorer.png");
|
||||
|
||||
internal static IconInfo Actions { get; } = IconHelpers.FromRelativePath("Assets\\Actions.png");
|
||||
|
||||
internal static IconInfo OpenFile { get; } = new("\uE8E5"); // OpenFile
|
||||
|
||||
internal static IconInfo Document { get; } = new("\uE8A5"); // Document
|
||||
|
||||
@@ -60,6 +60,15 @@ namespace Microsoft.CmdPal.Ext.Indexer.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Actions.
|
||||
/// </summary>
|
||||
internal static string Indexer_Command_Actions {
|
||||
get {
|
||||
return ResourceManager.GetString("Indexer_Command_Actions", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Browse.
|
||||
/// </summary>
|
||||
|
||||
@@ -138,6 +138,9 @@
|
||||
<data name="Indexer_Command_OpenWith" xml:space="preserve">
|
||||
<value>Open with</value>
|
||||
</data>
|
||||
<data name="Indexer_Command_Actions" xml:space="preserve">
|
||||
<value>Actions...</value>
|
||||
</data>
|
||||
<data name="Indexer_Command_ShowInFolder" xml:space="preserve">
|
||||
<value>Show in folder</value>
|
||||
</data>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using ManagedCommon;
|
||||
@@ -188,17 +189,20 @@ public static class DefaultBrowserInfo
|
||||
{
|
||||
var buffer = stackalloc char[128];
|
||||
var capacity = 128;
|
||||
void* reserved = null;
|
||||
var firstChar = str[0];
|
||||
var strPtr = &firstChar;
|
||||
|
||||
// S_OK == 0
|
||||
if (global::Windows.Win32.PInvoke.SHLoadIndirectString(
|
||||
str,
|
||||
fixed (char* pszSourceLocal = str)
|
||||
{
|
||||
if (global::Windows.Win32.PInvoke.SHLoadIndirectString(
|
||||
pszSourceLocal,
|
||||
buffer,
|
||||
(uint)capacity,
|
||||
ref reserved)
|
||||
== 0)
|
||||
{
|
||||
return new string(buffer);
|
||||
default) == 0)
|
||||
{
|
||||
return new string(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user