minor fix

This commit is contained in:
vanzue
2025-12-12 14:18:15 +08:00
parent a75eb482bf
commit e0ccac7550
14 changed files with 224 additions and 408 deletions

View File

@@ -25,7 +25,7 @@
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<PropertyGroup>
<PropertyGroup Condition="'$(CIBuild)' != 'true'">
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
</PropertyGroup>

View File

@@ -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
<Applications>
<Application Id="CmdPalExt.YourExtension"
Executable="External\Win32\<yourExe>.exe"
EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements DisplayName="Your CmdPal Extension" Square44x44Logo="Assets\Square44x44Logo.png" Description="..." />
</Application>
<!-- Existing PowerToys entries remain -->
</Applications>
```
* Embed sparse identity in your Win32 binary (RT_MANIFEST):
```xml
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<msix xmlns="urn:schemas-microsoft-com:msix.v1"
packageName="Microsoft.PowerToys.SparseApp"
applicationId="CmdPalExt.YourExtension"
publisher="CN=PowerToys Dev Cert, O=Microsoft Corporation, L=..., C=US"/>
</assembly>
```
4.2 Register the App Extension & COM server
In your packaged projects Package.appxmanifest (follow SamplePagesExtension pattern):
```xml
<Extensions>
<!-- COM host out-of-proc for the extension -->
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:ExeServer Executable="External\Win32\<yourExe>.exe"
Arguments="-RegisterProcessAsComServer"
DisplayName="CmdPal Extension COM Host">
<com:Class Id="{YOUR-CLS-GUID}" DisplayName="YourExtension" />
</com:ExeServer>
</com:ComServer>
</com:Extension>
<!-- Command Palette AppExtension -->
<uap3:Extension Category="windows.appExtension">
<uap3:AppExtension Name="com.microsoft.commandpalette"
Id="YourExtension"
PublicFolder="Public">
<uap3:Properties>
<CmdPalProvider xmlns="http://schemas.microsoft.com/commandpalette/2024/extension">
<Metadata DisplayName="Your Extension" Description="..." />
<Activation>
<CreateInstance ClassId="{YOUR-CLS-GUID}" />
</Activation>
</CmdPalProvider>
</uap3:Properties>
</uap3:AppExtension>
</uap3:Extension>
</Extensions>
```
4.3 Project configuration
```xml
<PropertyGroup>
<GenerateAppxPackageOnBuild>true</GenerateAppxPackageOnBuild>
<AppxBundle>Never</AppxBundle>
<ApplicationManifest>..\..\..\src\PackageIdentity\AppxManifest.xml</ApplicationManifest>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPalExtensions\$(MSBuildProjectName)\</OutDir>
</PropertyGroup>
```
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<ICommandProvider> 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<SampleExtension>();
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<CommandItem> 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<Workspace> 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<Workspace> 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<CommandItem> 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.

View File

@@ -13,15 +13,15 @@ namespace PowerToysExtension.Commands;
internal sealed partial class StartAwakeCommand : InvokableCommand
{
private readonly Func<Task<OperationResult>> _action;
private readonly string? _successToast;
private readonly string _successToast;
internal StartAwakeCommand(string title, Func<Task<OperationResult>> action, string? successToast = null)
internal StartAwakeCommand(string title, Func<Task<OperationResult>> action, string successToast = "")
{
ArgumentNullException.ThrowIfNull(action);
ArgumentException.ThrowIfNullOrWhiteSpace(title);
_action = action;
_successToast = successToast;
_successToast = successToast ?? string.Empty;
Name = title;
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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<IListItem>();
@@ -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<IListItem>();
@@ -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<Task<OperationResult>> Action, string? Toast);
private sealed record AwakeCommandDefinition(string Title, string Subtitle, Func<Task<OperationResult>> Action, string Toast);
}

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 Microsoft.Win32;
namespace PowerToysExtension.Helpers;
internal enum GpoRuleConfiguredValue
{
WrongValue = -3,
Unavailable = -2,
NotConfigured = -1,
Disabled = 0,
Enabled = 1,
}
/// <summary>
/// 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.
/// </summary>
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;
}
}
}

View File

@@ -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,
};
}

View File

@@ -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;
}
}

View File

@@ -18,39 +18,22 @@ internal sealed class AwakeModuleCommandProvider : ModuleCommandProvider
{
var items = new List<ListItem>();
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"))
{

View File

@@ -21,49 +21,33 @@ internal sealed class ColorPickerModuleCommandProvider : ModuleCommandProvider
var commands = new List<ListItem>();
if (ModuleEnablementService.IsModuleEnabled(module))
commands.Add(new ListItem(new OpenInSettingsCommand(module, title))
{
// Quick actions under MoreCommands.
var more = new List<CommandContextItem>
{
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;
}
}

View File

@@ -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",