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>.

![cmdpal-pr-002](https://github.com/user-attachments/assets/5077ec04-1009-478a-92d6-0a30989d44ac)
![cmdpal-pr-003](https://github.com/user-attachments/assets/63b4762a-9c19-48eb-9242-18ea48240ba0)

----

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:
Mike Griese
2025-03-19 03:39:57 -05:00
committed by GitHub
parent a62acf7a71
commit f68f408be3
984 changed files with 69758 additions and 277 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -0,0 +1,19 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.7095 0.541777C10.543 0.47519 10.3765 0.541777 10.2767 0.708245C9.81054 1.50729 8.94491 2.03998 7.94611 2.03998C6.9473 2.03998 6.08167 1.50729 5.61556 0.674952C5.51568 0.541778 5.34922 0.475191 5.18275 0.508484C4.31712 0.808126 3.51808 1.27423 2.81891 1.87352C2.68574 1.9734 2.65244 2.17316 2.75232 2.33963C3.21843 3.13867 3.28502 4.17077 2.78562 5.0031C2.28622 5.86874 1.38729 6.33484 0.455073 6.33484C0.288606 6.33484 0.122139 6.46802 0.122139 6.63449C-0.0443288 7.53341 -0.0110333 8.46563 0.155434 9.36455C0.188728 9.53102 0.321903 9.66419 0.521664 9.66419C1.45388 9.66419 2.35281 10.1303 2.85221 10.9959C3.35161 11.8616 3.31832 12.8604 2.85221 13.6594C2.75233 13.7926 2.78562 13.9923 2.91879 14.1255C3.25172 14.4252 3.65125 14.6915 4.05078 14.9246C4.4503 15.1576 4.88311 15.3574 5.31592 15.5238C5.48239 15.5904 5.64885 15.5238 5.74873 15.3574C6.21484 14.5583 7.08047 14.0256 8.07928 14.0256C9.07808 14.0256 9.94371 14.5583 10.4098 15.3907C10.5097 15.5238 10.6762 15.5904 10.8426 15.5571C11.7083 15.2575 12.5073 14.7914 13.2065 14.1921C13.3397 14.0922 13.373 13.8925 13.2731 13.7593C12.807 12.9602 12.7404 11.9282 13.2398 11.0958C13.7392 10.2302 14.6381 9.76407 15.5703 9.76407C15.7368 9.76407 15.9032 9.6309 15.9032 9.46443C16.0697 8.56551 16.0364 7.63329 15.87 6.73437C15.8367 6.5679 15.7035 6.43473 15.5037 6.43473C14.5715 6.43473 13.6726 5.96862 13.1732 5.10299C12.6738 4.23736 12.7071 3.23855 13.1732 2.43951C13.2731 2.30633 13.2398 2.10657 13.1066 1.9734C12.7737 1.67376 12.3741 1.40741 11.9746 1.17435C11.5418 0.874713 11.1423 0.708245 10.7095 0.541777Z" fill="url(#paint0_linear_1825_17920)"/>
<path d="M8.0127 12.166C10.2218 12.166 12.0127 10.3752 12.0127 8.16602C12.0127 5.95688 10.2218 4.16602 8.0127 4.16602C5.80356 4.16602 4.0127 5.95688 4.0127 8.16602C4.0127 10.3752 5.80356 12.166 8.0127 12.166Z" fill="url(#paint1_linear_1825_17920)"/>
<path d="M8.01237 10.8333C9.48513 10.8333 10.679 9.63943 10.679 8.16667C10.679 6.69391 9.48513 5.5 8.01237 5.5C6.53961 5.5 5.3457 6.69391 5.3457 8.16667C5.3457 9.63943 6.53961 10.8333 8.01237 10.8333Z" fill="url(#paint2_linear_1825_17920)"/>
<defs>
<linearGradient id="paint0_linear_1825_17920" x1="11.9746" y1="14.9328" x2="3.98418" y2="1.09299" gradientUnits="userSpaceOnUse">
<stop stop-color="#626F7A"/>
<stop offset="1" stop-color="#8B9299"/>
</linearGradient>
<linearGradient id="paint1_linear_1825_17920" x1="10.013" y1="11.6441" x2="6.01236" y2="4.71475" gradientUnits="userSpaceOnUse">
<stop stop-color="white"/>
<stop offset="1" stop-color="#CCCCCC"/>
</linearGradient>
<linearGradient id="paint2_linear_1825_17920" x1="9.3459" y1="10.4899" x2="6.67884" y2="5.8703" gradientUnits="userSpaceOnUse">
<stop stop-color="#114A8B"/>
<stop offset="1" stop-color="#0669BC"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,86 @@
// 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;
namespace Microsoft.CmdPal.Ext.WindowsSettings.Classes;
/// <summary>
/// A windows setting
/// </summary>
internal sealed class WindowsSetting
{
/// <summary>
/// Initializes a new instance of the <see cref="WindowsSetting"/> class.
/// </summary>
public WindowsSetting()
{
Name = string.Empty;
Command = string.Empty;
Type = string.Empty;
ShowAsFirstResult = false;
}
/// <summary>
/// Gets or sets the name of this setting.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the areas of this setting. The order is fixed to the order in json.
/// </summary>
#pragma warning disable CS8632
public IList<string>? Areas { get; set; }
/// <summary>
/// Gets or sets the command of this setting.
/// </summary>
public string Command { get; set; }
/// <summary>
/// Gets or sets the type of the windows setting.
/// </summary>
public string Type { get; set; }
/// <summary>
/// Gets or sets the alternative names of this setting.
/// </summary>
public IEnumerable<string>? AltNames { get; set; }
/// <summary>
/// Gets or sets a additional note of this settings.
/// <para>(e.g. why is not supported on your system)</para>
/// </summary>
public string? Note { get; set; }
/// <summary>
/// Gets or sets the minimum need Windows build for this setting.
/// </summary>
public uint? IntroducedInBuild { get; set; }
/// <summary>
/// Gets or sets the Windows build since this settings is not longer present.
/// </summary>
public uint? DeprecatedInBuild { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to use a higher score as normal for this setting to show it as one of the first results.
/// </summary>
public bool ShowAsFirstResult { get; set; }
/// <summary>
/// Gets or sets the value with the generated area path as string.
/// This Property IS NOT PART OF THE DATA IN "WindowsSettings.json".
/// This property will be filled on runtime by "WindowsSettingsPathHelper".
/// </summary>
public string? JoinedAreaPath { get; set; }
/// <summary>
/// Gets or sets the value with the generated full settings path (App and areas) as string.
/// This Property IS NOT PART OF THE DATA IN "WindowsSettings.json".
/// This property will be filled on runtime by "WindowsSettingsPathHelper".
/// </summary>
public string? JoinedFullSettingsPath { get; set; }
#pragma warning restore CS8632
}

View File

@@ -0,0 +1,27 @@
// 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.Linq;
namespace Microsoft.CmdPal.Ext.WindowsSettings.Classes;
/// <summary>
/// A class that contain all possible windows settings
/// </summary>
internal sealed class WindowsSettings
{
/// <summary>
/// Initializes a new instance of the <see cref="WindowsSettings"/> class with an empty settings list.
/// </summary>
public WindowsSettings()
{
Settings = Enumerable.Empty<WindowsSetting>();
}
/// <summary>
/// Gets or sets a list with all possible windows settings
/// </summary>
public IEnumerable<WindowsSetting> Settings { get; set; }
}

View File

@@ -0,0 +1,40 @@
// 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.Linq;
using System.Resources;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CmdPal.Ext.WindowsSettings.Classes;
using Microsoft.CmdPal.Ext.WindowsSettings.Helpers;
using Microsoft.CmdPal.Ext.WindowsSettings.Properties;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.ApplicationModel.DataTransfer;
using Windows.Networking.NetworkOperators;
using Windows.UI;
namespace Microsoft.CmdPal.Ext.WindowsSettings.Commands;
internal sealed partial class CopySettingCommand : InvokableCommand
{
private readonly WindowsSetting _entry;
internal CopySettingCommand(WindowsSetting entry)
{
Name = Resources.CopyCommand;
Icon = new IconInfo("\xE8C8"); // Copy icon
_entry = entry;
}
public override CommandResult Invoke()
{
ClipboardHelper.SetText(_entry.Command);
return CommandResult.Dismiss();
}
}

View File

@@ -0,0 +1,86 @@
// 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.Linq;
using System.Resources;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CmdPal.Ext.WindowsSettings.Classes;
using Microsoft.CmdPal.Ext.WindowsSettings.Helpers;
using Microsoft.CmdPal.Ext.WindowsSettings.Properties;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.ApplicationModel.DataTransfer;
using Windows.Networking.NetworkOperators;
using Windows.UI;
namespace Microsoft.CmdPal.Ext.WindowsSettings.Commands;
internal sealed partial class OpenSettingsCommand : InvokableCommand
{
private readonly WindowsSetting _entry;
internal OpenSettingsCommand(WindowsSetting entry)
{
Name = Resources.OpenSettings;
Icon = new IconInfo("\xE8C8");
_entry = entry;
}
private static bool DoOpenSettingsAction(WindowsSetting entry)
{
ProcessStartInfo processStartInfo;
var command = entry.Command;
if (command.Contains("%windir%", StringComparison.InvariantCultureIgnoreCase))
{
var windowsFolder = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
command = command.Replace("%windir%", windowsFolder, StringComparison.InvariantCultureIgnoreCase);
}
if (command.Contains(' '))
{
var commandSplit = command.Split(' ');
var file = commandSplit.FirstOrDefault() ?? string.Empty;
var arguments = command[file.Length..].TrimStart();
processStartInfo = new ProcessStartInfo(file, arguments)
{
UseShellExecute = false,
};
}
else
{
processStartInfo = new ProcessStartInfo(command)
{
UseShellExecute = true,
};
}
try
{
Process.Start(processStartInfo);
return true;
}
#pragma warning disable CS0168, IDE0059
catch (Exception exception)
{
// TODO GH #108 Logging is something we have to take care of
// Log.Exception("can't open settings", exception, typeof(ResultHelper));
return false;
}
#pragma warning restore CS0168, IDE0059
}
public override CommandResult Invoke()
{
DoOpenSettingsAction(_entry);
return CommandResult.Dismiss();
}
}

View File

@@ -0,0 +1,31 @@
// 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.Windows;
using System.Windows.Input;
using Microsoft.CmdPal.Ext.WindowsSettings.Classes;
using Microsoft.CmdPal.Ext.WindowsSettings.Commands;
using Microsoft.CmdPal.Ext.WindowsSettings.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.WindowsSettings.Helpers;
/// <summary>
/// Helper class to easier work with context menu entries
/// </summary>
internal static class ContextMenuHelper
{
internal static List<CommandContextItem> GetContextMenu(WindowsSetting entry)
{
var list = new List<CommandContextItem>(1)
{
new(new CopySettingCommand(entry)),
};
return list;
}
}

View File

@@ -0,0 +1,67 @@
// 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.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Microsoft.CmdPal.Ext.WindowsSettings.Helpers;
/// <summary>
/// Helper class to easier work with the JSON file that contains all Windows settings
/// </summary>
internal static class JsonSettingsListHelper
{
/// <summary>
/// The name of the file that contains all settings for the query
/// </summary>
private const string _settingsFile = "WindowsSettings.json";
private static readonly JsonSerializerOptions _serializerOptions = new()
{
};
/// <summary>
/// Read all possible Windows settings.
/// </summary>
/// <returns>A list with all possible windows settings.</returns>
internal static Classes.WindowsSettings ReadAllPossibleSettings()
{
var assembly = Assembly.GetExecutingAssembly();
var type = assembly.GetTypes().FirstOrDefault(x => x.Name == nameof(WindowsSettingsCommandsProvider));
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
Classes.WindowsSettings? settings = null;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
try
{
var resourceName = $"{type?.Namespace}.{_settingsFile}";
using var stream = assembly.GetManifestResourceStream(resourceName);
if (stream is null)
{
throw new ArgumentNullException(nameof(stream), "stream is null");
}
var options = _serializerOptions;
options.Converters.Add(new JsonStringEnumConverter());
using var reader = new StreamReader(stream);
var text = reader.ReadToEnd();
settings = JsonSerializer.Deserialize<Classes.WindowsSettings>(text, options);
}
#pragma warning disable CS0168
catch (Exception exception)
{
// TODO GH #108 Logging is something we have to take care of
// Log.Exception("Error loading settings JSON file", exception, typeof(JsonSettingsListHelper));
}
#pragma warning restore CS0168
return settings ?? new Classes.WindowsSettings();
}
}

View File

@@ -0,0 +1,112 @@
// 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.Globalization;
using System.Linq;
using System.Text;
using Microsoft.CmdPal.Ext.WindowsSettings.Commands;
using Microsoft.CmdPal.Ext.WindowsSettings.Helpers;
using Microsoft.CmdPal.Ext.WindowsSettings.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.WindowsSettings;
/// <summary>
/// Helper class to easier work with List Items
/// </summary>
internal static class ResultHelper
{
internal static List<ListItem> GetResultList(
in IEnumerable<Classes.WindowsSetting> list,
string query)
{
var resultList = new List<ListItem>(list.Count());
foreach (var entry in list)
{
var result = new ListItem(new OpenSettingsCommand(entry))
{
Icon = IconHelpers.FromRelativePath("Assets\\WindowsSettings.svg"),
Subtitle = entry.JoinedFullSettingsPath,
Title = entry.Name,
MoreCommands = ContextMenuHelper.GetContextMenu(entry).ToArray(),
};
// TODO GH #126 investigate tooltips
// AddOptionalToolTip(entry, result);
// There is a case with MMC snap-ins where we don't have .msc files fort them. Then we need to show the note for this results in subtitle too.
// These results have mmc.exe as command and their note property is filled.
if (entry.Command == "mmc.exe" && !string.IsNullOrEmpty(entry.Note))
{
result.Subtitle += $"\u0020\u0020\u002D\u0020\u0020{Resources.Note}: {entry.Note}"; // "\u0020\u0020\u002D\u0020\u0020" = "<space><space><minus><space><space>"
}
// To not show duplicate entries we check the existing results on the list before adding the new entry. Example: Device Manager entry for Control Panel and Device Manager entry for MMC.
if (!resultList.Any(x => x.Title == result.Title))
{
resultList.Add(result);
}
}
// TODO GH #127 --> Investigate scoring
// SetScores(resultList, query);
return resultList;
}
/// <summary>
/// Checks if a setting <see cref="WindowsSetting"/> matches the search string <see cref="Query.Search"/> to filter settings by settings path.
/// This method is called from the <see cref="Predicate{T}"/> method in <see cref="Main.Query(Query)"/> if the search string <see cref="Query.Search"/> contains the character ">".
/// </summary>
/// <param name="found">The WindowsSetting's result that should be checked.</param>
/// <param name="queryString">The searchString entered by the user <see cref="Query.Search"/>s.</param>
internal static bool FilterBySettingsPath(in Classes.WindowsSetting found, in string queryString)
{
if (!queryString.Contains('>'))
{
return false;
}
// Init vars
var queryElements = queryString.Split('>');
List<string> settingsPath = new List<string>();
settingsPath.Add(found.Type);
if (!(found.Areas is null))
{
settingsPath.AddRange(found.Areas);
}
// Compare query and settings path
for (var i = 0; i < queryElements.Length; i++)
{
if (string.IsNullOrWhiteSpace(queryElements[i]))
{
// The queryElement is an WhiteSpace. Nothing to compare.
break;
}
if (i < settingsPath.Count)
{
if (!settingsPath[i].StartsWith(queryElements[i], StringComparison.CurrentCultureIgnoreCase))
{
return false;
}
}
else
{
// The user has entered more query parts than existing elements in settings path.
return false;
}
}
// Return "true" if <found> matches <queryString>.
return true;
}
}

View File

@@ -0,0 +1,117 @@
// 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.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using Microsoft.CmdPal.Ext.WindowsSettings.Properties;
namespace Microsoft.CmdPal.Ext.WindowsSettings.Helpers;
/// <summary>
/// Helper class to easier work with translations.
/// </summary>
internal static class TranslationHelper
{
/// <summary>
/// Translate all settings of the settings list in the given <see cref="WindowsSettings"/> class.
/// </summary>
/// <param name="windowsSettings">A class that contain all possible windows settings.</param>
internal static void TranslateAllSettings(in Classes.WindowsSettings windowsSettings)
{
if (windowsSettings?.Settings is null)
{
return;
}
foreach (var settings in windowsSettings.Settings)
{
// Translate Name
if (!string.IsNullOrWhiteSpace(settings.Name))
{
var name = Resources.ResourceManager.GetString(settings.Name, CultureInfo.CurrentUICulture);
if (string.IsNullOrEmpty(name))
{
// Log.Warn($"Resource string for [{settings.Name}] not found", typeof(TranslationHelper));
}
settings.Name = name ?? settings.Name ?? string.Empty;
}
// Translate Type (App)
if (!string.IsNullOrWhiteSpace(settings.Type))
{
var type = Resources.ResourceManager.GetString(settings.Type, CultureInfo.CurrentUICulture);
if (string.IsNullOrEmpty(type))
{
// Log.Warn($"Resource string for [{settings.Type}] not found", typeof(TranslationHelper));
}
settings.Type = type ?? settings.Type ?? string.Empty;
}
// Translate Areas
if (!(settings.Areas is null) && settings.Areas.Any())
{
var translatedAreas = new List<string>();
foreach (var area in settings.Areas)
{
if (string.IsNullOrWhiteSpace(area))
{
continue;
}
var translatedArea = Resources.ResourceManager.GetString(area, CultureInfo.CurrentUICulture);
if (string.IsNullOrEmpty(translatedArea))
{
// Log.Warn($"Resource string for [{area}] not found", typeof(TranslationHelper));
}
translatedAreas.Add(translatedArea ?? area);
}
settings.Areas = translatedAreas;
}
// Translate Alternative names
if (!(settings.AltNames is null) && settings.AltNames.Any())
{
var translatedAltNames = new Collection<string>();
foreach (var altName in settings.AltNames)
{
if (string.IsNullOrWhiteSpace(altName))
{
continue;
}
var translatedAltName = Resources.ResourceManager.GetString(altName, CultureInfo.CurrentUICulture);
if (string.IsNullOrEmpty(translatedAltName))
{
// Log.Warn($"Resource string for [{altName}] not found", typeof(TranslationHelper));
}
translatedAltNames.Add(translatedAltName ?? altName);
}
settings.AltNames = translatedAltNames;
}
// Translate Note
if (!string.IsNullOrWhiteSpace(settings.Note))
{
var note = Resources.ResourceManager.GetString(settings.Note, CultureInfo.CurrentUICulture);
if (string.IsNullOrEmpty(note))
{
// Log.Warn($"Resource string for [{settings.Note}] not found", typeof(TranslationHelper));
}
settings.Note = note ?? settings.Note ?? string.Empty;
}
}
}
}

View File

@@ -0,0 +1,87 @@
// 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.Linq;
namespace Microsoft.CmdPal.Ext.WindowsSettings.Helpers;
/// <summary>
/// Helper class to easier work with the version of the Windows OS
/// </summary>
internal static class UnsupportedSettingsHelper
{
private const string _keyPath = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
private const string _keyNameBuild = "CurrentBuild";
private const string _keyNameBuildNumber = "CurrentBuildNumber";
/// <summary>
/// Remove all <see cref="WindowsSetting"/> from the settings list in the given <see cref="WindowsSetting"/> class.
/// </summary>
/// <param name="windowsSettings">A class that contain all possible windows settings.</param>
internal static void FilterByBuild(in Classes.WindowsSettings windowsSettings)
{
if (windowsSettings?.Settings is null)
{
return;
}
var currentBuild = GetNumericRegistryValue(_keyPath, _keyNameBuild);
var currentBuildNumber = GetNumericRegistryValue(_keyPath, _keyNameBuildNumber);
if (currentBuild != currentBuildNumber)
{
var usedValueName = currentBuild != uint.MinValue ? _keyNameBuild : _keyNameBuildNumber;
var warningMessage =
$"Detecting the Windows version in registry ({_keyPath}) leads to an inconclusive"
+ $" result ({_keyNameBuild}={currentBuild}, {_keyNameBuildNumber}={currentBuildNumber})!"
+ $" For resolving the conflict we use the value of '{usedValueName}'.";
// TODO GH #108 Logging is something we have to take care of
// Log.Warn(warningMessage, typeof(UnsupportedSettingsHelper));
}
var currentWindowsBuild = currentBuild != uint.MinValue
? currentBuild
: currentBuildNumber;
var filteredSettingsList = windowsSettings.Settings.Where(found
=> (found.DeprecatedInBuild == null || currentWindowsBuild < found.DeprecatedInBuild)
&& (found.IntroducedInBuild == null || currentWindowsBuild >= found.IntroducedInBuild));
filteredSettingsList = filteredSettingsList.OrderBy(found => found.Name);
windowsSettings.Settings = filteredSettingsList;
}
/// <summary>
/// Return a unsigned numeric value from given registry value name inside the given registry key.
/// </summary>
/// <param name="registryKey">The registry key.</param>
/// <param name="valueName">The name of the registry value.</param>
/// <returns>A registry value or <see cref="uint.MinValue"/> on error.</returns>
private static uint GetNumericRegistryValue(in string registryKey, in string valueName)
{
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
object? registryValueData;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
try
{
registryValueData = Win32.Registry.GetValue(registryKey, valueName, uint.MinValue);
}
catch
{
// Log.Exception(
// $"Can't get registry value for '{valueName}'",
// exception,
// typeof(UnsupportedSettingsHelper));
return uint.MinValue;
}
return uint.TryParse(registryValueData as string, out var buildNumber)
? buildNumber
: uint.MinValue;
}
}

View File

@@ -0,0 +1,66 @@
// 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.Linq;
namespace Microsoft.CmdPal.Ext.WindowsSettings.Helpers;
/// <summary>
/// Helper class to help with the path of a <see cref="WindowsSetting"/>. The settings path shows where to find a setting within Windows' user interface.
/// </summary>
internal static class WindowsSettingsPathHelper
{
/// <summary>
/// The symbol which is used as delimiter between the parts of the path.
/// </summary>
private const string _pathDelimiterSequence = "\u0020\u0020\u02C3\u0020\u0020"; // = "<space><space><arrow><space><space>"
/// <summary>
/// Generates the values for <see cref="WindowsSetting.JoinedAreaPath"/> and <see cref="WindowsSetting.JoinedFullSettingsPath"/> on all settings of the list in the given <see cref="WindowsSettings"/> class.
/// </summary>
/// <param name="windowsSettings">A class that contain all possible windows settings.</param>
internal static void GenerateSettingsPathValues(in Classes.WindowsSettings windowsSettings)
{
if (windowsSettings?.Settings is null)
{
return;
}
foreach (var settings in windowsSettings.Settings)
{
// Check if type value is filled. If not, then write log warning.
if (string.IsNullOrEmpty(settings.Type))
{
// TODO GH #108 Logging is something we have to take care of
// Log.Warn($"The type property is not set for setting [{settings.Name}] in json. Skipping generating of settings path.", typeof(WindowsSettingsPathHelper));
continue;
}
// Check if "JoinedAreaPath" and "JoinedFullSettingsPath" are filled. Then log debug message.
if (!string.IsNullOrEmpty(settings.JoinedAreaPath))
{
// Log.Debug($"The property [JoinedAreaPath] of setting [{settings.Name}] was filled from the json. This value is not used and will be overwritten.", typeof(WindowsSettingsPathHelper));
}
if (!string.IsNullOrEmpty(settings.JoinedFullSettingsPath))
{
// TODO GH #108 Logging is something we have to take care of
// Log.Debug($"The property [JoinedFullSettingsPath] of setting [{settings.Name}] was filled from the json. This value is not used and will be overwritten.", typeof(WindowsSettingsPathHelper));
}
// Generating path values.
if (!(settings.Areas is null) && settings.Areas.Any())
{
var areaValue = string.Join(_pathDelimiterSequence, settings.Areas);
settings.JoinedAreaPath = areaValue;
settings.JoinedFullSettingsPath = $"{settings.Type}{_pathDelimiterSequence}{areaValue}";
}
else
{
settings.JoinedAreaPath = string.Empty;
settings.JoinedFullSettingsPath = settings.Type;
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 941 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 861 B

View File

@@ -0,0 +1,46 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<PropertyGroup>
<RootNamespace>Microsoft.CmdPal.Ext.WindowsSettings</RootNamespace>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<!-- MRT from windows app sdk will search for a pri file with the same name of the module before defaulting to resources.pri -->
<ProjectPriFileName>Microsoft.CmdPal.Ext.WindowsSettings.pri</ProjectPriFileName>
</PropertyGroup>
<ItemGroup>
<None Remove="Assets\WindowsSettings.svg" />
<None Remove="WindowsSettings.json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="WindowsSettings.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.ServiceProcess.ServiceController" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Content Update="Assets\WindowsSettings.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="Assets\WindowsSettings.svg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,99 @@
// 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 Microsoft.CmdPal.Ext.WindowsSettings.Classes;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.WindowsSettings;
internal sealed partial class WindowsSettingsListPage : DynamicListPage
{
private readonly Classes.WindowsSettings _windowsSettings;
public WindowsSettingsListPage(Classes.WindowsSettings windowsSettings)
{
Icon = IconHelpers.FromRelativePath("Assets\\WindowsSettings.svg");
Name = "Windows Settings";
Id = "com.microsoft.cmdpal.windowsSettings";
_windowsSettings = windowsSettings;
}
public List<ListItem> Query(string query)
{
if (_windowsSettings?.Settings is null)
{
return new List<ListItem>(0);
}
var filteredList = _windowsSettings.Settings
.Where(Predicate)
.OrderBy(found => found.Name);
var newList = ResultHelper.GetResultList(filteredList, query);
return newList;
bool Predicate(WindowsSetting found)
{
if (string.IsNullOrWhiteSpace(query))
{
// If no search string is entered skip query comparison.
return true;
}
if (found.Name.Contains(query, StringComparison.CurrentCultureIgnoreCase))
{
return true;
}
if (!(found.Areas is null))
{
foreach (var area in found.Areas)
{
// Search for areas on normal queries.
if (area.Contains(query, StringComparison.CurrentCultureIgnoreCase))
{
return true;
}
// Search for Area only on queries with action char.
if (area.Contains(query.Replace(":", string.Empty), StringComparison.CurrentCultureIgnoreCase)
&& query.EndsWith(":", StringComparison.CurrentCultureIgnoreCase))
{
return true;
}
}
}
if (!(found.AltNames is null))
{
foreach (var altName in found.AltNames)
{
if (altName.Contains(query, StringComparison.CurrentCultureIgnoreCase))
{
return true;
}
}
}
// Search by key char '>' for app name and settings path
return query.Contains('>') ? ResultHelper.FilterBySettingsPath(found, query) : false;
}
}
public override void UpdateSearchText(string oldSearch, string newSearch)
{
RaiseItemsChanged(0);
}
public override IListItem[] GetItems()
{
var items = Query(SearchText).ToArray();
return items;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,73 @@
{
"$schema": "http://json-schema.org/draft-04/schema",
"additionalProperties": false,
"required": [ "Settings" ],
"properties": {
"$schema": {
"description": "Path to the schema file.",
"type": "string"
},
"Settings": {
"description": "A list with all possible windows settings.",
"type": "array",
"items": {
"additionalProperties": false,
"required": [ "Name", "Command", "Type" ],
"type": "object",
"properties": {
"Name": {
"description": "The name of this setting.",
"type": "string"
},
"Areas": {
"description": "A list of areas of this setting",
"type": "array",
"items": {
"description": "A area of this setting",
"type": "string",
"pattern": "^Area"
}
},
"Type": {
"description": "The type of this setting.",
"type": "string",
"pattern": "^App"
},
"AltNames": {
"description": "A list with alternative names for this setting",
"type": "array",
"items": {
"description": "A alternative name for this setting",
"type": "string"
}
},
"Command": {
"description": "The command for this setting.",
"type": "string"
},
"Note": {
"description": "A additional note for this setting.",
"type": "string",
"pattern": "^Note"
},
"DeprecatedInBuild": {
"description": "The Windows build since this settings is not longer present.",
"type": "integer",
"minimum": 0,
"maximum": 4294967295
},
"IntroducedInBuild": {
"description": "The minimum need Windows build for this setting.",
"type": "integer",
"minimum": 0,
"maximum": 4294967295
},
"ShowAsFirstResult": {
"description": "Use a higher score as normal for this setting to show it as one of the first results.",
"type": "boolean"
}
}
}
}
}
}

View File

@@ -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.Ext.WindowsSettings.Helpers;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.WindowsSettings;
public partial class WindowsSettingsCommandsProvider : CommandProvider
{
private readonly CommandItem _searchSettingsListItem;
#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
private readonly WindowsSettings.Classes.WindowsSettings? _windowsSettings;
#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public WindowsSettingsCommandsProvider()
{
Id = "Windows.Settings";
DisplayName = $"Windows Settings";
Icon = IconHelpers.FromRelativePath("Assets\\WindowsSettings.svg");
_windowsSettings = JsonSettingsListHelper.ReadAllPossibleSettings();
_searchSettingsListItem = new CommandItem(new WindowsSettingsListPage(_windowsSettings))
{
Title = "Windows Settings",
Subtitle = "Navigate to specific Windows settings",
};
UnsupportedSettingsHelper.FilterByBuild(_windowsSettings);
TranslationHelper.TranslateAllSettings(_windowsSettings);
WindowsSettingsPathHelper.GenerateSettingsPathValues(_windowsSettings);
}
public override ICommandItem[] TopLevelCommands()
{
return [
_searchSettingsListItem
];
}
}