mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
minor fix
This commit is contained in:
@@ -25,7 +25,7 @@
|
|||||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup Condition="'$(CIBuild)' != 'true'">
|
||||||
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
||||||
</PropertyGroup>
|
</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
|
internal sealed partial class StartAwakeCommand : InvokableCommand
|
||||||
{
|
{
|
||||||
private readonly Func<Task<OperationResult>> _action;
|
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);
|
ArgumentNullException.ThrowIfNull(action);
|
||||||
ArgumentException.ThrowIfNullOrWhiteSpace(title);
|
ArgumentException.ThrowIfNullOrWhiteSpace(title);
|
||||||
|
|
||||||
_action = action;
|
_action = action;
|
||||||
_successToast = successToast;
|
_successToast = successToast ?? string.Empty;
|
||||||
Name = title;
|
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 localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||||
var historyPath = Path.Combine(localAppData, "Microsoft", "PowerToys", "ColorPicker", "colorHistory.json");
|
var historyPath = Path.Combine(localAppData, "Microsoft", "PowerToys", "ColorPicker", "colorHistory.json");
|
||||||
if (!File.Exists(historyPath))
|
if (!File.Exists(historyPath))
|
||||||
{
|
{
|
||||||
return null;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var lines = File.ReadAllLines(historyPath);
|
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
|
internal sealed partial class LaunchModuleCommand : InvokableCommand
|
||||||
{
|
{
|
||||||
private readonly string _moduleName;
|
private readonly string _moduleName;
|
||||||
private readonly string? _eventName;
|
private readonly string _eventName;
|
||||||
private readonly string? _executableName;
|
private readonly string _executableName;
|
||||||
private readonly string? _arguments;
|
private readonly string _arguments;
|
||||||
|
|
||||||
internal LaunchModuleCommand(
|
internal LaunchModuleCommand(
|
||||||
string moduleName,
|
string moduleName,
|
||||||
string? eventName = null,
|
string eventName = "",
|
||||||
string? executableName = null,
|
string executableName = "",
|
||||||
string? arguments = null,
|
string arguments = "",
|
||||||
string? displayName = null)
|
string displayName = "")
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(moduleName))
|
if (string.IsNullOrWhiteSpace(moduleName))
|
||||||
{
|
{
|
||||||
@@ -33,9 +33,9 @@ internal sealed partial class LaunchModuleCommand : InvokableCommand
|
|||||||
}
|
}
|
||||||
|
|
||||||
_moduleName = moduleName;
|
_moduleName = moduleName;
|
||||||
_eventName = eventName;
|
_eventName = eventName ?? string.Empty;
|
||||||
_executableName = executableName;
|
_executableName = executableName ?? string.Empty;
|
||||||
_arguments = arguments;
|
_arguments = arguments ?? string.Empty;
|
||||||
Name = string.IsNullOrWhiteSpace(displayName) ? $"Launch {moduleName}" : displayName;
|
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;
|
private readonly SettingsDeepLink.SettingsWindow _module;
|
||||||
|
|
||||||
internal OpenInSettingsCommand(SettingsDeepLink.SettingsWindow module, string? title = null)
|
internal OpenInSettingsCommand(SettingsDeepLink.SettingsWindow module, string title = "")
|
||||||
{
|
{
|
||||||
_module = module;
|
_module = module;
|
||||||
Name = string.IsNullOrWhiteSpace(title) ? $"Open {_module} settings" : title;
|
Name = string.IsNullOrWhiteSpace(title) ? $"Open {_module} settings" : title;
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ internal static class AwakeCommandsFactory
|
|||||||
moreCommands.Add(stopContext);
|
moreCommands.Add(stopContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static IListItem[] GetSessionItems(string? searchText)
|
internal static IListItem[] GetSessionItems(string searchText)
|
||||||
{
|
{
|
||||||
var results = new List<IListItem>();
|
var results = new List<IListItem>();
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ internal static class AwakeCommandsFactory
|
|||||||
return results.ToArray();
|
return results.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static IListItem[] GetProcessItems(string? searchText)
|
internal static IListItem[] GetProcessItems(string searchText)
|
||||||
{
|
{
|
||||||
var results = new List<IListItem>();
|
var results = new List<IListItem>();
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ internal static class AwakeCommandsFactory
|
|||||||
}
|
}
|
||||||
|
|
||||||
var title = $"{name} ({process.Id})";
|
var title = $"{name} ({process.Id})";
|
||||||
if (!Matches(title, null, searchText))
|
if (!Matches(title, string.Empty, searchText))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -226,7 +226,7 @@ internal static class AwakeCommandsFactory
|
|||||||
return results.ToArray();
|
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))
|
if (string.IsNullOrWhiteSpace(searchText))
|
||||||
{
|
{
|
||||||
@@ -236,9 +236,9 @@ internal static class AwakeCommandsFactory
|
|||||||
return Contains(source, searchText) || Contains(subtitle, searchText);
|
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)
|
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)
|
internal static bool IsModuleEnabled(SettingsWindow module)
|
||||||
{
|
{
|
||||||
var key = GetEnabledKey(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)
|
internal static bool IsKeyEnabled(string enabledKey)
|
||||||
@@ -34,6 +40,18 @@ internal static class ModuleEnablementService
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var gpoPolicy = GetGpoPolicyForEnabledKey(enabledKey);
|
||||||
|
var gpoRule = GpoEnablementService.GetUtilityEnabledValue(gpoPolicy);
|
||||||
|
if (gpoRule == GpoRuleConfiguredValue.Disabled)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gpoRule == GpoRuleConfiguredValue.Enabled)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var enabled = ReadEnabledFlags();
|
var enabled = ReadEnabledFlags();
|
||||||
@@ -73,7 +91,7 @@ internal static class ModuleEnablementService
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string? GetEnabledKey(SettingsWindow module) => module switch
|
private static string GetEnabledKey(SettingsWindow module) => module switch
|
||||||
{
|
{
|
||||||
SettingsWindow.Awake => "Awake",
|
SettingsWindow.Awake => "Awake",
|
||||||
SettingsWindow.AdvancedPaste => "AdvancedPaste",
|
SettingsWindow.AdvancedPaste => "AdvancedPaste",
|
||||||
@@ -103,6 +121,42 @@ internal static class ModuleEnablementService
|
|||||||
SettingsWindow.ZoomIt => "ZoomIt",
|
SettingsWindow.ZoomIt => "ZoomIt",
|
||||||
SettingsWindow.CmdNotFound => "CmdNotFound",
|
SettingsWindow.CmdNotFound => "CmdNotFound",
|
||||||
SettingsWindow.CmdPal => "CmdPal",
|
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 PowerToysProtocolKey = @"Software\Classes\powertoys";
|
||||||
private const string PowerToysUserKey = @"Software\Microsoft\PowerToys";
|
private const string PowerToysUserKey = @"Software\Microsoft\PowerToys";
|
||||||
|
|
||||||
internal static string? GetPowerToysInstallPath()
|
internal static string GetPowerToysInstallPath()
|
||||||
{
|
{
|
||||||
var perUser = GetInstallPathFromRegistry(RegistryHive.CurrentUser);
|
var perUser = GetInstallPathFromRegistry(RegistryHive.CurrentUser);
|
||||||
if (!string.IsNullOrEmpty(perUser))
|
if (!string.IsNullOrEmpty(perUser))
|
||||||
@@ -27,24 +27,24 @@ internal static class PowerToysPathResolver
|
|||||||
return GetInstallPathFromRegistry(RegistryHive.LocalMachine);
|
return GetInstallPathFromRegistry(RegistryHive.LocalMachine);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string? TryResolveExecutable(string executableName)
|
internal static string TryResolveExecutable(string executableName)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(executableName))
|
if (string.IsNullOrEmpty(executableName))
|
||||||
{
|
{
|
||||||
return null;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var baseDirectory = GetPowerToysInstallPath();
|
var baseDirectory = GetPowerToysInstallPath();
|
||||||
if (string.IsNullOrEmpty(baseDirectory))
|
if (string.IsNullOrEmpty(baseDirectory))
|
||||||
{
|
{
|
||||||
return null;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var candidate = Path.Combine(baseDirectory, executableName);
|
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
|
try
|
||||||
{
|
{
|
||||||
@@ -70,41 +70,41 @@ internal static class PowerToysPathResolver
|
|||||||
// Ignore registry access failures and fall back to other checks.
|
// 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
|
try
|
||||||
{
|
{
|
||||||
using var commandKey = baseKey.OpenSubKey($@"{PowerToysProtocolKey}\shell\open\command");
|
using var commandKey = baseKey.OpenSubKey($@"{PowerToysProtocolKey}\shell\open\command");
|
||||||
if (commandKey == null)
|
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))
|
if (string.IsNullOrEmpty(command))
|
||||||
{
|
{
|
||||||
return null;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ExtractInstallDirectory(command);
|
return ExtractInstallDirectory(command);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
return null;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string? GetPathFromUserRegistration(RegistryKey baseKey)
|
private static string GetPathFromUserRegistration(RegistryKey baseKey)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var userKey = baseKey.OpenSubKey(PowerToysUserKey);
|
using var userKey = baseKey.OpenSubKey(PowerToysUserKey);
|
||||||
if (userKey == null)
|
if (userKey == null)
|
||||||
{
|
{
|
||||||
return null;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var installedValue = userKey.GetValue("installed");
|
var installedValue = userKey.GetValue("installed");
|
||||||
@@ -118,14 +118,14 @@ internal static class PowerToysPathResolver
|
|||||||
// Ignore registry access failures.
|
// 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))
|
if (string.IsNullOrEmpty(command))
|
||||||
{
|
{
|
||||||
return null;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -138,7 +138,7 @@ internal static class PowerToysPathResolver
|
|||||||
var quotedPath = command.Substring(1, closingQuote - 1);
|
var quotedPath = command.Substring(1, closingQuote - 1);
|
||||||
if (File.Exists(quotedPath))
|
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(' ');
|
var parts = command.Split(' ');
|
||||||
if (parts.Length > 0 && File.Exists(parts[0]))
|
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.
|
// 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 items = new List<ListItem>();
|
||||||
var module = SettingsDeepLink.SettingsWindow.Awake;
|
var module = SettingsDeepLink.SettingsWindow.Awake;
|
||||||
var settingsTitle = module.ModuleDisplayName();
|
var title = module.ModuleDisplayName();
|
||||||
var icon = PowerToysResourcesHelper.IconFromSettingsIcon("Awake.png");
|
var icon = PowerToysResourcesHelper.IconFromSettingsIcon("Awake.png");
|
||||||
var moduleIcon = module.ModuleIcon();
|
var moduleIcon = module.ModuleIcon();
|
||||||
|
|
||||||
|
items.Add(new ListItem(new OpenInSettingsCommand(module, title))
|
||||||
|
{
|
||||||
|
Title = title,
|
||||||
|
Subtitle = "Open Awake settings",
|
||||||
|
Icon = moduleIcon,
|
||||||
|
});
|
||||||
|
|
||||||
if (!ModuleEnablementService.IsModuleEnabled(module))
|
if (!ModuleEnablementService.IsModuleEnabled(module))
|
||||||
{
|
{
|
||||||
items.Add(new ListItem(new OpenInSettingsCommand(module, settingsTitle))
|
|
||||||
{
|
|
||||||
Title = settingsTitle,
|
|
||||||
Subtitle = "Open Awake settings",
|
|
||||||
Icon = moduleIcon,
|
|
||||||
});
|
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settings entry with quick actions in MoreCommands.
|
|
||||||
items.Add(new ListItem(new OpenInSettingsCommand(module, settingsTitle))
|
|
||||||
{
|
|
||||||
Title = settingsTitle,
|
|
||||||
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()),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
// Direct commands surfaced in the PowerToys list page.
|
// 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"))
|
items.Add(new ListItem(new StartAwakeCommand("Awake: Keep awake indefinitely", () => AwakeService.Instance.SetIndefiniteAsync(), "Awake set to indefinite"))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,24 +21,18 @@ internal sealed class ColorPickerModuleCommandProvider : ModuleCommandProvider
|
|||||||
|
|
||||||
var commands = new List<ListItem>();
|
var commands = new List<ListItem>();
|
||||||
|
|
||||||
if (ModuleEnablementService.IsModuleEnabled(module))
|
|
||||||
{
|
|
||||||
// Quick actions under MoreCommands.
|
|
||||||
var more = new List<CommandContextItem>
|
|
||||||
{
|
|
||||||
new CommandContextItem(new OpenColorPickerCommand()),
|
|
||||||
new CommandContextItem(new CopyColorCommand()),
|
|
||||||
new CommandContextItem(new ColorPickerSavedColorsPage()),
|
|
||||||
};
|
|
||||||
|
|
||||||
commands.Add(new ListItem(new OpenInSettingsCommand(module, title))
|
commands.Add(new ListItem(new OpenInSettingsCommand(module, title))
|
||||||
{
|
{
|
||||||
Title = title,
|
Title = title,
|
||||||
Subtitle = "Open Color Picker settings",
|
Subtitle = "Open Color Picker settings",
|
||||||
Icon = icon,
|
Icon = icon,
|
||||||
MoreCommands = more.ToArray(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!ModuleEnablementService.IsModuleEnabled(module))
|
||||||
|
{
|
||||||
|
return commands;
|
||||||
|
}
|
||||||
|
|
||||||
// Direct entries in the module list.
|
// Direct entries in the module list.
|
||||||
commands.Add(new ListItem(new OpenColorPickerCommand())
|
commands.Add(new ListItem(new OpenColorPickerCommand())
|
||||||
{
|
{
|
||||||
@@ -53,16 +47,6 @@ internal sealed class ColorPickerModuleCommandProvider : ModuleCommandProvider
|
|||||||
Subtitle = "Browse and copy saved colors",
|
Subtitle = "Browse and copy saved colors",
|
||||||
Icon = icon,
|
Icon = icon,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
commands.Add(new ListItem(new OpenInSettingsCommand(module, title))
|
|
||||||
{
|
|
||||||
Title = title,
|
|
||||||
Subtitle = "Open Color Picker settings",
|
|
||||||
Icon = icon,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return commands;
|
return commands;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ using Microsoft.CommandPalette.Extensions;
|
|||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
using PowerToysExtension.Commands;
|
using PowerToysExtension.Commands;
|
||||||
using PowerToysExtension.Helpers;
|
using PowerToysExtension.Helpers;
|
||||||
using PowerToysExtension.Pages;
|
|
||||||
using Workspaces.ModuleServices;
|
using Workspaces.ModuleServices;
|
||||||
using WorkspacesCsharpLibrary.Data;
|
using WorkspacesCsharpLibrary.Data;
|
||||||
|
|
||||||
@@ -26,8 +25,6 @@ internal sealed class WorkspacesModuleCommandProvider : ModuleCommandProvider
|
|||||||
var icon = PowerToysResourcesHelper.IconFromSettingsIcon("Workspaces.png");
|
var icon = PowerToysResourcesHelper.IconFromSettingsIcon("Workspaces.png");
|
||||||
var moduleIcon = module.ModuleIcon();
|
var moduleIcon = module.ModuleIcon();
|
||||||
|
|
||||||
if (!ModuleEnablementService.IsModuleEnabled(module))
|
|
||||||
{
|
|
||||||
items.Add(new ListItem(new OpenInSettingsCommand(module, title))
|
items.Add(new ListItem(new OpenInSettingsCommand(module, title))
|
||||||
{
|
{
|
||||||
Title = title,
|
Title = title,
|
||||||
@@ -35,22 +32,12 @@ internal sealed class WorkspacesModuleCommandProvider : ModuleCommandProvider
|
|||||||
Icon = moduleIcon,
|
Icon = moduleIcon,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!ModuleEnablementService.IsModuleEnabled(module))
|
||||||
|
{
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settings entry plus common actions.
|
// 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()),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
items.Add(new ListItem(new OpenWorkspaceEditorCommand())
|
items.Add(new ListItem(new OpenWorkspaceEditorCommand())
|
||||||
{
|
{
|
||||||
Title = "Workspaces: Open editor",
|
Title = "Workspaces: Open editor",
|
||||||
|
|||||||
Reference in New Issue
Block a user