mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 18:57:19 +02:00
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>
462 lines
17 KiB
C#
462 lines
17 KiB
C#
// 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.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text.Json;
|
|
using System.Threading.Tasks;
|
|
|
|
using ManagedCommon;
|
|
using Microsoft.PowerToys.Settings.UI.Helpers;
|
|
using Microsoft.PowerToys.Settings.UI.Library;
|
|
using Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events;
|
|
using Microsoft.PowerToys.Settings.UI.SerializationContext;
|
|
using Microsoft.PowerToys.Settings.UI.Services;
|
|
using Microsoft.PowerToys.Settings.UI.Views;
|
|
using Microsoft.PowerToys.Telemetry;
|
|
using Microsoft.UI.Xaml;
|
|
using PowerToys.Interop;
|
|
using Windows.UI.Popups;
|
|
using WinRT.Interop;
|
|
using WinUIEx;
|
|
|
|
namespace Microsoft.PowerToys.Settings.UI
|
|
{
|
|
/// <summary>
|
|
/// Provides application-specific behavior to supplement the default Application class.
|
|
/// </summary>
|
|
public partial class App : Application
|
|
{
|
|
private enum Arguments
|
|
{
|
|
PTPipeName = 1,
|
|
SettingsPipeName,
|
|
PTPid,
|
|
Theme, // used in the old settings
|
|
ElevatedStatus,
|
|
IsUserAdmin,
|
|
ShowOobeWindow,
|
|
ShowScoobeWindow,
|
|
ShowFlyout,
|
|
ContainsSettingsWindow,
|
|
ContainsFlyoutPosition,
|
|
}
|
|
|
|
private const int RequiredArgumentsSetSettingQty = 4;
|
|
private const int RequiredArgumentsSetAdditionalSettingsQty = 4;
|
|
private const int RequiredArgumentsGetSettingQty = 3;
|
|
|
|
private const int RequiredArgumentsLaunchedFromRunnerQty = 12;
|
|
|
|
// Create an instance of the IPC wrapper.
|
|
private static TwoWayPipeMessageIPCManaged ipcmanager;
|
|
|
|
public static bool IsElevated { get; set; }
|
|
|
|
public static bool IsUserAnAdmin { get; set; }
|
|
|
|
public static int PowerToysPID { get; set; }
|
|
|
|
public bool ShowOobe { get; set; }
|
|
|
|
public bool ShowFlyout { get; set; }
|
|
|
|
public bool ShowScoobe { get; set; }
|
|
|
|
public Type StartupPage { get; set; } = typeof(Views.DashboardPage);
|
|
|
|
public static Action<string> IPCMessageReceivedCallback { get; set; }
|
|
|
|
public ETWTrace EtwTrace { get; private set; } = new ETWTrace();
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="App"/> class.
|
|
/// Initializes the singleton application object. This is the first line of authored code
|
|
/// executed, and as such is the logical equivalent of main() or WinMain().
|
|
/// </summary>
|
|
public App()
|
|
{
|
|
Logger.InitializeLogger(@"\Settings\Logs");
|
|
|
|
string appLanguage = LanguageHelper.LoadLanguage();
|
|
if (!string.IsNullOrEmpty(appLanguage))
|
|
{
|
|
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage;
|
|
}
|
|
|
|
InitializeComponent();
|
|
|
|
UnhandledException += App_UnhandledException;
|
|
|
|
NativeEventWaiter.WaitForEventLoop(
|
|
Constants.PowerToysRunnerTerminateSettingsEvent(), () =>
|
|
{
|
|
EtwTrace?.Dispose();
|
|
Environment.Exit(0);
|
|
});
|
|
}
|
|
|
|
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
|
{
|
|
Logger.LogError("Unhandled exception", e.Exception);
|
|
}
|
|
|
|
public static void OpenSettingsWindow(Type type = null, bool ensurePageIsSelected = false)
|
|
{
|
|
if (settingsWindow == null)
|
|
{
|
|
settingsWindow = new MainWindow();
|
|
}
|
|
|
|
settingsWindow.Activate();
|
|
|
|
if (type != null)
|
|
{
|
|
settingsWindow.NavigateToSection(type);
|
|
|
|
WindowHelpers.BringToForeground(settingsWindow.GetWindowHandle());
|
|
}
|
|
|
|
if (ensurePageIsSelected)
|
|
{
|
|
settingsWindow.EnsurePageIsSelected();
|
|
}
|
|
}
|
|
|
|
private void OnLaunchedToSetSetting(string[] cmdArgs)
|
|
{
|
|
var settingName = cmdArgs[2];
|
|
var settingValue = cmdArgs[3];
|
|
try
|
|
{
|
|
SetSettingCommandLineCommand.Execute(settingName, settingValue, new SettingsUtils());
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError($"SetSettingCommandLineCommand exception: '{settingName}' setting couldn't be set to {settingValue}", ex);
|
|
}
|
|
|
|
Exit();
|
|
}
|
|
|
|
private void OnLaunchedToSetAdditionalSetting(string[] cmdArgs)
|
|
{
|
|
var moduleName = cmdArgs[2];
|
|
var ipcFileName = cmdArgs[3];
|
|
try
|
|
{
|
|
using (var settings = JsonDocument.Parse(File.ReadAllText(ipcFileName)))
|
|
{
|
|
SetAdditionalSettingsCommandLineCommand.Execute(moduleName, settings, new SettingsUtils());
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError($"SetAdditionalSettingsCommandLineCommand exception: couldn't set additional settings for '{moduleName}'", ex);
|
|
}
|
|
|
|
Exit();
|
|
}
|
|
|
|
private void OnLaunchedToGetSetting(string[] cmdArgs)
|
|
{
|
|
var ipcFileName = cmdArgs[2];
|
|
|
|
try
|
|
{
|
|
var requestedSettings = JsonSerializer.Deserialize<Dictionary<string, List<string>>>(File.ReadAllText(ipcFileName), SourceGenerationContextContext.Default.DictionaryStringListString);
|
|
File.WriteAllText(ipcFileName, GetSettingCommandLineCommand.Execute(requestedSettings));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError($"GetSettingCommandLineCommand exception", ex);
|
|
}
|
|
|
|
Exit();
|
|
}
|
|
|
|
private void OnLaunchedFromRunner(string[] cmdArgs)
|
|
{
|
|
// Skip the first argument which is prepended when launched by explorer
|
|
if (cmdArgs[0].EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase) && cmdArgs[1].EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase) && (cmdArgs.Length >= RequiredArgumentsLaunchedFromRunnerQty + 1))
|
|
{
|
|
cmdArgs = cmdArgs.Skip(1).ToArray();
|
|
}
|
|
|
|
_ = int.TryParse(cmdArgs[(int)Arguments.PTPid], out int powerToysPID);
|
|
PowerToysPID = powerToysPID;
|
|
|
|
IsElevated = cmdArgs[(int)Arguments.ElevatedStatus] == "true";
|
|
IsUserAnAdmin = cmdArgs[(int)Arguments.IsUserAdmin] == "true";
|
|
ShowOobe = cmdArgs[(int)Arguments.ShowOobeWindow] == "true";
|
|
ShowScoobe = cmdArgs[(int)Arguments.ShowScoobeWindow] == "true";
|
|
ShowFlyout = cmdArgs[(int)Arguments.ShowFlyout] == "true";
|
|
bool containsSettingsWindow = cmdArgs[(int)Arguments.ContainsSettingsWindow] == "true";
|
|
bool containsFlyoutPosition = cmdArgs[(int)Arguments.ContainsFlyoutPosition] == "true";
|
|
|
|
// To keep track of variable arguments
|
|
int currentArgumentIndex = RequiredArgumentsLaunchedFromRunnerQty;
|
|
|
|
if (containsSettingsWindow)
|
|
{
|
|
// Open specific window
|
|
StartupPage = GetPage(cmdArgs[currentArgumentIndex]);
|
|
|
|
currentArgumentIndex++;
|
|
}
|
|
|
|
int flyout_x = 0;
|
|
int flyout_y = 0;
|
|
if (containsFlyoutPosition)
|
|
{
|
|
// get the flyout position arguments
|
|
_ = int.TryParse(cmdArgs[currentArgumentIndex++], out flyout_x);
|
|
_ = int.TryParse(cmdArgs[currentArgumentIndex++], out flyout_y);
|
|
}
|
|
|
|
RunnerHelper.WaitForPowerToysRunner(PowerToysPID, () =>
|
|
{
|
|
Environment.Exit(0);
|
|
});
|
|
|
|
ipcmanager = new TwoWayPipeMessageIPCManaged(cmdArgs[(int)Arguments.SettingsPipeName], cmdArgs[(int)Arguments.PTPipeName], (string message) =>
|
|
{
|
|
if (IPCMessageReceivedCallback != null && message.Length > 0)
|
|
{
|
|
IPCMessageReceivedCallback(message);
|
|
}
|
|
});
|
|
ipcmanager.Start();
|
|
|
|
if (!ShowOobe && !ShowScoobe && !ShowFlyout)
|
|
{
|
|
settingsWindow = new MainWindow();
|
|
settingsWindow.Activate();
|
|
settingsWindow.ExtendsContentIntoTitleBar = true;
|
|
settingsWindow.NavigateToSection(StartupPage);
|
|
|
|
// https://github.com/microsoft/microsoft-ui-xaml/issues/7595 - Activate doesn't bring window to the foreground
|
|
// Need to call SetForegroundWindow to actually gain focus.
|
|
WindowHelpers.BringToForeground(settingsWindow.GetWindowHandle());
|
|
|
|
// https://github.com/microsoft/microsoft-ui-xaml/issues/8948 - A window's top border incorrectly
|
|
// renders as black on Windows 10.
|
|
WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(settingsWindow));
|
|
}
|
|
else
|
|
{
|
|
// Create the Settings window hidden so that it's fully initialized and
|
|
// it will be ready to receive the notification if the user opens
|
|
// the Settings from the tray icon.
|
|
settingsWindow = new MainWindow(true);
|
|
|
|
if (ShowOobe)
|
|
{
|
|
PowerToysTelemetry.Log.WriteEvent(new OobeStartedEvent());
|
|
OobeWindow oobeWindow = new OobeWindow(OOBE.Enums.PowerToysModules.Overview);
|
|
oobeWindow.Activate();
|
|
oobeWindow.ExtendsContentIntoTitleBar = true;
|
|
WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(settingsWindow));
|
|
SetOobeWindow(oobeWindow);
|
|
}
|
|
else if (ShowScoobe)
|
|
{
|
|
PowerToysTelemetry.Log.WriteEvent(new ScoobeStartedEvent());
|
|
OobeWindow scoobeWindow = new OobeWindow(OOBE.Enums.PowerToysModules.WhatsNew);
|
|
scoobeWindow.Activate();
|
|
scoobeWindow.ExtendsContentIntoTitleBar = true;
|
|
WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(settingsWindow));
|
|
SetOobeWindow(scoobeWindow);
|
|
}
|
|
else if (ShowFlyout)
|
|
{
|
|
POINT? p = null;
|
|
if (containsFlyoutPosition)
|
|
{
|
|
p = new POINT(flyout_x, flyout_y);
|
|
}
|
|
|
|
ShellPage.OpenFlyoutCallback(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invoked when the application is launched normally by the end user. Other entry points
|
|
/// will be used such as when the application is launched to open a specific file.
|
|
/// </summary>
|
|
/// <param name="args">Details about the launch request and process.</param>
|
|
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
|
|
{
|
|
var cmdArgs = Environment.GetCommandLineArgs();
|
|
|
|
if (cmdArgs?.Length >= RequiredArgumentsLaunchedFromRunnerQty)
|
|
{
|
|
OnLaunchedFromRunner(cmdArgs);
|
|
}
|
|
else if (cmdArgs?.Length == RequiredArgumentsSetSettingQty && cmdArgs[1] == "set")
|
|
{
|
|
OnLaunchedToSetSetting(cmdArgs);
|
|
}
|
|
else if (cmdArgs?.Length == RequiredArgumentsSetAdditionalSettingsQty && cmdArgs[1] == "setAdditional")
|
|
{
|
|
OnLaunchedToSetAdditionalSetting(cmdArgs);
|
|
}
|
|
else if (cmdArgs?.Length == RequiredArgumentsGetSettingQty && cmdArgs[1] == "get")
|
|
{
|
|
OnLaunchedToGetSetting(cmdArgs);
|
|
}
|
|
else
|
|
{
|
|
#if DEBUG
|
|
// For debugging purposes
|
|
// Window is also needed to show MessageDialog
|
|
settingsWindow = new MainWindow();
|
|
settingsWindow.ExtendsContentIntoTitleBar = true;
|
|
WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(settingsWindow));
|
|
settingsWindow.Activate();
|
|
settingsWindow.NavigateToSection(StartupPage);
|
|
ShowMessageDialog("The application is running in Debug mode.", "DEBUG");
|
|
#else
|
|
/* If we try to run Settings as a standalone app, it will start PowerToys.exe if not running and open Settings again through it in the Dashboard page. */
|
|
Common.UI.SettingsDeepLink.OpenSettings(Common.UI.SettingsDeepLink.SettingsWindow.Dashboard, true);
|
|
Exit();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if !DEBUG
|
|
private async void ShowMessageDialogAndExit(string content, string title = null)
|
|
#else
|
|
private async void ShowMessageDialog(string content, string title = null)
|
|
#endif
|
|
{
|
|
await ShowDialogAsync(content, title);
|
|
#if !DEBUG
|
|
this.Exit();
|
|
#endif
|
|
}
|
|
|
|
public static Task<IUICommand> ShowDialogAsync(string content, string title = null)
|
|
{
|
|
var dialog = new MessageDialog(content, title ?? string.Empty);
|
|
var handle = NativeMethods.GetActiveWindow();
|
|
if (handle == IntPtr.Zero)
|
|
{
|
|
throw new InvalidOperationException();
|
|
}
|
|
|
|
InitializeWithWindow.Initialize(dialog, handle);
|
|
return dialog.ShowAsync().AsTask<IUICommand>();
|
|
}
|
|
|
|
public static TwoWayPipeMessageIPCManaged GetTwoWayIPCManager()
|
|
{
|
|
return ipcmanager;
|
|
}
|
|
|
|
public static bool IsDarkTheme()
|
|
{
|
|
return ThemeService.Theme == ElementTheme.Dark || (ThemeService.Theme == ElementTheme.Default && ThemeHelpers.GetAppTheme() == AppTheme.Dark);
|
|
}
|
|
|
|
public static int UpdateUIThemeMethod(string themeName)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
private static ISettingsUtils settingsUtils = new SettingsUtils();
|
|
private static ThemeService themeService = new ThemeService(SettingsRepository<GeneralSettings>.GetInstance(settingsUtils));
|
|
|
|
public static ThemeService ThemeService => themeService;
|
|
|
|
private static MainWindow settingsWindow;
|
|
private static OobeWindow oobeWindow;
|
|
private static FlyoutWindow flyoutWindow;
|
|
|
|
public static void ClearSettingsWindow()
|
|
{
|
|
settingsWindow = null;
|
|
}
|
|
|
|
public static MainWindow GetSettingsWindow()
|
|
{
|
|
return settingsWindow;
|
|
}
|
|
|
|
public static OobeWindow GetOobeWindow()
|
|
{
|
|
return oobeWindow;
|
|
}
|
|
|
|
public static FlyoutWindow GetFlyoutWindow()
|
|
{
|
|
return flyoutWindow;
|
|
}
|
|
|
|
public static void SetOobeWindow(OobeWindow window)
|
|
{
|
|
oobeWindow = window;
|
|
}
|
|
|
|
public static void SetFlyoutWindow(FlyoutWindow window)
|
|
{
|
|
flyoutWindow = window;
|
|
}
|
|
|
|
public static void ClearOobeWindow()
|
|
{
|
|
oobeWindow = null;
|
|
}
|
|
|
|
public static void ClearFlyoutWindow()
|
|
{
|
|
flyoutWindow = null;
|
|
}
|
|
|
|
public static Type GetPage(string settingWindow)
|
|
{
|
|
switch (settingWindow)
|
|
{
|
|
case "Dashboard": return typeof(DashboardPage);
|
|
case "Overview": return typeof(GeneralPage);
|
|
case "AdvancedPaste": return typeof(AdvancedPastePage);
|
|
case "AlwaysOnTop": return typeof(AlwaysOnTopPage);
|
|
case "Awake": return typeof(AwakePage);
|
|
case "CmdNotFound": return typeof(CmdNotFoundPage);
|
|
case "ColorPicker": return typeof(ColorPickerPage);
|
|
case "FancyZones": return typeof(FancyZonesPage);
|
|
case "FileLocksmith": return typeof(FileLocksmithPage);
|
|
case "Run": return typeof(PowerLauncherPage);
|
|
case "ImageResizer": return typeof(ImageResizerPage);
|
|
case "KBM": return typeof(KeyboardManagerPage);
|
|
case "MouseUtils": return typeof(MouseUtilsPage);
|
|
case "MouseWithoutBorders": return typeof(MouseWithoutBordersPage);
|
|
case "PowerRename": return typeof(PowerRenamePage);
|
|
case "QuickAccent": return typeof(PowerAccentPage);
|
|
case "FileExplorer": return typeof(PowerPreviewPage);
|
|
case "ShortcutGuide": return typeof(ShortcutGuidePage);
|
|
case "PowerOcr": return typeof(PowerOcrPage);
|
|
case "MeasureTool": return typeof(MeasureToolPage);
|
|
case "Hosts": return typeof(HostsPage);
|
|
case "RegistryPreview": return typeof(RegistryPreviewPage);
|
|
case "Peek": return typeof(PeekPage);
|
|
case "CropAndLock": return typeof(CropAndLockPage);
|
|
case "EnvironmentVariables": return typeof(EnvironmentVariablesPage);
|
|
case "NewPlus": return typeof(NewPlusPage);
|
|
case "Workspaces": return typeof(WorkspacesPage);
|
|
case "CmdPal": return typeof(CmdPalPage);
|
|
case "ZoomIt": return typeof(ZoomItPage);
|
|
default:
|
|
// Fallback to Dashboard
|
|
Debug.Assert(false, "Unexpected SettingsWindow argument value");
|
|
return typeof(DashboardPage);
|
|
}
|
|
}
|
|
}
|
|
}
|