From e0ccac75502bd750ff88d123d1ae45c13e931780 Mon Sep 17 00:00:00 2001 From: vanzue Date: Fri, 12 Dec 2025 14:18:15 +0800 Subject: [PATCH] minor fix --- .../Microsoft.CmdPal.UI.csproj | 2 +- .../doc/powertoys-commandpalette-extension.md | 258 ------------------ .../Commands/Awake/StartAwakeCommand.cs | 6 +- .../Commands/ColorPicker/CopyColorCommand.cs | 6 +- .../Commands/LaunchModuleCommand.cs | 20 +- .../Commands/NoOpCommand.cs | 20 -- .../Commands/OpenInSettingsCommand.cs | 2 +- .../Helpers/AwakeCommandsFactory.cs | 14 +- .../Helpers/GpoEnablementService.cs | 86 ++++++ .../Helpers/ModuleEnablementService.cs | 60 +++- .../Helpers/PowerToysPathResolver.cs | 40 +-- .../Modules/AwakeModuleCommandProvider.cs | 33 +-- .../ColorPickerModuleCommandProvider.cs | 60 ++-- .../WorkspacesModuleCommandProvider.cs | 25 +- 14 files changed, 224 insertions(+), 408 deletions(-) delete mode 100644 src/modules/cmdpal/doc/powertoys-commandpalette-extension.md delete mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/NoOpCommand.cs create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/GpoEnablementService.cs diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj b/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj index fcdb169f94..b0179b6a51 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Microsoft.CmdPal.UI.csproj @@ -25,7 +25,7 @@ false - + false diff --git a/src/modules/cmdpal/doc/powertoys-commandpalette-extension.md b/src/modules/cmdpal/doc/powertoys-commandpalette-extension.md deleted file mode 100644 index b896e8a7ac..0000000000 --- a/src/modules/cmdpal/doc/powertoys-commandpalette-extension.md +++ /dev/null @@ -1,258 +0,0 @@ -Build a packaged Command Palette extension that exposes com.microsoft.commandpalette, while re-using the existing PowerToys sparse MSIX identity for discovery. -The extension directly references and consumes PowerToys module libraries (C#) to execute commands, removing the need for inter-process communication (IPC) with the PowerToys Runner for these interactions. - -0) Goals / Non-Goals - -Goals - -Let Command Palette (host) discover a PowerToys extension via the Windows AppExtension contract. - -Provide direct, low-latency execution of PowerToys commands by hosting module logic directly within the Extension process. - -Define a pattern for exposing PowerToys modules as consumable C# libraries. - -Non-Goals - -Not a general inter-process broker. - -Not relying on the PowerToys Runner process to be active for command execution (where possible). - -1) Terms - -Host: Command Palette process (WinUI3 app) loading extensions. - -Extension: sparse packaged project that declares uap3:AppExtension Name="com.microsoft.commandpalette". - -Module Library: A .NET assembly (DLL) implementing the core logic of a PowerToys module, exposing a public C# API. - -Command: A unit of invocation (e.g., workspace.list, awake.enable) mapped directly to a C# method call. - -2) High-Level Architecture - -1. Command Palette gathers user intent → Invokes Command Provider in Extension. - -2. Extension (running in CmdPal process) calls into the referenced Module Library. - - ```csharp - // Direct C# call - var workspaces = _workspacesService.GetWorkspaces(); - ``` - -3. Module Library executes logic (e.g., reads config, spawns process, applies setting). - -4. Result is returned directly to the Extension and rendered by Command Palette. - -3) Prerequisites - -PowerToys sparse package installed. - -Module Libraries refactored/available as .NET assemblies (e.g., `PowerToys.Workspaces.Lib.dll`). - -4) Packaging & Identity -4.1 Add your executable to the sparse package - -* Edit src/PackageIdentity/AppxManifest.xml: -```xml - - - - - - -``` - -* Embed sparse identity in your Win32 binary (RT_MANIFEST): -```xml - - - -``` - -4.2 Register the App Extension & COM server -In your packaged project’s Package.appxmanifest (follow SamplePagesExtension pattern): -```xml - - - - - - - - - - - - - - - - - - - - - - - - -``` - -4.3 Project configuration -```xml - - true - Never - ..\..\..\src\PackageIdentity\AppxManifest.xml - $(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPalExtensions\$(MSBuildProjectName)\ - -``` - -5) Extension Entry Point (COM) & Lifetime -5.1 COM entry -```csharp -[ComVisible(true)] -[Guid("YOUR-CLS-GUID")] -public sealed class SampleExtension : IExtension -{ - private readonly ManualResetEvent _lifetime = new(false); - - public void Initialize(IHostContext ctx) - { - // optional host features / events subscription - } - - public IEnumerable GetProviders() => - new ICommandProvider[] { new PowerToysCommandProvider() }; - - public void Dispose() - { - _lifetime.Set(); // signal process shutdown - } - - // Program.cs - public static int Main(string[] args) - { - if (args.Contains("-RegisterProcessAsComServer")) - { - using var server = ExtensionServer.RegisterExtension(); - WaitHandle.WaitAny(new[] { server.LifetimeHandle /* or your own */ }); - return 0; - } - return 0; - } -} -``` -6) Providers, Pages, and Items -Derive from Microsoft.CommandPalette.Extensions.Toolkit.CommandProvider. - -Provide a consistent capabilities set: -```csharp -public sealed class PowerToysCommandProvider : CommandProvider -{ - public PowerToysCommandProvider() - { - Id = "PowerToys"; - DisplayName = "PowerToys"; - Icon = new Uri("ms-appx:///Public/Icons/powertoys.png"); - } - - public override IEnumerable TopLevelCommands() - { - yield return CommandItem.Run("Workspaces", "List or launch a workspace") - .WithId("workspace.list") - .WithInvoke(async ctx => { - // Direct call to module library - var workspaces = new WorkspacesService(); - var list = workspaces.GetWorkspaces(); - // ... - }); - // …more commands - } -} -``` -Use toolkit pages (ListPage, MarkdownContent, FormContent, etc.) to render results. - -7) Module Library Pattern -To expose a module to Command Palette, the module must provide a .NET-consumable library (C# Project or C++/CLI wrapper). - -7.1 Library Responsibilities -* **Statelessness:** The library should ideally be stateless or manage state via persistent storage (files, registry) that can be shared between the Runner and the Extension. -* **Public API:** Expose high-level methods for commands (e.g., `Launch()`, `Enable()`, `GetState()`). -* **Dependencies:** Keep dependencies minimal to avoid bloating the Extension package. - -7.2 Interface Definition (Recommended) -Define an interface for your module's capabilities to allow for easy testing and mocking. - -```csharp -public interface IWorkspacesService -{ - IEnumerable GetWorkspaces(); - void LaunchWorkspace(string workspaceId); - void LaunchEditor(); -} -``` - -8) Example: Workspaces Module -The Workspaces module exposes a C# library `PowerToys.Workspaces.Lib` that implements the logic. - -8.1 Implementation (in `src/modules/Workspaces/WorkspacesLib`) -```csharp -public class WorkspacesService : IWorkspacesService -{ - public IEnumerable GetWorkspaces() - { - // Read from settings/storage - return WorkspaceStorage.Load(); - } - - public void LaunchWorkspace(string workspaceId) - { - // Logic to launch apps defined in the workspace - var workspace = WorkspaceStorage.Get(workspaceId); - Launcher.Launch(workspace); - } - - public void LaunchEditor() - { - // Launch the editor executable - Process.Start("PowerToys.Workspaces.Editor.exe"); - } -} -``` - -8.2 Consumption (in `Microsoft.CmdPal.Ext.PowerToys`) -The extension project references `PowerToys.Workspaces.Lib.csproj`. - -```csharp -public sealed class PowerToysCommandProvider : CommandProvider -{ - private readonly IWorkspacesService _workspaces = new WorkspacesService(); - - public override IEnumerable TopLevelCommands() - { - yield return CommandItem.Run("Workspaces", "List or launch a workspace") - .WithId("workspace.list") - .WithInvoke(async ctx => { - var list = _workspaces.GetWorkspaces(); - // Render list page... - }); - } -} -``` - -9) Security & Isolation -* **Process Identity:** The Extension runs as the user in the Command Palette process (or a separate extension process). It inherits the user's permissions. -* **Elevation:** If a module requires elevation (e.g., modifying system files), the Library must handle the UAC prompt or fail gracefully. The Extension cannot "request" elevation from the Runner via this direct path. -* **Concurrency:** Be aware that `PowerToys.exe` (Runner) and `CmdPal.exe` (Extension) may access shared resources (settings files) simultaneously. Use appropriate file locking or synchronization. - -10) Telemetry -* The Extension should log telemetry events directly using the standard PowerToys telemetry pipeline, ensuring the `Caller` is identified as the Command Palette Extension. diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/Awake/StartAwakeCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/Awake/StartAwakeCommand.cs index cfbb445eda..b46b0301e7 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/Awake/StartAwakeCommand.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/Awake/StartAwakeCommand.cs @@ -13,15 +13,15 @@ namespace PowerToysExtension.Commands; internal sealed partial class StartAwakeCommand : InvokableCommand { private readonly Func> _action; - private readonly string? _successToast; + private readonly string _successToast; - internal StartAwakeCommand(string title, Func> action, string? successToast = null) + internal StartAwakeCommand(string title, Func> action, string successToast = "") { ArgumentNullException.ThrowIfNull(action); ArgumentException.ThrowIfNullOrWhiteSpace(title); _action = action; - _successToast = successToast; + _successToast = successToast ?? string.Empty; Name = title; } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/ColorPicker/CopyColorCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/ColorPicker/CopyColorCommand.cs index 525132c7d9..37ec5818b5 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/ColorPicker/CopyColorCommand.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/ColorPicker/CopyColorCommand.cs @@ -38,13 +38,13 @@ internal sealed partial class CopyColorCommand : InvokableCommand } } - private static string? TryGetLastColor() + private static string TryGetLastColor() { var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); var historyPath = Path.Combine(localAppData, "Microsoft", "PowerToys", "ColorPicker", "colorHistory.json"); if (!File.Exists(historyPath)) { - return null; + return string.Empty; } var lines = File.ReadAllLines(historyPath); @@ -64,6 +64,6 @@ internal sealed partial class CopyColorCommand : InvokableCommand } } - return null; + return string.Empty; } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/LaunchModuleCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/LaunchModuleCommand.cs index 0249a624d1..3101a73030 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/LaunchModuleCommand.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/LaunchModuleCommand.cs @@ -16,16 +16,16 @@ namespace PowerToysExtension.Commands; internal sealed partial class LaunchModuleCommand : InvokableCommand { private readonly string _moduleName; - private readonly string? _eventName; - private readonly string? _executableName; - private readonly string? _arguments; + private readonly string _eventName; + private readonly string _executableName; + private readonly string _arguments; internal LaunchModuleCommand( string moduleName, - string? eventName = null, - string? executableName = null, - string? arguments = null, - string? displayName = null) + string eventName = "", + string executableName = "", + string arguments = "", + string displayName = "") { if (string.IsNullOrWhiteSpace(moduleName)) { @@ -33,9 +33,9 @@ internal sealed partial class LaunchModuleCommand : InvokableCommand } _moduleName = moduleName; - _eventName = eventName; - _executableName = executableName; - _arguments = arguments; + _eventName = eventName ?? string.Empty; + _executableName = executableName ?? string.Empty; + _arguments = arguments ?? string.Empty; Name = string.IsNullOrWhiteSpace(displayName) ? $"Launch {moduleName}" : displayName; } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/NoOpCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/NoOpCommand.cs deleted file mode 100644 index c8f20b2e6c..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/NoOpCommand.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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.Toolkit; - -namespace PowerToysExtension.Commands; - -internal sealed partial class NoOpCommand : InvokableCommand -{ - internal NoOpCommand(string title = "No operation") - { - Name = title; - } - - public override CommandResult Invoke() - { - return CommandResult.Hide(); - } -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/OpenInSettingsCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/OpenInSettingsCommand.cs index 5afbdac920..65860c249a 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/OpenInSettingsCommand.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/OpenInSettingsCommand.cs @@ -14,7 +14,7 @@ internal sealed partial class OpenInSettingsCommand : InvokableCommand { private readonly SettingsDeepLink.SettingsWindow _module; - internal OpenInSettingsCommand(SettingsDeepLink.SettingsWindow module, string? title = null) + internal OpenInSettingsCommand(SettingsDeepLink.SettingsWindow module, string title = "") { _module = module; Name = string.IsNullOrWhiteSpace(title) ? $"Open {_module} settings" : title; diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/AwakeCommandsFactory.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/AwakeCommandsFactory.cs index 1322ac6a10..2f6a04300e 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/AwakeCommandsFactory.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/AwakeCommandsFactory.cs @@ -67,7 +67,7 @@ internal static class AwakeCommandsFactory moreCommands.Add(stopContext); } - internal static IListItem[] GetSessionItems(string? searchText) + internal static IListItem[] GetSessionItems(string searchText) { var results = new List(); @@ -170,7 +170,7 @@ internal static class AwakeCommandsFactory return results.ToArray(); } - internal static IListItem[] GetProcessItems(string? searchText) + internal static IListItem[] GetProcessItems(string searchText) { var results = new List(); @@ -195,7 +195,7 @@ internal static class AwakeCommandsFactory } var title = $"{name} ({process.Id})"; - if (!Matches(title, null, searchText)) + if (!Matches(title, string.Empty, searchText)) { continue; } @@ -226,7 +226,7 @@ internal static class AwakeCommandsFactory return results.ToArray(); } - internal static bool Matches(string? source, string? subtitle, string? searchText) + internal static bool Matches(string source, string subtitle, string searchText) { if (string.IsNullOrWhiteSpace(searchText)) { @@ -236,9 +236,9 @@ internal static class AwakeCommandsFactory return Contains(source, searchText) || Contains(subtitle, searchText); } - private static bool Contains(string? source, string? searchText) + private static bool Contains(string source, string searchText) { - return !string.IsNullOrEmpty(source) && source.Contains(searchText!, StringComparison.CurrentCultureIgnoreCase); + return !string.IsNullOrEmpty(source) && source.Contains(searchText, StringComparison.CurrentCultureIgnoreCase); } private static string FormatDuration(TimeSpan span) @@ -289,5 +289,5 @@ internal static class AwakeCommandsFactory } } - private sealed record AwakeCommandDefinition(string Title, string Subtitle, Func> Action, string? Toast); + private sealed record AwakeCommandDefinition(string Title, string Subtitle, Func> Action, string Toast); } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/GpoEnablementService.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/GpoEnablementService.cs new file mode 100644 index 0000000000..244870ebe5 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/GpoEnablementService.cs @@ -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 Microsoft.Win32; + +namespace PowerToysExtension.Helpers; + +internal enum GpoRuleConfiguredValue +{ + WrongValue = -3, + Unavailable = -2, + NotConfigured = -1, + Disabled = 0, + Enabled = 1, +} + +/// +/// Lightweight GPO reader for module/feature enablement policies. +/// Mirrors the logic in src/common/utils/gpo.h but avoids taking a dependency on the full GPOWrapper. +/// +internal static class GpoEnablementService +{ + private const string PoliciesPath = @"SOFTWARE\Policies\PowerToys"; + private const string PolicyConfigureEnabledGlobalAllUtilities = "ConfigureGlobalUtilityEnabledState"; + + internal static GpoRuleConfiguredValue GetUtilityEnabledValue(string individualPolicyValueName) + { + if (!string.IsNullOrEmpty(individualPolicyValueName)) + { + var individual = GetConfiguredValue(individualPolicyValueName); + if (individual is GpoRuleConfiguredValue.Disabled or GpoRuleConfiguredValue.Enabled) + { + return individual; + } + } + + return GetConfiguredValue(PolicyConfigureEnabledGlobalAllUtilities); + } + + private static GpoRuleConfiguredValue GetConfiguredValue(string registryValueName) + { + try + { + // Machine scope has priority over user scope. + var value = ReadRegistryValue(Registry.LocalMachine, registryValueName); + value ??= ReadRegistryValue(Registry.CurrentUser, registryValueName); + + if (!value.HasValue) + { + return GpoRuleConfiguredValue.NotConfigured; + } + + return value.Value switch + { + 0 => GpoRuleConfiguredValue.Disabled, + 1 => GpoRuleConfiguredValue.Enabled, + _ => GpoRuleConfiguredValue.WrongValue, + }; + } + catch + { + return GpoRuleConfiguredValue.Unavailable; + } + } + + private static int? ReadRegistryValue(RegistryKey rootKey, string valueName) + { + try + { + using var key = rootKey.OpenSubKey(PoliciesPath, writable: false); + if (key is null) + { + return null; + } + + var value = key.GetValue(valueName); + return value as int?; + } + catch + { + return null; + } + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/ModuleEnablementService.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/ModuleEnablementService.cs index f76d75668c..fccf5b8687 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/ModuleEnablementService.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/ModuleEnablementService.cs @@ -24,7 +24,13 @@ internal static class ModuleEnablementService internal static bool IsModuleEnabled(SettingsWindow module) { var key = GetEnabledKey(module); - return string.IsNullOrEmpty(key) || IsKeyEnabled(key); + if (string.IsNullOrEmpty(key)) + { + var globalRule = GpoEnablementService.GetUtilityEnabledValue(string.Empty); + return globalRule != GpoRuleConfiguredValue.Disabled; + } + + return IsKeyEnabled(key); } internal static bool IsKeyEnabled(string enabledKey) @@ -34,6 +40,18 @@ internal static class ModuleEnablementService return true; } + var gpoPolicy = GetGpoPolicyForEnabledKey(enabledKey); + var gpoRule = GpoEnablementService.GetUtilityEnabledValue(gpoPolicy); + if (gpoRule == GpoRuleConfiguredValue.Disabled) + { + return false; + } + + if (gpoRule == GpoRuleConfiguredValue.Enabled) + { + return true; + } + try { var enabled = ReadEnabledFlags(); @@ -73,7 +91,7 @@ internal static class ModuleEnablementService return result; } - private static string? GetEnabledKey(SettingsWindow module) => module switch + private static string GetEnabledKey(SettingsWindow module) => module switch { SettingsWindow.Awake => "Awake", SettingsWindow.AdvancedPaste => "AdvancedPaste", @@ -103,6 +121,42 @@ internal static class ModuleEnablementService SettingsWindow.ZoomIt => "ZoomIt", SettingsWindow.CmdNotFound => "CmdNotFound", SettingsWindow.CmdPal => "CmdPal", - _ => null, + _ => string.Empty, + }; + + private static string GetGpoPolicyForEnabledKey(string enabledKey) => enabledKey switch + { + "AdvancedPaste" => "ConfigureEnabledUtilityAdvancedPaste", + "AlwaysOnTop" => "ConfigureEnabledUtilityAlwaysOnTop", + "Awake" => "ConfigureEnabledUtilityAwake", + "CmdNotFound" => "ConfigureEnabledUtilityCmdNotFound", + "CmdPal" => "ConfigureEnabledUtilityCmdPal", + "ColorPicker" => "ConfigureEnabledUtilityColorPicker", + "CropAndLock" => "ConfigureEnabledUtilityCropAndLock", + "CursorWrap" => "ConfigureEnabledUtilityCursorWrap", + "EnvironmentVariables" => "ConfigureEnabledUtilityEnvironmentVariables", + "FancyZones" => "ConfigureEnabledUtilityFancyZones", + "FileLocksmith" => "ConfigureEnabledUtilityFileLocksmith", + "FindMyMouse" => "ConfigureEnabledUtilityFindMyMouse", + "Hosts" => "ConfigureEnabledUtilityHostsFileEditor", + "Image Resizer" => "ConfigureEnabledUtilityImageResizer", + "Keyboard Manager" => "ConfigureEnabledUtilityKeyboardManager", + "LightSwitch" => "ConfigureEnabledUtilityLightSwitch", + "Measure Tool" => "ConfigureEnabledUtilityScreenRuler", + "MouseHighlighter" => "ConfigureEnabledUtilityMouseHighlighter", + "MouseJump" => "ConfigureEnabledUtilityMouseJump", + "MousePointerCrosshairs" => "ConfigureEnabledUtilityMousePointerCrosshairs", + "MouseWithoutBorders" => "ConfigureEnabledUtilityMouseWithoutBorders", + "NewPlus" => "ConfigureEnabledUtilityNewPlus", + "Peek" => "ConfigureEnabledUtilityPeek", + "PowerRename" => "ConfigureEnabledUtilityPowerRename", + "PowerToys Run" => "ConfigureEnabledUtilityPowerLauncher", + "QuickAccent" => "ConfigureEnabledUtilityQuickAccent", + "RegistryPreview" => "ConfigureEnabledUtilityRegistryPreview", + "Shortcut Guide" => "ConfigureEnabledUtilityShortcutGuide", + "TextExtractor" => "ConfigureEnabledUtilityTextExtractor", + "Workspaces" => "ConfigureEnabledUtilityWorkspaces", + "ZoomIt" => "ConfigureEnabledUtilityZoomIt", + _ => string.Empty, }; } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/PowerToysPathResolver.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/PowerToysPathResolver.cs index 72405b71f9..d9ac9443fe 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/PowerToysPathResolver.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/PowerToysPathResolver.cs @@ -16,7 +16,7 @@ internal static class PowerToysPathResolver private const string PowerToysProtocolKey = @"Software\Classes\powertoys"; private const string PowerToysUserKey = @"Software\Microsoft\PowerToys"; - internal static string? GetPowerToysInstallPath() + internal static string GetPowerToysInstallPath() { var perUser = GetInstallPathFromRegistry(RegistryHive.CurrentUser); if (!string.IsNullOrEmpty(perUser)) @@ -27,24 +27,24 @@ internal static class PowerToysPathResolver return GetInstallPathFromRegistry(RegistryHive.LocalMachine); } - internal static string? TryResolveExecutable(string executableName) + internal static string TryResolveExecutable(string executableName) { if (string.IsNullOrEmpty(executableName)) { - return null; + return string.Empty; } var baseDirectory = GetPowerToysInstallPath(); if (string.IsNullOrEmpty(baseDirectory)) { - return null; + return string.Empty; } var candidate = Path.Combine(baseDirectory, executableName); - return File.Exists(candidate) ? candidate : null; + return File.Exists(candidate) ? candidate : string.Empty; } - private static string? GetInstallPathFromRegistry(RegistryHive hive) + private static string GetInstallPathFromRegistry(RegistryHive hive) { try { @@ -70,41 +70,41 @@ internal static class PowerToysPathResolver // Ignore registry access failures and fall back to other checks. } - return null; + return string.Empty; } - private static string? GetPathFromProtocolRegistration(RegistryKey baseKey) + private static string GetPathFromProtocolRegistration(RegistryKey baseKey) { try { using var commandKey = baseKey.OpenSubKey($@"{PowerToysProtocolKey}\shell\open\command"); if (commandKey == null) { - return null; + return string.Empty; } - var command = commandKey.GetValue(string.Empty)?.ToString(); + var command = commandKey.GetValue(string.Empty)?.ToString() ?? string.Empty; if (string.IsNullOrEmpty(command)) { - return null; + return string.Empty; } return ExtractInstallDirectory(command); } catch { - return null; + return string.Empty; } } - private static string? GetPathFromUserRegistration(RegistryKey baseKey) + private static string GetPathFromUserRegistration(RegistryKey baseKey) { try { using var userKey = baseKey.OpenSubKey(PowerToysUserKey); if (userKey == null) { - return null; + return string.Empty; } var installedValue = userKey.GetValue("installed"); @@ -118,14 +118,14 @@ internal static class PowerToysPathResolver // Ignore registry access failures. } - return null; + return string.Empty; } - private static string? ExtractInstallDirectory(string command) + private static string ExtractInstallDirectory(string command) { if (string.IsNullOrEmpty(command)) { - return null; + return string.Empty; } try @@ -138,7 +138,7 @@ internal static class PowerToysPathResolver var quotedPath = command.Substring(1, closingQuote - 1); if (File.Exists(quotedPath)) { - return Path.GetDirectoryName(quotedPath); + return Path.GetDirectoryName(quotedPath) ?? string.Empty; } } } @@ -147,7 +147,7 @@ internal static class PowerToysPathResolver var parts = command.Split(' '); if (parts.Length > 0 && File.Exists(parts[0])) { - return Path.GetDirectoryName(parts[0]); + return Path.GetDirectoryName(parts[0]) ?? string.Empty; } } } @@ -156,6 +156,6 @@ internal static class PowerToysPathResolver // Fall through and report no path. } - return null; + return string.Empty; } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AwakeModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AwakeModuleCommandProvider.cs index 7f62d24aad..744e01b9e3 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AwakeModuleCommandProvider.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AwakeModuleCommandProvider.cs @@ -18,39 +18,22 @@ internal sealed class AwakeModuleCommandProvider : ModuleCommandProvider { var items = new List(); var module = SettingsDeepLink.SettingsWindow.Awake; - var settingsTitle = module.ModuleDisplayName(); + var title = module.ModuleDisplayName(); var icon = PowerToysResourcesHelper.IconFromSettingsIcon("Awake.png"); var moduleIcon = module.ModuleIcon(); - if (!ModuleEnablementService.IsModuleEnabled(module)) + items.Add(new ListItem(new OpenInSettingsCommand(module, title)) { - items.Add(new ListItem(new OpenInSettingsCommand(module, settingsTitle)) - { - Title = settingsTitle, - Subtitle = "Open Awake settings", - Icon = moduleIcon, - }); - - return items; - } - - // Settings entry with quick actions in MoreCommands. - items.Add(new ListItem(new OpenInSettingsCommand(module, settingsTitle)) - { - Title = settingsTitle, + Title = title, Subtitle = "Open Awake settings", Icon = moduleIcon, - MoreCommands = - [ - new CommandContextItem(new StartAwakeCommand("Awake: Keep awake indefinitely", () => AwakeService.Instance.SetIndefiniteAsync(), "Awake set to indefinite")), - new CommandContextItem(new StartAwakeCommand("Awake: Keep awake for 30 minutes", () => AwakeService.Instance.SetTimedAsync(30), "Awake set for 30 minutes")), - new CommandContextItem(new StartAwakeCommand("Awake: Keep awake for 1 hour", () => AwakeService.Instance.SetTimedAsync(60), "Awake set for 1 hour")), - new CommandContextItem(new StartAwakeCommand("Awake: Keep awake for 2 hours", () => AwakeService.Instance.SetTimedAsync(120), "Awake set for 2 hours")), - new CommandContextItem(new StopAwakeCommand()), - new CommandContextItem(new AwakeProcessListPage()), - ], }); + if (!ModuleEnablementService.IsModuleEnabled(module)) + { + return items; + } + // Direct commands surfaced in the PowerToys list page. items.Add(new ListItem(new StartAwakeCommand("Awake: Keep awake indefinitely", () => AwakeService.Instance.SetIndefiniteAsync(), "Awake set to indefinite")) { diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ColorPickerModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ColorPickerModuleCommandProvider.cs index 296b14e0a5..27b3be6f05 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ColorPickerModuleCommandProvider.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ColorPickerModuleCommandProvider.cs @@ -21,49 +21,33 @@ internal sealed class ColorPickerModuleCommandProvider : ModuleCommandProvider var commands = new List(); - if (ModuleEnablementService.IsModuleEnabled(module)) + commands.Add(new ListItem(new OpenInSettingsCommand(module, title)) { - // Quick actions under MoreCommands. - var more = new List - { - new CommandContextItem(new OpenColorPickerCommand()), - new CommandContextItem(new CopyColorCommand()), - new CommandContextItem(new ColorPickerSavedColorsPage()), - }; + Title = title, + Subtitle = "Open Color Picker settings", + Icon = icon, + }); - commands.Add(new ListItem(new OpenInSettingsCommand(module, title)) - { - Title = title, - Subtitle = "Open Color Picker settings", - Icon = icon, - MoreCommands = more.ToArray(), - }); - - // Direct entries in the module list. - commands.Add(new ListItem(new OpenColorPickerCommand()) - { - Title = "Open Color Picker", - Subtitle = "Start a color pick session", - Icon = icon, - }); - - commands.Add(new ListItem(new CommandItem(new ColorPickerSavedColorsPage())) - { - Title = "Saved colors", - Subtitle = "Browse and copy saved colors", - Icon = icon, - }); - } - else + if (!ModuleEnablementService.IsModuleEnabled(module)) { - commands.Add(new ListItem(new OpenInSettingsCommand(module, title)) - { - Title = title, - Subtitle = "Open Color Picker settings", - Icon = icon, - }); + return commands; } + // Direct entries in the module list. + commands.Add(new ListItem(new OpenColorPickerCommand()) + { + Title = "Open Color Picker", + Subtitle = "Start a color pick session", + Icon = icon, + }); + + commands.Add(new ListItem(new CommandItem(new ColorPickerSavedColorsPage())) + { + Title = "Saved colors", + Subtitle = "Browse and copy saved colors", + Icon = icon, + }); + return commands; } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/WorkspacesModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/WorkspacesModuleCommandProvider.cs index 9d63ce7f0c..bfbf1a853a 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/WorkspacesModuleCommandProvider.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/WorkspacesModuleCommandProvider.cs @@ -10,7 +10,6 @@ using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions.Toolkit; using PowerToysExtension.Commands; using PowerToysExtension.Helpers; -using PowerToysExtension.Pages; using Workspaces.ModuleServices; using WorkspacesCsharpLibrary.Data; @@ -26,31 +25,19 @@ internal sealed class WorkspacesModuleCommandProvider : ModuleCommandProvider var icon = PowerToysResourcesHelper.IconFromSettingsIcon("Workspaces.png"); var moduleIcon = module.ModuleIcon(); - if (!ModuleEnablementService.IsModuleEnabled(module)) - { - items.Add(new ListItem(new OpenInSettingsCommand(module, title)) - { - Title = title, - Subtitle = "Open Workspaces settings", - Icon = moduleIcon, - }); - - return items; - } - - // Settings entry plus common actions. items.Add(new ListItem(new OpenInSettingsCommand(module, title)) { Title = title, Subtitle = "Open Workspaces settings", Icon = moduleIcon, - MoreCommands = - [ - new CommandContextItem(new WorkspacesListPage()), - new CommandContextItem(new OpenWorkspaceEditorCommand()), - ], }); + if (!ModuleEnablementService.IsModuleEnabled(module)) + { + return items; + } + + // Settings entry plus common actions. items.Add(new ListItem(new OpenWorkspaceEditorCommand()) { Title = "Workspaces: Open editor",