Skip to content

Commit 6b5bf5b

Browse files
authored
Feature: Added support for Windows App Actions (#17178)
1 parent 9dfc475 commit 6b5bf5b

File tree

6 files changed

+160
-26
lines changed

6 files changed

+160
-26
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<TargetFrameworkVersion>net9.0</TargetFrameworkVersion>
55
<TargetWindowsVersion>10.0.22621.0</TargetWindowsVersion>
66
<MinimalWindowsVersion>10.0.19041.0</MinimalWindowsVersion>
7-
<WindowsSdkPackageVersion>10.0.22621.57</WindowsSdkPackageVersion>
7+
<WindowsSdkPackageVersion>10.0.26100.67-preview</WindowsSdkPackageVersion>
88
<WindowsTargetFramework>$(TargetFrameworkVersion)-windows$(TargetWindowsVersion)</WindowsTargetFramework>
99
</PropertyGroup>
1010
</Project>

src/Files.App/Data/Items/NavigationBarSuggestionItem.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@
33

44
using Files.App.Controls;
55
using Microsoft.UI.Xaml;
6+
using Microsoft.UI.Xaml.Media;
7+
using Windows.AI.Actions.Hosting;
68

79
namespace Files.App.Data.Items
810
{
911
[Obsolete("Remove once Omnibar goes out of experimental.")]
1012
public sealed partial class NavigationBarSuggestionItem : ObservableObject, IOmnibarTextMemberPathProvider
1113
{
14+
private ImageSource? _ActionIconSource;
15+
public ImageSource? ActionIconSource { get => _ActionIconSource; set => SetProperty(ref _ActionIconSource, value); }
16+
1217
private Style? _ThemedIconStyle;
1318
public Style? ThemedIconStyle { get => _ThemedIconStyle; set => SetProperty(ref _ThemedIconStyle, value); }
1419

@@ -47,6 +52,14 @@ public string? PrimaryDisplayPreMatched
4752
private set => SetProperty(ref _PrimaryDisplayPreMatched, value);
4853
}
4954

55+
56+
private ActionInstance? _ActionInstance;
57+
public ActionInstance? ActionInstance
58+
{
59+
get => _ActionInstance;
60+
set => SetProperty(ref _ActionInstance, value);
61+
}
62+
5063
private string? _PrimaryDisplayMatched;
5164
public string? PrimaryDisplayMatched
5265
{
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using System.Runtime.InteropServices;
2+
using Windows.AI.Actions;
3+
using Windows.AI.Actions.Hosting;
4+
using Windows.Win32;
5+
using Windows.Win32.Foundation;
6+
using Windows.Win32.System.Com;
7+
using WinRT;
8+
9+
namespace Files.App.Extensions
10+
{
11+
internal class ActionManager
12+
{
13+
internal static ActionManager Instance => _instance ??= new();
14+
15+
private static ActionManager? _instance;
16+
17+
// NOTE: This Guid is subject to change in the future
18+
private static readonly Guid IActionRuntimeIID = Guid.Parse("206EFA2C-C909-508A-B4B0-9482BE96DB9C");
19+
20+
// Public API usage (ActionCatalog)
21+
private const string ActionRuntimeClsidStr = "C36FEF7E-35F3-4192-9F2C-AF1FD425FB85";
22+
23+
internal ActionEntityFactory EntityFactory => ActionRuntime.EntityFactory;
24+
25+
internal ActionRuntime? ActionRuntime;
26+
internal ActionCatalog ActionCatalog => ActionRuntime.ActionCatalog;
27+
28+
private ActionManager()
29+
{
30+
ActionRuntime = CreateActionRuntime();
31+
}
32+
33+
public static unsafe Windows.AI.Actions.ActionRuntime? CreateActionRuntime()
34+
{
35+
IntPtr abiPtr = default;
36+
try
37+
{
38+
Guid classId = Guid.Parse(ActionRuntimeClsidStr);
39+
Guid iid = IActionRuntimeIID;
40+
41+
HRESULT hresult = PInvoke.CoCreateInstance(&classId, null, CLSCTX.CLSCTX_LOCAL_SERVER, &iid, (void**)&abiPtr);
42+
Marshal.ThrowExceptionForHR((int)hresult);
43+
44+
return MarshalInterface<Windows.AI.Actions.ActionRuntime>.FromAbi(abiPtr);
45+
}
46+
catch
47+
{
48+
return null;
49+
}
50+
finally
51+
{
52+
MarshalInspectable<object>.DisposeAbi(abiPtr);
53+
}
54+
}
55+
}
56+
}

src/Files.App/UserControls/NavigationToolbar.xaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@
392392
<FontIcon Foreground="{ThemeResource App.Theme.IconBaseBrush}" Glyph="{x:Bind Glyph}" />
393393
</Viewbox>
394394
<controls:ThemedIcon Style="{x:Bind ThemedIconStyle}" Visibility="{x:Bind ThemedIconStyle, Converter={StaticResource NullToVisibilityCollapsedConverter}}" />
395+
<Image Source="{x:Bind ActionIconSource, Mode=OneWay}" />
395396
</Grid>
396397

397398
<!-- Primary Title -->

src/Files.App/UserControls/NavigationToolbar.xaml.cs

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Microsoft.UI.Xaml.Input;
1111
using Microsoft.UI.Xaml.Media;
1212
using Microsoft.UI.Xaml.Navigation;
13+
using Windows.AI.Actions.Hosting;
1314
using Windows.System;
1415

1516
namespace Files.App.UserControls
@@ -262,20 +263,41 @@ private async void Omnibar_QuerySubmitted(Omnibar sender, OmnibarQuerySubmittedE
262263
}
263264
else if (Omnibar.CurrentSelectedMode == OmnibarCommandPaletteMode)
264265
{
265-
if (args.Item is not NavigationBarSuggestionItem item || item.Text is not { } commandText)
266+
if (args.Item is not NavigationBarSuggestionItem item)
266267
return;
267268

268-
var command = Commands[commandText];
269-
if (command == Commands.None)
270-
await DialogDisplayHelper.ShowDialogAsync(Strings.InvalidCommand.GetLocalizedResource(),
271-
string.Format(Strings.InvalidCommandContent.GetLocalizedResource(), commandText));
272-
else if (!command.IsExecutable)
273-
await DialogDisplayHelper.ShowDialogAsync(Strings.CommandNotExecutable.GetLocalizedResource(),
274-
string.Format(Strings.CommandNotExecutableContent.GetLocalizedResource(), command.Code));
275-
else
276-
await command.ExecuteAsync();
269+
// Try invoking built-in command
270+
if (item.Text is { } commandText)
271+
{
272+
var command = Commands[commandText];
273+
if (command == Commands.None)
274+
await DialogDisplayHelper.ShowDialogAsync(Strings.InvalidCommand.GetLocalizedResource(),
275+
string.Format(Strings.InvalidCommandContent.GetLocalizedResource(), commandText));
276+
else if (!command.IsExecutable)
277+
await DialogDisplayHelper.ShowDialogAsync(Strings.CommandNotExecutable.GetLocalizedResource(),
278+
string.Format(Strings.CommandNotExecutableContent.GetLocalizedResource(), command.Code));
279+
else
280+
await command.ExecuteAsync();
281+
282+
ViewModel.OmnibarCurrentSelectedMode = OmnibarPathMode;
283+
return;
284+
}
277285

278-
ViewModel.OmnibarCurrentSelectedMode = OmnibarPathMode;
286+
// Try invoking Windows app action
287+
if (ActionManager.Instance.ActionRuntime is not null && item.ActionInstance is ActionInstance actionInstance)
288+
{
289+
// Workaround for https://github.com/microsoft/App-Actions-On-Windows-Samples/issues/7
290+
var action = ActionManager.Instance.ActionRuntime.ActionCatalog.GetAllActions()
291+
.FirstOrDefault(a => a.Id == actionInstance.Context.ActionId);
292+
293+
if (action is not null)
294+
{
295+
var overload = action.GetOverloads().FirstOrDefault();
296+
await overload?.InvokeAsync(actionInstance.Context);
297+
}
298+
299+
ViewModel.OmnibarCurrentSelectedMode = OmnibarPathMode;
300+
}
279301
}
280302
else if (Omnibar.CurrentSelectedMode == OmnibarSearchMode)
281303
{

src/Files.App/ViewModels/UserControls/NavigationToolbarViewModel.cs

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@
1111
using Microsoft.UI.Xaml.Input;
1212
using System.IO;
1313
using System.Windows.Input;
14+
using Windows.AI.Actions;
15+
using Windows.AI.Actions.Hosting;
1416
using Windows.ApplicationModel.DataTransfer;
17+
using Microsoft.Windows.ApplicationModel.Resources;
1518
using Windows.UI.Text;
19+
using Microsoft.UI.Xaml.Media.Imaging;
1620

1721
namespace Files.App.ViewModels.UserControls
1822
{
@@ -65,6 +69,8 @@ public sealed partial class NavigationToolbarViewModel : ObservableObject, IAddr
6569

6670
// Properties
6771

72+
internal static ActionRuntime? ActionRuntime { get; private set; }
73+
6874
public ObservableCollection<PathBoxItem> PathComponents { get; } = [];
6975

7076
public ObservableCollection<NavigationBarSuggestionItem> NavigationBarSuggestions { get; } = [];
@@ -248,7 +254,6 @@ public bool IsOmnibarFocused
248254
_ = PopulateOmnibarSuggestionsForPathMode();
249255
break;
250256
case OmnibarPaletteModeName:
251-
if (OmnibarCommandPaletteModeSuggestionItems.Count is 0)
252257
PopulateOmnibarSuggestionsForCommandPaletteMode();
253258
break;
254259
case OmnibarSearchModeName:
@@ -1172,23 +1177,60 @@ public void PopulateOmnibarSuggestionsForCommandPaletteMode()
11721177
OmnibarCommandPaletteModeText ??= string.Empty;
11731178
OmnibarCommandPaletteModeSuggestionItems.Clear();
11741179

1175-
var suggestionItems = Commands.Where(command =>
1176-
command.IsExecutable &&
1177-
command.IsAccessibleGlobally &&
1178-
(command.Description.Contains(OmnibarCommandPaletteModeText, StringComparison.OrdinalIgnoreCase) ||
1179-
command.Code.ToString().Contains(OmnibarCommandPaletteModeText, StringComparison.OrdinalIgnoreCase)))
1180-
.Select(command => new NavigationBarSuggestionItem()
1180+
if (ContentPageContext.SelectedItems.Count == 1 && ContentPageContext.SelectedItem is not null && !ContentPageContext.SelectedItem.IsFolder)
11811181
{
1182-
ThemedIconStyle = command.Glyph.ToThemedIconStyle(),
1183-
Glyph = command.Glyph.BaseGlyph,
1184-
Text = command.Code.ToString(),
1185-
PrimaryDisplay = command.Description,
1186-
HotKeys = command.HotKeys,
1187-
SearchText = OmnibarCommandPaletteModeText,
1188-
});
1182+
var dispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread();
1183+
1184+
dispatcherQueue.TryEnqueue(() =>
1185+
{
1186+
var selectedItemPath = ContentPageContext.SelectedItem.ItemPath;
1187+
var fileActionEntity = ActionManager.Instance.EntityFactory.CreateFileEntity(selectedItemPath);
1188+
var actions = ActionManager.Instance.ActionRuntime.ActionCatalog.GetActionsForInputs(new[] { fileActionEntity });
1189+
1190+
foreach (var action in actions.Where(a => a.Definition.Description.Contains(OmnibarCommandPaletteModeText, StringComparison.OrdinalIgnoreCase)))
1191+
{
1192+
var newItem = new NavigationBarSuggestionItem
1193+
{
1194+
PrimaryDisplay = action.Definition.Description,
1195+
SearchText = OmnibarCommandPaletteModeText,
1196+
ActionInstance = action
1197+
};
1198+
1199+
if (Uri.TryCreate(action.Definition.IconFullPath, UriKind.RelativeOrAbsolute, out Uri? validUri))
1200+
{
1201+
try
1202+
{
1203+
newItem.ActionIconSource = new BitmapImage(validUri);
1204+
}
1205+
catch (Exception)
1206+
{
1207+
}
1208+
}
1209+
1210+
OmnibarCommandPaletteModeSuggestionItems.Add(newItem);
1211+
}
1212+
});
1213+
}
1214+
1215+
var suggestionItems = Commands
1216+
.Where(command => command.IsExecutable
1217+
&& command.IsAccessibleGlobally
1218+
&& (command.Description.Contains(OmnibarCommandPaletteModeText, StringComparison.OrdinalIgnoreCase)
1219+
|| command.Code.ToString().Contains(OmnibarCommandPaletteModeText, StringComparison.OrdinalIgnoreCase)))
1220+
.Select(command => new NavigationBarSuggestionItem
1221+
{
1222+
ThemedIconStyle = command.Glyph.ToThemedIconStyle(),
1223+
Glyph = command.Glyph.BaseGlyph,
1224+
Text = command.Code.ToString(),
1225+
PrimaryDisplay = command.Description,
1226+
HotKeys = command.HotKeys,
1227+
SearchText = OmnibarCommandPaletteModeText,
1228+
});
11891229

11901230
foreach (var item in suggestionItems)
1231+
{
11911232
OmnibarCommandPaletteModeSuggestionItems.Add(item);
1233+
}
11921234
}
11931235

11941236
[Obsolete("Remove once Omnibar goes out of experimental.")]

0 commit comments

Comments
 (0)