mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-06 03:07:04 +02:00
Add the Command Palette module (#37908)
Windows Command Palette ("CmdPal") is the next iteration of PowerToys Run. With extensibility at its core, the Command Palette is your one-stop launcher to start _anything_.
By default, CmdPal is bound to <kbd>Win+Alt+Space</kbd>.


----
This brings the current preview version of CmdPal into the upstream PowerToys repo. There are still lots of bugs to work out, but it's reached the state we're ready to start sharing it with the world. From here, we can further collaborate with the community on the features that are important, and ensuring that we've got a most robust API to enable developers to build whatever extensions they want.
Most of the built-in PT Run modules have already been ported to CmdPal's extension API. Those include:
* Installed apps
* Shell commands
* File search (powered by the indexer)
* Windows Registry search
* Web search
* Windows Terminal Profiles
* Windows Services
* Windows settings
There are a couple new extensions built-in
* You can now search for packages on `winget` and install them right from the palette. This also powers searching for extensions for the palette
* The calculator has an entirely new implementation. This is currently less feature complete than the original PT Run one - we're looking forward to updating it to be more complete for future ingestion in Windows
* "Bookmarks" allow you to save shortcuts to files, folders, and webpages as top-level commands in the palette.
We've got a bunch of other samples too, in this repo and elsewhere
### PowerToys specific notes
CmdPal will eventually graduate out of PowerToys to live as its own application, which is why it's implemented just a little differently than most other modules. Enabling CmdPal will install its `msix` package.
The CI was minorly changed to support CmdPal version numbers independent of PowerToys itself. It doesn't make sense for us to start CmdPal at v0.90, and in the future, we want to be able to rev CmdPal independently of PT itself.
Closes #3200, closes #3600, closes #7770, closes #34273, closes #36471, closes #20976, closes #14495
-----
TODOs et al
**Blocking:**
- [ ] Images and descriptions in Settings and OOBE need to be properly defined, as mentioned before
- [ ] Niels is on it
- [x] Doesn't start properly from PowerToys unless the fix PR is merged.
- https://github.com/zadjii-msft/PowerToys/pull/556 merged
- [x] I seem to lose focus a lot when I press on some limits, like between the search bar and the results.
- This is https://github.com/zadjii-msft/PowerToys/issues/427
- [x] Turned off an extension like Calculator and it was still working.
- Need to get rid of that toggle, it doesn't do anything currently
- [x] `ListViewModel.<FetchItems>` crash
- Pretty confident that was fixed in https://github.com/zadjii-msft/PowerToys/pull/553
**Not blocking / improvements:**
- Show the shortcut through settings, as mentioned before, or create a button that would open CmdPalette settings.
- When PowerToys starts, CmdPalette is always shown if enabled. That's weird when just starting PowerToys/ logging in to the computer with PowerToys auto-start activated. I think this should at least be a setting.
- Needing to double press a result for it to do the default action seems quirky. If one is already selected, I think just pressing should be enough for it to do the action.
- This is currently a setting, though we're thinking of changing the setting even more: https://github.com/zadjii-msft/PowerToys/issues/392
- There's no URI extension. Was surprised when typing a URL that it only proposed a web search.
- [x] There's no System commands extension. Was expecting to be able to quickly restart the computer by typing restart but it wasn't there.
- This is in PR https://github.com/zadjii-msft/PowerToys/pull/452
---------
Co-authored-by: joadoumie <98557455+joadoumie@users.noreply.github.com>
Co-authored-by: Jordi Adoumie <jordiadoumie@microsoft.com>
Co-authored-by: Mike Griese <zadjii@gmail.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
Co-authored-by: Michael Hawker <24302614+michael-hawker@users.noreply.github.com>
Co-authored-by: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com>
Co-authored-by: Seraphima <zykovas91@gmail.com>
Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
Co-authored-by: Kristen Schau <47155823+krschau@users.noreply.github.com>
Co-authored-by: Eric Johnson <ericjohnson327@gmail.com>
Co-authored-by: Ethan Fang <ethanfang@microsoft.com>
Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Clint Rutkas <clint@rutkas.com>
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.Bookmarks;
|
||||
using Microsoft.UI.Xaml.Documents;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Helpers;
|
||||
|
||||
internal enum GpoRuleConfiguredValue
|
||||
{
|
||||
WrongValue = -3,
|
||||
Unavailable = -2,
|
||||
NotConfigured = -1,
|
||||
Disabled = 0,
|
||||
Enabled = 1,
|
||||
}
|
||||
|
||||
/*
|
||||
* Contains methods extracted from PowerToys gpo.h
|
||||
* The idea is to keep CmdPal codebase take as little dependences on the PowerToys codebase as possible.
|
||||
* Having this class to check GPO being contained in CmdPal means we don't need to depend on GPOWrapper.
|
||||
*/
|
||||
internal static class GpoValueChecker
|
||||
{
|
||||
private const string PoliciesPath = @"SOFTWARE\Policies\PowerToys";
|
||||
private static readonly RegistryKey PoliciesScopeMachine = Registry.LocalMachine;
|
||||
private static readonly RegistryKey PoliciesScopeUser = Registry.CurrentUser;
|
||||
private const string PolicyConfigureEnabledCmdPal = @"ConfigureEnabledUtilityCmdPal";
|
||||
private const string PolicyConfigureEnabledGlobalAllUtilities = @"ConfigureGlobalUtilityEnabledState";
|
||||
|
||||
private static GpoRuleConfiguredValue GetConfiguredValue(string registryValueName)
|
||||
{
|
||||
// For GPO policies, machine scope should take precedence over user scope
|
||||
var value = ReadRegistryValue(PoliciesScopeMachine, PoliciesPath, registryValueName);
|
||||
|
||||
if (!value.HasValue)
|
||||
{
|
||||
// If not found in machine scope, check user scope
|
||||
value = ReadRegistryValue(PoliciesScopeUser, PoliciesPath, registryValueName);
|
||||
if (!value.HasValue)
|
||||
{
|
||||
return GpoRuleConfiguredValue.NotConfigured;
|
||||
}
|
||||
}
|
||||
|
||||
return value switch
|
||||
{
|
||||
0 => GpoRuleConfiguredValue.Disabled,
|
||||
1 => GpoRuleConfiguredValue.Enabled,
|
||||
_ => GpoRuleConfiguredValue.WrongValue,
|
||||
};
|
||||
}
|
||||
|
||||
// Reads an integer registry value if it exists.
|
||||
private static int? ReadRegistryValue(RegistryKey rootKey, string subKeyPath, string valueName)
|
||||
{
|
||||
using (RegistryKey? key = rootKey.OpenSubKey(subKeyPath, false))
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var value = key.GetValue(valueName);
|
||||
if (value is int intValue)
|
||||
{
|
||||
return intValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static GpoRuleConfiguredValue GetUtilityEnabledValue(string utilityName)
|
||||
{
|
||||
var individualValue = GetConfiguredValue(utilityName);
|
||||
|
||||
if (individualValue == GpoRuleConfiguredValue.Disabled || individualValue == GpoRuleConfiguredValue.Enabled)
|
||||
{
|
||||
return individualValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the individual utility value is not set, check the global all utilities policy value.
|
||||
return GetConfiguredValue(PolicyConfigureEnabledGlobalAllUtilities);
|
||||
}
|
||||
}
|
||||
|
||||
internal static GpoRuleConfiguredValue GetConfiguredCmdPalEnabledValue()
|
||||
{
|
||||
return GetUtilityEnabledValue(PolicyConfigureEnabledCmdPal);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// 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 Microsoft.CmdPal.UI.Controls;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Common async event handler provides the cache lookup function for the <see cref="IconBox.SourceRequested"/> deferred event.
|
||||
/// </summary>
|
||||
public static partial class IconCacheProvider
|
||||
{
|
||||
private static readonly IconCacheService IconService = new(Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread());
|
||||
|
||||
#pragma warning disable IDE0060 // Remove unused parameter
|
||||
public static async void SourceRequested(IconBox sender, SourceRequestedEventArgs args)
|
||||
#pragma warning restore IDE0060 // Remove unused parameter
|
||||
{
|
||||
if (args.Key == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Key is IconDataViewModel iconData)
|
||||
{
|
||||
var deferral = args.GetDeferral();
|
||||
|
||||
args.Value = await IconService.GetIconSource(iconData);
|
||||
|
||||
deferral.Complete();
|
||||
}
|
||||
else if (args.Key is IconInfoViewModel iconInfo)
|
||||
{
|
||||
var deferral = args.GetDeferral();
|
||||
|
||||
var data = args.Theme == Microsoft.UI.Xaml.ElementTheme.Dark ? iconInfo.Dark : iconInfo.Light;
|
||||
args.Value = await IconService.GetIconSource(data);
|
||||
|
||||
deferral.Complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// 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;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.Terminal.UI;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Helpers;
|
||||
|
||||
public sealed class IconCacheService(DispatcherQueue dispatcherQueue)
|
||||
{
|
||||
public Task<IconSource?> GetIconSource(IconDataViewModel icon) =>
|
||||
|
||||
// todo: actually implement a cache of some sort
|
||||
IconToSource(icon);
|
||||
|
||||
private async Task<IconSource?> IconToSource(IconDataViewModel icon)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(icon.Icon))
|
||||
{
|
||||
var source = IconPathConverter.IconSourceMUX(icon.Icon, false);
|
||||
return source;
|
||||
}
|
||||
else if (icon.Data != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await StreamToIconSource(icon.Data.Unsafe!);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Debug.WriteLine("Failed to load icon from stream");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<IconSource?> StreamToIconSource(IRandomAccessStreamReference iconStreamRef)
|
||||
{
|
||||
if (iconStreamRef == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var bitmap = await IconStreamToBitmapImageAsync(iconStreamRef);
|
||||
var icon = new ImageIconSource() { ImageSource = bitmap };
|
||||
return icon;
|
||||
}
|
||||
|
||||
private async Task<BitmapImage> IconStreamToBitmapImageAsync(IRandomAccessStreamReference iconStreamRef)
|
||||
{
|
||||
// Return the bitmap image via TaskCompletionSource. Using WCT's EnqueueAsync does not suffice here, since if
|
||||
// we're already on the thread of the DispatcherQueue then it just directly calls the function, with no async involved.
|
||||
var completionSource = new TaskCompletionSource<BitmapImage>();
|
||||
dispatcherQueue.TryEnqueue(async () =>
|
||||
{
|
||||
using var bitmapStream = await iconStreamRef.OpenReadAsync();
|
||||
var itemImage = new BitmapImage();
|
||||
await itemImage.SetSourceAsync(bitmapStream);
|
||||
completionSource.TrySetResult(itemImage);
|
||||
});
|
||||
|
||||
var bitmapImage = await completionSource.Task;
|
||||
|
||||
return bitmapImage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// 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 Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Color = Windows.UI.Color;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Helpers;
|
||||
|
||||
public static partial class OptionalColorBrushCacheProvider
|
||||
{
|
||||
private static readonly Dictionary<OptionalColor, SolidColorBrush> _brushCache = [];
|
||||
|
||||
public static SolidColorBrush? Convert(OptionalColor color)
|
||||
{
|
||||
if (!color.HasValue)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!_brushCache.TryGetValue(color, out var brush))
|
||||
{
|
||||
// Create and cache the brush if we see this color for the first time.
|
||||
brush = new SolidColorBrush(Color.FromArgb(color.Color.A, color.Color.R, color.Color.G, color.Color.B));
|
||||
_brushCache[color] = brush;
|
||||
}
|
||||
|
||||
return brush;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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 Microsoft.Windows.ApplicationModel.Resources;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Helpers;
|
||||
|
||||
internal static class ResourceLoaderInstance
|
||||
{
|
||||
internal static ResourceLoader ResourceLoader { get; private set; }
|
||||
|
||||
static ResourceLoaderInstance()
|
||||
{
|
||||
ResourceLoader = new ResourceLoader("resources.pri");
|
||||
}
|
||||
|
||||
internal static string GetString(string resourceId) => ResourceLoader.GetString(resourceId);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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 CommunityToolkit.Common.Deferred;
|
||||
using Windows.Foundation;
|
||||
|
||||
// Pilfered from CommunityToolkit.WinUI.Deferred
|
||||
namespace Microsoft.CmdPal.UI.Deferred;
|
||||
|
||||
/// <summary>
|
||||
/// Extensions to <see cref="TypedEventHandler{TSender, TResult}"/> for Deferred Events.
|
||||
/// </summary>
|
||||
public static class TypedEventHandlerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Use to invoke an async <see cref="TypedEventHandler{TSender, TResult}"/> using <see cref="DeferredEventArgs"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="S">Type of sender.</typeparam>
|
||||
/// <typeparam name="R"><see cref="EventArgs"/> type.</typeparam>
|
||||
/// <param name="eventHandler"><see cref="TypedEventHandler{TSender, TResult}"/> to be invoked.</param>
|
||||
/// <param name="sender">Sender of the event.</param>
|
||||
/// <param name="eventArgs"><see cref="EventArgs"/> instance.</param>
|
||||
/// <returns><see cref="Task"/> to wait on deferred event handler.</returns>
|
||||
#pragma warning disable CA1715 // Identifiers should have correct prefix
|
||||
#pragma warning disable SA1314 // Type parameter names should begin with T
|
||||
public static Task InvokeAsync<S, R>(this TypedEventHandler<S, R> eventHandler, S sender, R eventArgs)
|
||||
#pragma warning restore SA1314 // Type parameter names should begin with T
|
||||
#pragma warning restore CA1715 // Identifiers should have correct prefix
|
||||
where R : DeferredEventArgs => InvokeAsync(eventHandler, sender, eventArgs, CancellationToken.None);
|
||||
|
||||
/// <summary>
|
||||
/// Use to invoke an async <see cref="TypedEventHandler{TSender, TResult}"/> using <see cref="DeferredEventArgs"/> with a <see cref="CancellationToken"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="S">Type of sender.</typeparam>
|
||||
/// <typeparam name="R"><see cref="EventArgs"/> type.</typeparam>
|
||||
/// <param name="eventHandler"><see cref="TypedEventHandler{TSender, TResult}"/> to be invoked.</param>
|
||||
/// <param name="sender">Sender of the event.</param>
|
||||
/// <param name="eventArgs"><see cref="EventArgs"/> instance.</param>
|
||||
/// <param name="cancellationToken"><see cref="CancellationToken"/> option.</param>
|
||||
/// <returns><see cref="Task"/> to wait on deferred event handler.</returns>
|
||||
#pragma warning disable CA1715 // Identifiers should have correct prefix
|
||||
#pragma warning disable SA1314 // Type parameter names should begin with T
|
||||
public static Task InvokeAsync<S, R>(this TypedEventHandler<S, R> eventHandler, S sender, R eventArgs, CancellationToken cancellationToken)
|
||||
#pragma warning restore SA1314 // Type parameter names should begin with T
|
||||
#pragma warning restore CA1715 // Identifiers should have correct prefix
|
||||
where R : DeferredEventArgs
|
||||
{
|
||||
if (eventHandler == null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var tasks = eventHandler.GetInvocationList()
|
||||
.OfType<TypedEventHandler<S, R>>()
|
||||
.Select(invocationDelegate =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
invocationDelegate(sender, eventArgs);
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var deferral = eventArgs.GetCurrentDeferralAndReset();
|
||||
|
||||
return deferral?.WaitForCompletion(cancellationToken) ?? Task.CompletedTask;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
return Task.WhenAll(tasks);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user