diff --git a/src/modules/Workspaces/Workspaces.ModuleServices/IWorkspaceService.cs b/src/modules/Workspaces/Workspaces.ModuleServices/IWorkspaceService.cs index f2b55f7f7f..00300fea9f 100644 --- a/src/modules/Workspaces/Workspaces.ModuleServices/IWorkspaceService.cs +++ b/src/modules/Workspaces/Workspaces.ModuleServices/IWorkspaceService.cs @@ -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 SnapshotAsync(string? targetPath = null, CancellationToken cancellationToken = default); - [RequiresUnreferencedCode("Workspace deserialization uses reflection-based JSON serializer.")] - [RequiresDynamicCode("Workspace deserialization uses reflection-based JSON serializer.")] Task>> GetWorkspacesAsync(CancellationToken cancellationToken = default); } diff --git a/src/modules/Workspaces/Workspaces.ModuleServices/WorkspaceService.cs b/src/modules/Workspaces/Workspaces.ModuleServices/WorkspaceService.cs index 06b83f010e..eb916e24df 100644 --- a/src/modules/Workspaces/Workspaces.ModuleServices/WorkspaceService.cs +++ b/src/modules/Workspaces/Workspaces.ModuleServices/WorkspaceService.cs @@ -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>> GetWorkspacesAsync(CancellationToken cancellationToken = default) { try diff --git a/src/modules/Workspaces/WorkspacesCsharpLibrary/Data/WorkspacesStorage.cs b/src/modules/Workspaces/WorkspacesCsharpLibrary/Data/WorkspacesStorage.cs index 0894ab117a..e1762d2e28 100644 --- a/src/modules/Workspaces/WorkspacesCsharpLibrary/Data/WorkspacesStorage.cs +++ b/src/modules/Workspaces/WorkspacesCsharpLibrary/Data/WorkspacesStorage.cs @@ -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; /// 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 Load() { var filePath = GetDefaultFilePath(); @@ -35,7 +26,7 @@ public static class WorkspacesStorage try { var json = File.ReadAllText(filePath); - var data = JsonSerializer.Deserialize(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 Workspaces { get; set; } = new(); } - private sealed class WorkspaceProject + internal sealed class WorkspaceProject { public string Id { get; set; } = string.Empty; diff --git a/src/modules/Workspaces/WorkspacesCsharpLibrary/Data/WorkspacesStorageJsonContext.cs b/src/modules/Workspaces/WorkspacesCsharpLibrary/Data/WorkspacesStorageJsonContext.cs new file mode 100644 index 0000000000..60b5e55ba3 --- /dev/null +++ b/src/modules/Workspaces/WorkspacesCsharpLibrary/Data/WorkspacesStorageJsonContext.cs @@ -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 +{ +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/LaunchWorkspaceCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/LaunchWorkspaceCommand.cs index 28eea5b467..9d47cf33aa 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/LaunchWorkspaceCommand.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/LaunchWorkspaceCommand.cs @@ -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."); - } - - 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}"); + return CommandResult.ShowToast(result.Error ?? "Launching workspace failed."); } + + return CommandResult.Hide(); } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/OpenWorkspaceEditorCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/OpenWorkspaceEditorCommand.cs index f4d5b1cc77..63150902a6 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/OpenWorkspaceEditorCommand.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/OpenWorkspaceEditorCommand.cs @@ -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 + var result = WorkspaceService.Instance.LaunchEditorAsync().GetAwaiter().GetResult(); + if (!result.Success) { - if (TrySignalLaunchEvent()) - { - 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; + return CommandResult.ShowToast(result.Error ?? "Unable to launch the Workspaces editor."); } - 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; + return CommandResult.Hide(); } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/ModuleCommandCatalog.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/ModuleCommandCatalog.cs new file mode 100644 index 0000000000..10822fbf22 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/ModuleCommandCatalog.cs @@ -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; + +/// +/// Aggregates commands exposed by individual module providers and applies fuzzy filtering. +/// +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>(); + foreach (var item in all) + { + var result = StringMatcher.FuzzyMatch(query, item.Title); + if (result.Success) + { + matched.Add(new Tuple(result.Score, item)); + } + } + + matched.Sort((x, y) => y.Item1.CompareTo(x.Item1)); + return [.. matched.Select(x => x.Item2)]; + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/ModuleItemsHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/ModuleItemsHelper.cs deleted file mode 100644 index b18e2fcd7f..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/ModuleItemsHelper.cs +++ /dev/null @@ -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; - -/// -/// Builds the list of PowerToys module entries and supports basic fuzzy filtering. -/// -internal static class ModuleItemsHelper -{ - private static List? _cache; - - public static IListItem[] FilteredItems(string query) - { - var all = AllItems(); - if (string.IsNullOrWhiteSpace(query)) - { - return [.. all]; - } - - var matched = new List>(); - foreach (var item in all) - { - var result = StringMatcher.FuzzyMatch(query, item.Title); - if (result.Success) - { - matched.Add(new Tuple(result.Score, item)); - } - } - - matched.Sort((x, y) => y.Item1.CompareTo(x.Item1)); - return [.. matched.Select(x => x.Item2)]; - } - - private static List AllItems() - { - if (_cache is not null) - { - return _cache; - } - - var items = new List(); - foreach (var module in Enum.GetValues()) - { - 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(); - - 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); - } -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/PowerToysJsonContext.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/PowerToysJsonContext.cs index 22b76aa696..1cc84756ad 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/PowerToysJsonContext.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/PowerToysJsonContext.cs @@ -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 diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/PowerToysModuleItemsHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/PowerToysModuleItemsHelper.cs deleted file mode 100644 index 7e2d4b8ce8..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/PowerToysModuleItemsHelper.cs +++ /dev/null @@ -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 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(); - - 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(); - 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 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); - } -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/WorkspaceItemsHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/WorkspaceItemsHelper.cs deleted file mode 100644 index 865b0ebf65..0000000000 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/WorkspaceItemsHelper.cs +++ /dev/null @@ -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? Applications { get; set; } - } - - internal sealed class WorkspacesData - { - public List? 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(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 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); - } -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj index 6a27e8c08a..9758422da5 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj @@ -56,6 +56,7 @@ + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AwakeModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AwakeModuleCommandProvider.cs new file mode 100644 index 0000000000..65ea125b8e --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AwakeModuleCommandProvider.cs @@ -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 BuildCommands() + { + var title = SettingsDeepLink.SettingsWindow.Awake.ModuleDisplayName(); + var icon = SettingsDeepLink.SettingsWindow.Awake.ModuleIcon(); + + var more = new List + { + 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]; + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ColorPickerModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ColorPickerModuleCommandProvider.cs new file mode 100644 index 0000000000..7eb11172de --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ColorPickerModuleCommandProvider.cs @@ -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 BuildCommands() + { + var title = SettingsDeepLink.SettingsWindow.ColorPicker.ModuleDisplayName(); + var icon = SettingsDeepLink.SettingsWindow.ColorPicker.ModuleIcon(); + + var more = new List + { + 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]; + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/DefaultSettingsModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/DefaultSettingsModuleCommandProvider.cs new file mode 100644 index 0000000000..bdef511bb1 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/DefaultSettingsModuleCommandProvider.cs @@ -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; + +/// +/// Provides open-settings commands for modules without specialized commands. +/// +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 BuildCommands() + { + foreach (var module in Enum.GetValues()) + { + 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(), + }; + } + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ModuleCommandProvider.cs new file mode 100644 index 0000000000..4e06731a2d --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ModuleCommandProvider.cs @@ -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; + +/// +/// Base contract for a PowerToys module to expose its command palette entries. +/// +internal abstract class ModuleCommandProvider +{ + public abstract IEnumerable BuildCommands(); +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/WorkspacesModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/WorkspacesModuleCommandProvider.cs new file mode 100644 index 0000000000..4686a27322 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/WorkspacesModuleCommandProvider.cs @@ -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 BuildCommands() + { + var items = new List(); + + // 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 LoadWorkspaces() + { + var result = WorkspaceService.Instance.GetWorkspacesAsync().GetAwaiter().GetResult(); + return result.Success && result.Value is not null ? result.Value : System.Array.Empty(); + } + + 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(); + } + + var elements = new List(); + foreach (var app in workspace.Applications) + { + var tags = new List(); + + 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); + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/PowerToysListPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/PowerToysListPage.cs index d7163862e9..ddf6616216 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/PowerToysListPage.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/PowerToysListPage.cs @@ -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); } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/WorkspacesListPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/WorkspacesListPage.cs index d5de42f19f..fab4f4f897 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/WorkspacesListPage.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/WorkspacesListPage.cs @@ -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()]; }