mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
workspace is done
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
// 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.Diagnostics.CodeAnalysis;
|
||||
using PowerToys.ModuleContracts;
|
||||
using WorkspacesCsharpLibrary.Data;
|
||||
|
||||
@@ -19,7 +18,5 @@ public interface IWorkspaceService : IModuleService
|
||||
|
||||
Task<OperationResult> SnapshotAsync(string? targetPath = null, CancellationToken cancellationToken = default);
|
||||
|
||||
[RequiresUnreferencedCode("Workspace deserialization uses reflection-based JSON serializer.")]
|
||||
[RequiresDynamicCode("Workspace deserialization uses reflection-based JSON serializer.")]
|
||||
Task<OperationResult<IReadOnlyList<ProjectWrapper>>> GetWorkspacesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using Common.UI;
|
||||
using ManagedCommon;
|
||||
using PowerToys.Interop;
|
||||
@@ -80,8 +80,6 @@ public sealed class WorkspaceService : ModuleServiceBase, IWorkspaceService
|
||||
return Task.FromResult(OperationResult.Fail("Snapshot is not implemented for Workspaces."));
|
||||
}
|
||||
|
||||
[RequiresUnreferencedCode("Workspace deserialization uses reflection-based JSON serializer.")]
|
||||
[RequiresDynamicCode("Workspace deserialization uses reflection-based JSON serializer.")]
|
||||
public Task<OperationResult<IReadOnlyList<ProjectWrapper>>> GetWorkspacesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -4,11 +4,9 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using WorkspacesCsharpLibrary.Data;
|
||||
|
||||
namespace WorkspacesCsharpLibrary.Data;
|
||||
|
||||
@@ -17,13 +15,6 @@ namespace WorkspacesCsharpLibrary.Data;
|
||||
/// </summary>
|
||||
public static class WorkspacesStorage
|
||||
{
|
||||
private static readonly JsonSerializerOptions _loadOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
};
|
||||
|
||||
[RequiresUnreferencedCode("Workspace file deserialization uses reflection-based JSON serializer.")]
|
||||
[RequiresDynamicCode("Workspace file deserialization uses reflection-based JSON serializer.")]
|
||||
public static IReadOnlyList<ProjectWrapper> Load()
|
||||
{
|
||||
var filePath = GetDefaultFilePath();
|
||||
@@ -35,7 +26,7 @@ public static class WorkspacesStorage
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(filePath);
|
||||
var data = JsonSerializer.Deserialize<WorkspacesFile>(json, _loadOptions);
|
||||
var data = JsonSerializer.Deserialize(json, WorkspacesStorageJsonContext.Default.WorkspacesFile);
|
||||
|
||||
if (data?.Workspaces == null)
|
||||
{
|
||||
@@ -70,12 +61,12 @@ public static class WorkspacesStorage
|
||||
return Path.Combine(localAppData, "Microsoft", "PowerToys", "Workspaces", "workspaces.json");
|
||||
}
|
||||
|
||||
private sealed class WorkspacesFile
|
||||
internal sealed class WorkspacesFile
|
||||
{
|
||||
public List<WorkspaceProject> Workspaces { get; set; } = new();
|
||||
}
|
||||
|
||||
private sealed class WorkspaceProject
|
||||
internal sealed class WorkspaceProject
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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.Text.Json.Serialization;
|
||||
|
||||
namespace WorkspacesCsharpLibrary.Data;
|
||||
|
||||
[JsonSourceGenerationOptions(PropertyNameCaseInsensitive = true)]
|
||||
[JsonSerializable(typeof(WorkspacesStorage.WorkspacesFile))]
|
||||
internal sealed partial class WorkspacesStorageJsonContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
@@ -2,10 +2,8 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using PowerToysExtension.Helpers;
|
||||
using Workspaces.ModuleServices;
|
||||
|
||||
namespace PowerToysExtension.Commands;
|
||||
|
||||
@@ -25,25 +23,12 @@ internal sealed partial class LaunchWorkspaceCommand : InvokableCommand
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
|
||||
try
|
||||
var result = WorkspaceService.Instance.LaunchWorkspaceAsync(_workspaceId).GetAwaiter().GetResult();
|
||||
if (!result.Success)
|
||||
{
|
||||
var launcherPath = PowerToysPathResolver.TryResolveExecutable("PowerToys.WorkspacesLauncher.exe");
|
||||
if (string.IsNullOrEmpty(launcherPath))
|
||||
{
|
||||
return CommandResult.ShowToast("Unable to locate PowerToys Workspaces launcher.");
|
||||
return CommandResult.ShowToast(result.Error ?? "Launching workspace failed.");
|
||||
}
|
||||
|
||||
var startInfo = new ProcessStartInfo(launcherPath, _workspaceId)
|
||||
{
|
||||
UseShellExecute = true,
|
||||
};
|
||||
|
||||
Process.Start(startInfo);
|
||||
return CommandResult.Hide();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return CommandResult.ShowToast($"Launching workspace failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,102 +2,21 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using PowerToysExtension.Helpers;
|
||||
using Workspaces.ModuleServices;
|
||||
|
||||
namespace PowerToysExtension.Commands;
|
||||
|
||||
internal sealed partial class OpenWorkspaceEditorCommand : InvokableCommand
|
||||
{
|
||||
private const string LaunchEditorEventName = "Local\\Workspaces-LaunchEditorEvent-a55ff427-cf62-4994-a2cd-9f72139296bf";
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (TrySignalLaunchEvent())
|
||||
var result = WorkspaceService.Instance.LaunchEditorAsync().GetAwaiter().GetResult();
|
||||
if (!result.Success)
|
||||
{
|
||||
return CommandResult.ShowToast(result.Error ?? "Unable to launch the Workspaces editor.");
|
||||
}
|
||||
|
||||
return CommandResult.Hide();
|
||||
}
|
||||
|
||||
if (TryLaunchEditorExecutable())
|
||||
{
|
||||
return CommandResult.Hide();
|
||||
}
|
||||
|
||||
if (TryLaunchThroughSettings())
|
||||
{
|
||||
return CommandResult.Hide();
|
||||
}
|
||||
|
||||
return CommandResult.ShowToast("Unable to launch the Workspaces editor.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return CommandResult.ShowToast($"Launching editor failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TrySignalLaunchEvent()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var existing = EventWaitHandle.OpenExisting(LaunchEditorEventName);
|
||||
return existing.Set();
|
||||
}
|
||||
catch (WaitHandleCannotBeOpenedException)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var created = new EventWaitHandle(false, EventResetMode.AutoReset, LaunchEditorEventName, out _);
|
||||
return created.Set();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryLaunchEditorExecutable()
|
||||
{
|
||||
var editorPath = PowerToysPathResolver.TryResolveExecutable("PowerToys.WorkspacesEditor.exe");
|
||||
if (string.IsNullOrEmpty(editorPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var startInfo = new ProcessStartInfo(editorPath)
|
||||
{
|
||||
UseShellExecute = true,
|
||||
};
|
||||
|
||||
Process.Start(startInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryLaunchThroughSettings()
|
||||
{
|
||||
var powerToysExe = PowerToysPathResolver.TryResolveExecutable("PowerToys.exe");
|
||||
if (string.IsNullOrEmpty(powerToysExe))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var startInfo = new ProcessStartInfo(powerToysExe)
|
||||
{
|
||||
Arguments = "--open-settings=Workspaces",
|
||||
UseShellExecute = false,
|
||||
};
|
||||
|
||||
Process.Start(startInfo);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Common.Search.FuzzSearch;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using PowerToysExtension.Modules;
|
||||
|
||||
namespace PowerToysExtension.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Aggregates commands exposed by individual module providers and applies fuzzy filtering.
|
||||
/// </summary>
|
||||
internal static class ModuleCommandCatalog
|
||||
{
|
||||
private static readonly ModuleCommandProvider[] Providers =
|
||||
[
|
||||
new AwakeModuleCommandProvider(),
|
||||
new WorkspacesModuleCommandProvider(),
|
||||
new ColorPickerModuleCommandProvider(),
|
||||
new DefaultSettingsModuleCommandProvider(),
|
||||
];
|
||||
|
||||
public static IListItem[] FilteredItems(string query)
|
||||
{
|
||||
var all = Providers.SelectMany(provider => provider.BuildCommands()).ToList();
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
return [.. all];
|
||||
}
|
||||
|
||||
var matched = new List<Tuple<int, ListItem>>();
|
||||
foreach (var item in all)
|
||||
{
|
||||
var result = StringMatcher.FuzzyMatch(query, item.Title);
|
||||
if (result.Success)
|
||||
{
|
||||
matched.Add(new Tuple<int, ListItem>(result.Score, item));
|
||||
}
|
||||
}
|
||||
|
||||
matched.Sort((x, y) => y.Item1.CompareTo(x.Item1));
|
||||
return [.. matched.Select(x => x.Item2)];
|
||||
}
|
||||
}
|
||||
@@ -1,113 +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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Common.Search.FuzzSearch;
|
||||
using Common.UI;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using PowerToysExtension.Commands;
|
||||
using PowerToysExtension.Pages;
|
||||
|
||||
namespace PowerToysExtension.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Builds the list of PowerToys module entries and supports basic fuzzy filtering.
|
||||
/// </summary>
|
||||
internal static class ModuleItemsHelper
|
||||
{
|
||||
private static List<ListItem>? _cache;
|
||||
|
||||
public static IListItem[] FilteredItems(string query)
|
||||
{
|
||||
var all = AllItems();
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
return [.. all];
|
||||
}
|
||||
|
||||
var matched = new List<Tuple<int, ListItem>>();
|
||||
foreach (var item in all)
|
||||
{
|
||||
var result = StringMatcher.FuzzyMatch(query, item.Title);
|
||||
if (result.Success)
|
||||
{
|
||||
matched.Add(new Tuple<int, ListItem>(result.Score, item));
|
||||
}
|
||||
}
|
||||
|
||||
matched.Sort((x, y) => y.Item1.CompareTo(x.Item1));
|
||||
return [.. matched.Select(x => x.Item2)];
|
||||
}
|
||||
|
||||
private static List<ListItem> AllItems()
|
||||
{
|
||||
if (_cache is not null)
|
||||
{
|
||||
return _cache;
|
||||
}
|
||||
|
||||
var items = new List<ListItem>();
|
||||
foreach (var module in Enum.GetValues<SettingsDeepLink.SettingsWindow>())
|
||||
{
|
||||
var item = CreateItem(module);
|
||||
if (item is not null)
|
||||
{
|
||||
items.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
_cache = items;
|
||||
return items;
|
||||
}
|
||||
|
||||
private static ListItem? CreateItem(SettingsDeepLink.SettingsWindow module)
|
||||
{
|
||||
// Skip purely internal pages.
|
||||
if (module is SettingsDeepLink.SettingsWindow.Dashboard)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var icon = module.ModuleIcon();
|
||||
var title = module.ModuleDisplayName();
|
||||
|
||||
var settingsCommand = new OpenInSettingsCommand(module, title);
|
||||
|
||||
var more = new List<ICommandContextItem>();
|
||||
|
||||
switch (module)
|
||||
{
|
||||
case SettingsDeepLink.SettingsWindow.Awake:
|
||||
more.Add(new CommandContextItem(new StartAwakeCommand("Awake: Keep awake indefinitely", () => "-m indefinite", "Awake set to indefinite")));
|
||||
more.Add(new CommandContextItem(new StartAwakeCommand("Awake: Keep awake for 30 minutes", () => "-m timed -t 30", "Awake set for 30 minutes")));
|
||||
more.Add(new CommandContextItem(new StartAwakeCommand("Awake: Keep awake for 2 hours", () => "-m timed -t 120", "Awake set for 2 hours")));
|
||||
more.Add(new CommandContextItem(new StopAwakeCommand()));
|
||||
break;
|
||||
|
||||
case SettingsDeepLink.SettingsWindow.Workspaces:
|
||||
more.Add(new CommandContextItem(new WorkspacesListPage()));
|
||||
more.Add(new CommandContextItem(new OpenWorkspaceEditorCommand()));
|
||||
break;
|
||||
|
||||
case SettingsDeepLink.SettingsWindow.ColorPicker:
|
||||
more.Add(new CommandContextItem(new CopyColorCommand()));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
var command = new CommandItem(settingsCommand)
|
||||
{
|
||||
Title = title,
|
||||
Icon = icon,
|
||||
MoreCommands = more.Count > 0 ? [.. more] : [],
|
||||
};
|
||||
|
||||
return new ListItem(command);
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,6 @@ using System.Text.Json.Serialization;
|
||||
|
||||
namespace PowerToysExtension.Helpers;
|
||||
|
||||
[JsonSerializable(typeof(WorkspaceItemsHelper.WorkspacesData))]
|
||||
[JsonSerializable(typeof(WorkspaceItemsHelper.WorkspaceProject))]
|
||||
[JsonSerializable(typeof(WorkspaceItemsHelper.WorkspaceApplication))]
|
||||
[JsonSerializable(typeof(AwakeSettingsDocument))]
|
||||
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, PropertyNameCaseInsensitive = true)]
|
||||
internal sealed partial class PowerToysJsonContext : JsonSerializerContext
|
||||
|
||||
@@ -1,452 +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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using PowerToysExtension.Commands;
|
||||
using PowerToysExtension.Helpers;
|
||||
using PowerToysExtension.Pages;
|
||||
using NoOpCommand = PowerToysExtension.Commands.NoOpCommand;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.PowerToys.Helpers;
|
||||
|
||||
internal static class PowerToysModuleItemsHelper
|
||||
{
|
||||
private sealed record ModuleMetadata(
|
||||
string SettingsKey,
|
||||
string Title,
|
||||
string IconPath,
|
||||
string[] SearchTerms,
|
||||
string? Subtitle = null,
|
||||
string? LaunchEvent = null,
|
||||
string? LaunchExecutable = null,
|
||||
string? LaunchArguments = null,
|
||||
ModuleCommandMetadata[]? AdditionalLaunchCommands = null);
|
||||
|
||||
private sealed record ModuleCommandMetadata(
|
||||
string Title,
|
||||
string? Subtitle = null,
|
||||
string? LaunchEvent = null,
|
||||
string? LaunchExecutable = null,
|
||||
string? LaunchArguments = null,
|
||||
string[]? SearchTerms = null);
|
||||
|
||||
private sealed record CustomEntry(Func<ListItem> Factory, string[] SearchTerms)
|
||||
{
|
||||
public ListItem CreateItem() => Factory();
|
||||
}
|
||||
|
||||
private static readonly ModuleMetadata[] Modules =
|
||||
[
|
||||
new(
|
||||
SettingsKey: "AdvancedPaste",
|
||||
Title: "Advanced Paste",
|
||||
IconPath: "Assets\\AdvancedPaste.png",
|
||||
SearchTerms: new[] { "advanced", "paste", "clipboard", "text" },
|
||||
Subtitle: "Open Advanced Paste settings"),
|
||||
new(
|
||||
SettingsKey: "AlwaysOnTop",
|
||||
Title: "Always On Top",
|
||||
IconPath: "Assets\\AlwaysOnTop.png",
|
||||
SearchTerms: new[] { "always", "top", "pin", "window" },
|
||||
Subtitle: "Toggle Always On Top for the active window"),
|
||||
new(
|
||||
SettingsKey: "Awake",
|
||||
Title: "Awake",
|
||||
IconPath: "Assets\\Awake.png",
|
||||
SearchTerms: new[] { "awake", "sleep", "keep", "monitor" },
|
||||
Subtitle: "Keep your PC awake",
|
||||
LaunchExecutable: "PowerToys.Awake.exe",
|
||||
LaunchArguments: "--use-parent-pid --display-on true"),
|
||||
new(
|
||||
SettingsKey: "ColorPicker",
|
||||
Title: "Color Picker",
|
||||
IconPath: "Assets\\ColorPicker.png",
|
||||
SearchTerms: new[] { "color", "picker", "eyedropper", "pick" },
|
||||
Subtitle: "Copy colors from anywhere",
|
||||
LaunchEvent: PowerToysEventNames.ColorPickerShow,
|
||||
LaunchExecutable: "PowerToys.ColorPickerUI.exe"),
|
||||
new(
|
||||
SettingsKey: "CropAndLock",
|
||||
Title: "Crop And Lock",
|
||||
IconPath: "Assets\\CropAndLock.png",
|
||||
SearchTerms: new[] { "crop", "lock", "thumbnail", "reparent" },
|
||||
Subtitle: "Configure Crop And Lock",
|
||||
AdditionalLaunchCommands: new[]
|
||||
{
|
||||
new ModuleCommandMetadata(
|
||||
Title: "Crop active window (thumbnail)",
|
||||
Subtitle: "Creates a static snapshot window",
|
||||
LaunchEvent: PowerToysEventNames.CropAndLockThumbnail,
|
||||
SearchTerms: new[] { "thumbnail", "snapshot", "crop" }),
|
||||
new ModuleCommandMetadata(
|
||||
Title: "Crop active window (reparent)",
|
||||
Subtitle: "Embeds the active window inside a new host",
|
||||
LaunchEvent: PowerToysEventNames.CropAndLockReparent,
|
||||
SearchTerms: new[] { "reparent", "embed", "live window" }),
|
||||
}),
|
||||
new(
|
||||
SettingsKey: "EnvironmentVariables",
|
||||
Title: "Environment Variables",
|
||||
IconPath: "Assets\\EnvironmentVariables.png",
|
||||
SearchTerms: new[] { "environment", "variables", "env", "path" },
|
||||
Subtitle: "Manage environment variables",
|
||||
LaunchEvent: PowerToysEventNames.EnvironmentVariablesShow,
|
||||
AdditionalLaunchCommands: new[]
|
||||
{
|
||||
new ModuleCommandMetadata(
|
||||
Title: "Open as administrator",
|
||||
Subtitle: "Launches the elevated editor",
|
||||
LaunchEvent: PowerToysEventNames.EnvironmentVariablesShowAdmin,
|
||||
SearchTerms: new[] { "admin", "administrator" }),
|
||||
}),
|
||||
new(
|
||||
SettingsKey: "FancyZones",
|
||||
Title: "FancyZones",
|
||||
IconPath: "Assets\\FancyZones.png",
|
||||
SearchTerms: new[] { "fancyzones", "zones", "layout", "window" },
|
||||
Subtitle: "Adjust FancyZones layouts",
|
||||
LaunchEvent: PowerToysEventNames.FancyZonesToggleEditor,
|
||||
LaunchExecutable: "PowerToys.FancyZonesEditor.exe"),
|
||||
new(
|
||||
SettingsKey: "FileExplorer",
|
||||
Title: "File Explorer Add-ons",
|
||||
IconPath: "Assets\\FileExplorerPreview.png",
|
||||
SearchTerms: new[] { "file explorer", "preview", "addons", "powerpreview" },
|
||||
Subtitle: "Configure File Explorer add-ons"),
|
||||
new(
|
||||
SettingsKey: "FileLocksmith",
|
||||
Title: "File Locksmith",
|
||||
IconPath: "Assets\\FileLocksmith.png",
|
||||
SearchTerms: new[] { "file", "locksmith", "lock", "unlock" },
|
||||
Subtitle: "Find which process locks a file"),
|
||||
new(
|
||||
SettingsKey: "Hosts",
|
||||
Title: "Hosts File Editor",
|
||||
IconPath: "Assets\\Hosts.png",
|
||||
SearchTerms: new[] { "hosts", "file", "editor" },
|
||||
Subtitle: "Edit the hosts file",
|
||||
LaunchEvent: PowerToysEventNames.HostsShow,
|
||||
AdditionalLaunchCommands: new[]
|
||||
{
|
||||
new ModuleCommandMetadata(
|
||||
Title: "Open as administrator",
|
||||
Subtitle: "Launches the elevated editor",
|
||||
LaunchEvent: PowerToysEventNames.HostsShowAdmin,
|
||||
SearchTerms: new[] { "admin", "administrator" }),
|
||||
}),
|
||||
new(
|
||||
SettingsKey: "ImageResizer",
|
||||
Title: "Image Resizer",
|
||||
IconPath: "Assets\\ImageResizer.png",
|
||||
SearchTerms: new[] { "image", "resize", "resizer", "photo" },
|
||||
Subtitle: "Resize images in bulk"),
|
||||
new(
|
||||
SettingsKey: "KBM",
|
||||
Title: "Keyboard Manager",
|
||||
IconPath: "Assets\\KeyboardManager.png",
|
||||
SearchTerms: new[] { "keyboard", "manager", "remap", "kbm", "shortcut" },
|
||||
Subtitle: "Remap keys and shortcuts"),
|
||||
new(
|
||||
SettingsKey: "MeasureTool",
|
||||
Title: "Screen Ruler",
|
||||
IconPath: "Assets\\ScreenRuler.png",
|
||||
SearchTerms: new[] { "screen", "ruler", "measure", "measuretool" },
|
||||
Subtitle: "Measure on-screen elements",
|
||||
LaunchEvent: PowerToysEventNames.MeasureToolTrigger),
|
||||
new(
|
||||
SettingsKey: "MouseUtils",
|
||||
Title: "Mouse Utilities",
|
||||
IconPath: "Assets\\MouseUtils.png",
|
||||
SearchTerms: new[] { "mouse", "utilities", "finder", "highlighter", "crosshairs" },
|
||||
Subtitle: "Configure mouse utilities"),
|
||||
new(
|
||||
SettingsKey: "MouseWithoutBorders",
|
||||
Title: "Mouse Without Borders",
|
||||
IconPath: "Assets\\MouseWithoutBorders.png",
|
||||
SearchTerms: new[] { "mouse", "borders", "multi pc", "mwob" },
|
||||
Subtitle: "Share mouse and keyboard across PCs"),
|
||||
new(
|
||||
SettingsKey: "NewPlus",
|
||||
Title: "New+",
|
||||
IconPath: "Assets\\NewPlus.png",
|
||||
SearchTerms: new[] { "new", "template", "file" },
|
||||
Subtitle: "Create templates quickly"),
|
||||
new(
|
||||
SettingsKey: "Peek",
|
||||
Title: "Peek",
|
||||
IconPath: "Assets\\Peek.png",
|
||||
SearchTerms: new[] { "peek", "preview", "quick look" },
|
||||
Subtitle: "Preview files instantly",
|
||||
LaunchEvent: PowerToysEventNames.PeekShow),
|
||||
new(
|
||||
SettingsKey: "PowerAccent",
|
||||
Title: "Quick Accent",
|
||||
IconPath: "Assets\\QuickAccent.png",
|
||||
SearchTerms: new[] { "accent", "quick", "characters", "diacritics", "poweraccent" },
|
||||
Subtitle: "Insert accented characters"),
|
||||
new(
|
||||
SettingsKey: "PowerOCR",
|
||||
Title: "Text Extractor",
|
||||
IconPath: "Assets\\TextExtractor.png",
|
||||
SearchTerms: new[] { "text", "extractor", "ocr", "copy" },
|
||||
Subtitle: "Extract text from the screen",
|
||||
LaunchEvent: PowerToysEventNames.PowerOcrShow,
|
||||
LaunchExecutable: "PowerToys.PowerOCR.exe"),
|
||||
new(
|
||||
SettingsKey: "PowerRename",
|
||||
Title: "PowerRename",
|
||||
IconPath: "Assets\\PowerRename.png",
|
||||
SearchTerms: new[] { "rename", "files", "powerrename" },
|
||||
Subtitle: "Batch rename files",
|
||||
LaunchExecutable: "PowerRename.exe"),
|
||||
new(
|
||||
SettingsKey: "RegistryPreview",
|
||||
Title: "Registry Preview",
|
||||
IconPath: "Assets\\RegistryPreview.png",
|
||||
SearchTerms: new[] { "registry", "preview", "reg" },
|
||||
Subtitle: "Inspect and edit registry files",
|
||||
LaunchEvent: PowerToysEventNames.RegistryPreviewTrigger),
|
||||
new(
|
||||
SettingsKey: "Run",
|
||||
Title: "PowerToys Run",
|
||||
IconPath: "Assets\\PowerToysRun.png",
|
||||
SearchTerms: new[] { "run", "launcher", "search", "powertoys run", "powerlauncher" },
|
||||
Subtitle: "Quickly search and launch",
|
||||
LaunchEvent: PowerToysEventNames.PowerToysRunInvoke),
|
||||
new(
|
||||
SettingsKey: "ShortcutGuide",
|
||||
Title: "Shortcut Guide",
|
||||
IconPath: "Assets\\ShortcutGuide.png",
|
||||
SearchTerms: new[] { "shortcut", "guide", "keys", "help" },
|
||||
Subtitle: "View available shortcuts",
|
||||
LaunchEvent: PowerToysEventNames.ShortcutGuideTrigger),
|
||||
new(
|
||||
SettingsKey: "CmdPal",
|
||||
Title: "Command Palette",
|
||||
IconPath: "Assets\\CmdPal.png",
|
||||
SearchTerms: new[] { "command", "palette", "cmdpal", "prompt" },
|
||||
Subtitle: "Open the Command Palette",
|
||||
LaunchEvent: PowerToysEventNames.CommandPaletteShow),
|
||||
new(
|
||||
SettingsKey: "CmdNotFound",
|
||||
Title: "Command Not Found",
|
||||
IconPath: "Assets\\CommandNotFound.png",
|
||||
SearchTerms: new[] { "command", "not", "found", "terminal" },
|
||||
Subtitle: "Suggest commands when mistyped"),
|
||||
new(
|
||||
SettingsKey: "Workspaces",
|
||||
Title: "Workspaces",
|
||||
IconPath: "Assets\\Workspaces.png",
|
||||
SearchTerms: new[] { "workspace", "layouts", "projects" },
|
||||
Subtitle: "Manage PowerToys workspaces",
|
||||
LaunchEvent: PowerToysEventNames.WorkspacesLaunchEditor,
|
||||
AdditionalLaunchCommands: new[]
|
||||
{
|
||||
new ModuleCommandMetadata(
|
||||
Title: "Trigger Workspaces hotkey",
|
||||
Subtitle: "Invokes the configured Workspaces action",
|
||||
LaunchEvent: PowerToysEventNames.WorkspacesHotkey,
|
||||
SearchTerms: new[] { "hotkey", "shortcut" }),
|
||||
}),
|
||||
new(
|
||||
SettingsKey: "ZoomIt",
|
||||
Title: "ZoomIt",
|
||||
IconPath: "Assets\\ZoomIt.png",
|
||||
SearchTerms: new[] { "zoom", "zoomit", "presentation" },
|
||||
Subtitle: "Configure ZoomIt"),
|
||||
new(
|
||||
SettingsKey: "Overview",
|
||||
Title: "General",
|
||||
IconPath: "Assets\\PowerToys.png",
|
||||
SearchTerms: new[] { "general", "overview", "about" },
|
||||
Subtitle: "General PowerToys settings"),
|
||||
new(
|
||||
SettingsKey: "Dashboard",
|
||||
Title: "Dashboard",
|
||||
IconPath: "Assets\\PowerToys.png",
|
||||
SearchTerms: new[] { "dashboard", "status", "summary" },
|
||||
Subtitle: "View overall status"),
|
||||
];
|
||||
|
||||
private static readonly CustomEntry[] CustomEntries =
|
||||
[
|
||||
new(
|
||||
() => new ListItem(new CommandItem(new WorkspacesListPage()))
|
||||
{
|
||||
Title = "Workspaces list",
|
||||
Subtitle = "Browse individual workspaces",
|
||||
Icon = IconHelpers.FromRelativePath("Assets\\Workspaces.png"),
|
||||
},
|
||||
new[] { "workspace", "workspaces", "layout" }),
|
||||
new(
|
||||
() => new ListItem(new CommandItem(new AwakeSessionsPage()))
|
||||
{
|
||||
Title = "Awake actions",
|
||||
Subtitle = "Start, stop, or schedule Awake",
|
||||
Icon = IconHelpers.FromRelativePath("Assets\\Awake.png"),
|
||||
},
|
||||
new[] { "awake", "keep awake", "sleep", "prevent", "timer" }),
|
||||
];
|
||||
|
||||
internal static IListItem[] GetModuleItems(string? searchText)
|
||||
{
|
||||
var results = new List<IListItem>();
|
||||
|
||||
foreach (var module in Modules)
|
||||
{
|
||||
if (Matches(module, searchText))
|
||||
{
|
||||
results.Add(CreateModuleItem(module));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var entry in CustomEntries)
|
||||
{
|
||||
var item = entry.CreateItem();
|
||||
if (Matches(item, entry.SearchTerms, searchText))
|
||||
{
|
||||
results.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
var workspaceItems = WorkspaceItemsHelper.GetWorkspaceItems(searchText);
|
||||
if (workspaceItems.Length > 0)
|
||||
{
|
||||
results.AddRange(workspaceItems);
|
||||
}
|
||||
|
||||
return results.ToArray();
|
||||
}
|
||||
|
||||
private static ListItem CreateModuleItem(ModuleMetadata metadata)
|
||||
{
|
||||
var mainCommand = CreatePrimaryCommand(metadata, out var usesSettingsForPrimary);
|
||||
|
||||
var listItem = new ListItem(mainCommand)
|
||||
{
|
||||
Title = metadata.Title,
|
||||
Subtitle = metadata.Subtitle ?? (usesSettingsForPrimary ? "Open module settings" : $"Launch {metadata.Title}"),
|
||||
Icon = IconHelpers.FromRelativePath(metadata.IconPath),
|
||||
};
|
||||
|
||||
var moreCommands = new List<ICommandContextItem>();
|
||||
if (string.Equals(metadata.SettingsKey, "Awake", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
listItem.Subtitle = AwakeStatusService.BuildSubtitle();
|
||||
AwakeCommandsFactory.PopulateModuleCommands(moreCommands);
|
||||
}
|
||||
|
||||
if (metadata.AdditionalLaunchCommands is { Length: > 0 })
|
||||
{
|
||||
foreach (var additional in metadata.AdditionalLaunchCommands)
|
||||
{
|
||||
var additionalCommand = new LaunchModuleCommand(
|
||||
metadata.Title,
|
||||
additional.LaunchEvent,
|
||||
additional.LaunchExecutable,
|
||||
additional.LaunchArguments,
|
||||
additional.Title);
|
||||
|
||||
var contextItem = new CommandContextItem(additionalCommand);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(additional.Title))
|
||||
{
|
||||
contextItem.Title = additional.Title;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(additional.Subtitle))
|
||||
{
|
||||
contextItem.Subtitle = additional.Subtitle;
|
||||
}
|
||||
|
||||
moreCommands.Add(contextItem);
|
||||
}
|
||||
}
|
||||
|
||||
if (!usesSettingsForPrimary && !string.IsNullOrEmpty(metadata.SettingsKey))
|
||||
{
|
||||
moreCommands.Add(new CommandContextItem(new OpenPowerToysSettingsCommand(metadata.Title, metadata.SettingsKey)));
|
||||
}
|
||||
|
||||
if (moreCommands.Count > 0)
|
||||
{
|
||||
listItem.MoreCommands = moreCommands.ToArray();
|
||||
}
|
||||
|
||||
return listItem;
|
||||
}
|
||||
|
||||
private static InvokableCommand CreatePrimaryCommand(ModuleMetadata metadata, out bool usesSettings)
|
||||
{
|
||||
usesSettings = false;
|
||||
|
||||
if (!string.IsNullOrEmpty(metadata.LaunchEvent) || !string.IsNullOrEmpty(metadata.LaunchExecutable))
|
||||
{
|
||||
return new LaunchModuleCommand(metadata.Title, metadata.LaunchEvent, metadata.LaunchExecutable, metadata.LaunchArguments);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(metadata.SettingsKey))
|
||||
{
|
||||
usesSettings = true;
|
||||
return new OpenPowerToysSettingsCommand(metadata.Title, metadata.SettingsKey);
|
||||
}
|
||||
|
||||
return new NoOpCommand();
|
||||
}
|
||||
|
||||
private static bool Matches(ModuleMetadata metadata, string? searchText)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(searchText))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Contains(metadata.Title, searchText) || Contains(metadata.Subtitle, searchText))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (metadata.AdditionalLaunchCommands is { Length: > 0 })
|
||||
{
|
||||
foreach (var additional in metadata.AdditionalLaunchCommands)
|
||||
{
|
||||
if (Contains(additional.Title, searchText) || Contains(additional.Subtitle, searchText))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (additional.SearchTerms is { Length: > 0 } && additional.SearchTerms.Any(term => Contains(term, searchText)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return metadata.SearchTerms.Any(term => Contains(term, searchText));
|
||||
}
|
||||
|
||||
private static bool Matches(ListItem item, IReadOnlyCollection<string> searchTerms, string? searchText)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(searchText))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Contains(item.Title, searchText) || Contains(item.Subtitle, searchText))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return searchTerms.Any(term => Contains(term, searchText));
|
||||
}
|
||||
|
||||
private static bool Contains(string? source, string query)
|
||||
{
|
||||
return !string.IsNullOrEmpty(source) && source.Contains(query, StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -1,144 +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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using PowerToysExtension.Commands;
|
||||
|
||||
namespace PowerToysExtension.Helpers;
|
||||
|
||||
internal static class WorkspaceItemsHelper
|
||||
{
|
||||
private const string WorkspacesDirectory = "Microsoft\\PowerToys\\Workspaces";
|
||||
private const string WorkspacesFileName = "workspaces.json";
|
||||
|
||||
internal sealed class WorkspaceApplication
|
||||
{
|
||||
public string? Application { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class WorkspaceProject
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public List<WorkspaceApplication>? Applications { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class WorkspacesData
|
||||
{
|
||||
public List<WorkspaceProject>? Workspaces { get; set; }
|
||||
}
|
||||
|
||||
internal static ListItem CreateOpenEditorItem()
|
||||
{
|
||||
return new ListItem(new OpenWorkspaceEditorCommand())
|
||||
{
|
||||
Title = "Open Workspaces editor",
|
||||
Subtitle = "Launch the PowerToys Workspaces editor",
|
||||
Icon = IconHelpers.FromRelativePath("Assets\\Workspaces.png"),
|
||||
};
|
||||
}
|
||||
|
||||
internal static IListItem[] GetWorkspaceItems(string? searchText)
|
||||
{
|
||||
var workspaces = LoadWorkspaces();
|
||||
var filtered = string.IsNullOrWhiteSpace(searchText)
|
||||
? workspaces
|
||||
: workspaces.Where(ws => Contains(ws.Name, searchText)).ToList();
|
||||
|
||||
var items = new List<IListItem>(filtered.Count + 1)
|
||||
{
|
||||
CreateOpenEditorItem(),
|
||||
};
|
||||
|
||||
foreach (var workspace in filtered)
|
||||
{
|
||||
if (string.IsNullOrEmpty(workspace.Id) || string.IsNullOrEmpty(workspace.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
items.Add(new ListItem(new LaunchWorkspaceCommand(workspace.Id))
|
||||
{
|
||||
Title = workspace.Name,
|
||||
Subtitle = BuildSubtitle(workspace),
|
||||
Icon = IconHelpers.FromRelativePath("Assets\\Workspaces.png"),
|
||||
});
|
||||
}
|
||||
|
||||
return items.ToArray();
|
||||
}
|
||||
|
||||
internal static IListItem[] FilteredItems(string searchText) => GetWorkspaceItems(searchText);
|
||||
|
||||
private static List<WorkspaceProject> LoadWorkspaces()
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = GetWorkspacesFilePath();
|
||||
if (string.IsNullOrEmpty(path) || !File.Exists(path))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var json = File.ReadAllText(path);
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var data = JsonSerializer.Deserialize(json, PowerToysJsonContext.Default.WorkspacesData);
|
||||
return data?.Workspaces ?? [];
|
||||
}
|
||||
catch
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private static string? GetWorkspacesFilePath()
|
||||
{
|
||||
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
if (string.IsNullOrEmpty(localAppData))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Path.Combine(localAppData, WorkspacesDirectory, WorkspacesFileName);
|
||||
}
|
||||
|
||||
private static string BuildSubtitle(WorkspaceProject workspace)
|
||||
{
|
||||
var count = workspace.Applications?.Count ?? 0;
|
||||
if (count == 0)
|
||||
{
|
||||
return "No applications";
|
||||
}
|
||||
|
||||
if (count == 1)
|
||||
{
|
||||
return "1 application";
|
||||
}
|
||||
|
||||
return string.Format(CultureInfo.CurrentCulture, "{0} applications", count);
|
||||
}
|
||||
|
||||
private static bool Contains(string? source, string needle)
|
||||
{
|
||||
if (string.IsNullOrEmpty(source))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return source.Contains(needle, StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -56,6 +56,7 @@
|
||||
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\common\Common.Search\Common.Search.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\common\Common.UI\Common.UI.csproj" />
|
||||
<ProjectReference Include="..\..\..\Workspaces\Workspaces.ModuleServices\Workspaces.ModuleServices.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Exclude legacy Helper folder in favor of Helpers -->
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Common.UI;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using PowerToysExtension.Commands;
|
||||
using PowerToysExtension.Helpers;
|
||||
|
||||
namespace PowerToysExtension.Modules;
|
||||
|
||||
internal sealed class AwakeModuleCommandProvider : ModuleCommandProvider
|
||||
{
|
||||
public override IEnumerable<ListItem> BuildCommands()
|
||||
{
|
||||
var title = SettingsDeepLink.SettingsWindow.Awake.ModuleDisplayName();
|
||||
var icon = SettingsDeepLink.SettingsWindow.Awake.ModuleIcon();
|
||||
|
||||
var more = new List<CommandContextItem>
|
||||
{
|
||||
new CommandContextItem(new StartAwakeCommand("Awake: Keep awake indefinitely", () => "-m indefinite", "Awake set to indefinite")),
|
||||
new CommandContextItem(new StartAwakeCommand("Awake: Keep awake for 30 minutes", () => "-m timed -t 30", "Awake set for 30 minutes")),
|
||||
new CommandContextItem(new StartAwakeCommand("Awake: Keep awake for 2 hours", () => "-m timed -t 120", "Awake set for 2 hours")),
|
||||
new CommandContextItem(new StopAwakeCommand()),
|
||||
};
|
||||
|
||||
var item = new ListItem(new OpenInSettingsCommand(SettingsDeepLink.SettingsWindow.Awake, title))
|
||||
{
|
||||
Title = title,
|
||||
Subtitle = "Open Awake settings",
|
||||
Icon = icon,
|
||||
MoreCommands = more.ToArray(),
|
||||
};
|
||||
|
||||
return [item];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Common.UI;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using PowerToysExtension.Commands;
|
||||
using PowerToysExtension.Helpers;
|
||||
|
||||
namespace PowerToysExtension.Modules;
|
||||
|
||||
internal sealed class ColorPickerModuleCommandProvider : ModuleCommandProvider
|
||||
{
|
||||
public override IEnumerable<ListItem> BuildCommands()
|
||||
{
|
||||
var title = SettingsDeepLink.SettingsWindow.ColorPicker.ModuleDisplayName();
|
||||
var icon = SettingsDeepLink.SettingsWindow.ColorPicker.ModuleIcon();
|
||||
|
||||
var more = new List<CommandContextItem>
|
||||
{
|
||||
new CommandContextItem(new CopyColorCommand()),
|
||||
};
|
||||
|
||||
var item = new ListItem(new OpenInSettingsCommand(SettingsDeepLink.SettingsWindow.ColorPicker, title))
|
||||
{
|
||||
Title = title,
|
||||
Subtitle = "Open Color Picker settings",
|
||||
Icon = icon,
|
||||
MoreCommands = more.ToArray(),
|
||||
};
|
||||
|
||||
return [item];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Common.UI;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using PowerToysExtension.Commands;
|
||||
using PowerToysExtension.Helpers;
|
||||
|
||||
namespace PowerToysExtension.Modules;
|
||||
|
||||
/// <summary>
|
||||
/// Provides open-settings commands for modules without specialized commands.
|
||||
/// </summary>
|
||||
internal sealed class DefaultSettingsModuleCommandProvider : ModuleCommandProvider
|
||||
{
|
||||
private static readonly SettingsDeepLink.SettingsWindow[] _excluded =
|
||||
[
|
||||
SettingsDeepLink.SettingsWindow.Dashboard,
|
||||
SettingsDeepLink.SettingsWindow.Workspaces,
|
||||
SettingsDeepLink.SettingsWindow.Awake,
|
||||
SettingsDeepLink.SettingsWindow.ColorPicker,
|
||||
];
|
||||
|
||||
public override IEnumerable<ListItem> BuildCommands()
|
||||
{
|
||||
foreach (var module in Enum.GetValues<SettingsDeepLink.SettingsWindow>())
|
||||
{
|
||||
if (_excluded.Contains(module))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var title = module.ModuleDisplayName();
|
||||
yield return new ListItem(new OpenInSettingsCommand(module, title))
|
||||
{
|
||||
Title = title,
|
||||
Subtitle = "Open module settings",
|
||||
Icon = module.ModuleIcon(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace PowerToysExtension.Modules;
|
||||
|
||||
/// <summary>
|
||||
/// Base contract for a PowerToys module to expose its command palette entries.
|
||||
/// </summary>
|
||||
internal abstract class ModuleCommandProvider
|
||||
{
|
||||
public abstract IEnumerable<ListItem> BuildCommands();
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Common.UI;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using PowerToysExtension.Commands;
|
||||
using PowerToysExtension.Helpers;
|
||||
using PowerToysExtension.Pages;
|
||||
using Workspaces.ModuleServices;
|
||||
using WorkspacesCsharpLibrary.Data;
|
||||
|
||||
namespace PowerToysExtension.Modules;
|
||||
|
||||
internal sealed class WorkspacesModuleCommandProvider : ModuleCommandProvider
|
||||
{
|
||||
public override IEnumerable<ListItem> BuildCommands()
|
||||
{
|
||||
var items = new List<ListItem>();
|
||||
|
||||
// Settings entry plus common actions.
|
||||
var title = SettingsDeepLink.SettingsWindow.Workspaces.ModuleDisplayName();
|
||||
items.Add(new ListItem(new OpenInSettingsCommand(SettingsDeepLink.SettingsWindow.Workspaces, title))
|
||||
{
|
||||
Title = title,
|
||||
Subtitle = "Open Workspaces settings",
|
||||
Icon = SettingsDeepLink.SettingsWindow.Workspaces.ModuleIcon(),
|
||||
MoreCommands =
|
||||
[
|
||||
new CommandContextItem(new WorkspacesListPage()),
|
||||
new CommandContextItem(new OpenWorkspaceEditorCommand()),
|
||||
],
|
||||
});
|
||||
|
||||
// Per-workspace entries via the shared service.
|
||||
foreach (var workspace in LoadWorkspaces())
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(workspace.Id) || string.IsNullOrWhiteSpace(workspace.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
items.Add(new ListItem(new LaunchWorkspaceCommand(workspace.Id))
|
||||
{
|
||||
Title = workspace.Name,
|
||||
Subtitle = BuildSubtitle(workspace),
|
||||
Icon = IconHelpers.FromRelativePath("Assets\\Workspaces.png"),
|
||||
Details = BuildDetails(workspace),
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<ProjectWrapper> LoadWorkspaces()
|
||||
{
|
||||
var result = WorkspaceService.Instance.GetWorkspacesAsync().GetAwaiter().GetResult();
|
||||
return result.Success && result.Value is not null ? result.Value : System.Array.Empty<ProjectWrapper>();
|
||||
}
|
||||
|
||||
private static string BuildSubtitle(ProjectWrapper workspace)
|
||||
{
|
||||
var appCount = workspace.Applications?.Count ?? 0;
|
||||
var monitorCount = workspace.MonitorConfiguration?.Count ?? 0;
|
||||
var appsText = appCount switch
|
||||
{
|
||||
0 => "No applications",
|
||||
1 => "1 application",
|
||||
_ => string.Format(CultureInfo.CurrentCulture, "{0} applications", appCount),
|
||||
};
|
||||
|
||||
var monitorsText = monitorCount switch
|
||||
{
|
||||
0 => "No monitors",
|
||||
1 => "1 monitor",
|
||||
_ => string.Format(CultureInfo.CurrentCulture, "{0} monitors", monitorCount),
|
||||
};
|
||||
|
||||
var lastLaunched = workspace.LastLaunchedTime > 0
|
||||
? $"Last launched {FormatRelativeTime(workspace.LastLaunchedTime)}"
|
||||
: "Never launched";
|
||||
|
||||
return $"{appsText} • {monitorsText} • {lastLaunched}";
|
||||
}
|
||||
|
||||
private static Details BuildDetails(ProjectWrapper workspace)
|
||||
{
|
||||
var appCount = workspace.Applications?.Count ?? 0;
|
||||
var monitorCount = workspace.MonitorConfiguration?.Count ?? 0;
|
||||
var lastLaunched = workspace.LastLaunchedTime > 0
|
||||
? FormatRelativeTime(workspace.LastLaunchedTime)
|
||||
: "Never launched";
|
||||
|
||||
return new Details
|
||||
{
|
||||
HeroImage = IconHelpers.FromRelativePath("Assets\\Workspaces.png"),
|
||||
Title = workspace.Name,
|
||||
Metadata = BuildAppMetadata(workspace),
|
||||
};
|
||||
}
|
||||
|
||||
private static IDetailsElement[] BuildAppMetadata(ProjectWrapper workspace)
|
||||
{
|
||||
if (workspace.Applications is null || workspace.Applications.Count == 0)
|
||||
{
|
||||
return Array.Empty<IDetailsElement>();
|
||||
}
|
||||
|
||||
var elements = new List<IDetailsElement>();
|
||||
foreach (var app in workspace.Applications)
|
||||
{
|
||||
var tags = new List<ITag>();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(app.ApplicationPath))
|
||||
{
|
||||
tags.Add(new Tag(app.ApplicationPath));
|
||||
}
|
||||
|
||||
tags.Add(new Tag(string.IsNullOrWhiteSpace(app.Application) ? "App" : app.Application));
|
||||
|
||||
if (app.Monitor > 0)
|
||||
{
|
||||
tags.Add(new Tag($"Monitor {app.Monitor}"));
|
||||
}
|
||||
|
||||
elements.Add(new DetailsElement
|
||||
{
|
||||
Key = string.IsNullOrWhiteSpace(app.Title) ? (app.Application ?? "Application") : app.Title,
|
||||
Data = new DetailsTags { Tags = tags.ToArray() },
|
||||
});
|
||||
}
|
||||
|
||||
return elements.ToArray();
|
||||
}
|
||||
|
||||
private static string FormatRelativeTime(long unixSeconds)
|
||||
{
|
||||
var lastLaunch = DateTimeOffset.FromUnixTimeSeconds(unixSeconds).UtcDateTime;
|
||||
var delta = DateTime.UtcNow - lastLaunch;
|
||||
|
||||
if (delta.TotalMinutes < 1)
|
||||
{
|
||||
return "just now";
|
||||
}
|
||||
|
||||
if (delta.TotalMinutes < 60)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, "{0} min ago", (int)delta.TotalMinutes);
|
||||
}
|
||||
|
||||
if (delta.TotalHours < 24)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, "{0} hr ago", (int)delta.TotalHours);
|
||||
}
|
||||
|
||||
return string.Format(CultureInfo.CurrentCulture, "{0} days ago", (int)delta.TotalDays);
|
||||
}
|
||||
}
|
||||
@@ -32,5 +32,5 @@ internal sealed partial class PowerToysListPage : DynamicListPage
|
||||
RaiseItemsChanged(0);
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems() => ModuleItemsHelper.FilteredItems(SearchText);
|
||||
public override IListItem[] GetItems() => ModuleCommandCatalog.FilteredItems(SearchText);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Linq;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using PowerToysExtension.Helpers;
|
||||
using PowerToysExtension.Modules;
|
||||
|
||||
namespace PowerToysExtension.Pages;
|
||||
|
||||
@@ -32,5 +33,5 @@ internal sealed partial class WorkspacesListPage : DynamicListPage
|
||||
RaiseItemsChanged(0);
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems() => WorkspaceItemsHelper.FilteredItems(SearchText);
|
||||
public override IListItem[] GetItems() => [.. new WorkspacesModuleCommandProvider().BuildCommands()];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user