mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-15 03:07:56 +01:00
minor fix
This commit is contained in:
@@ -25,7 +25,7 @@
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<PropertyGroup Condition="'$(CIBuild)' != 'true'">
|
||||
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -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 project’s 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.
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user