From 99706d432405358aec97da1cca5f4d913ff1b70a Mon Sep 17 00:00:00 2001
From: Kai Tao <69313318+vanzue@users.noreply.github.com>
Date: Thu, 19 Mar 2026 13:31:10 +0800
Subject: [PATCH] PowerToys Extension: Fancyzone layout command should be able
to be pinned into dock (#46198)
## Summary of the Pull Request
Currently, the layout do not have id, they can't be pinned into dock
This pr address this and they can be pinned into dock
## PR Checklist
- [ ] Closes: #xxx
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
## Detailed Description of the Pull Request / Additional comments
## Validation Steps Performed
https://github.com/user-attachments/assets/8e7c8b04-663d-4cd3-b26f-d74e46511feb
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---
.../ApplyFancyZonesLayoutCommand.cs | 1 +
.../Helpers/FancyZonesCommandIds.cs | 92 +++++++++++++++++++
.../Helpers/FancyZonesContextHelper.cs | 39 ++++++++
.../Pages/FancyZonesLayoutsPage.cs | 20 +---
.../PowerToysExtensionCommandsProvider.cs | 49 +++++++++-
5 files changed, 181 insertions(+), 20 deletions(-)
create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/FancyZonesCommandIds.cs
create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/FancyZonesContextHelper.cs
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/FancyZones/ApplyFancyZonesLayoutCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/FancyZones/ApplyFancyZonesLayoutCommand.cs
index b4ef1e55c9..7e4b4e6b93 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/FancyZones/ApplyFancyZonesLayoutCommand.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/FancyZones/ApplyFancyZonesLayoutCommand.cs
@@ -16,6 +16,7 @@ internal sealed partial class ApplyFancyZonesLayoutCommand : InvokableCommand
{
_layout = layout;
_targetMonitor = monitor;
+ Id = FancyZonesCommandIds.BuildApplyLayoutCommandId(layout, monitor);
Name = monitor is null ? "Apply to all monitors" : $"Apply to Monitor {monitor.Value.Title}";
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/FancyZonesCommandIds.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/FancyZonesCommandIds.cs
new file mode 100644
index 0000000000..aa9b5c62b6
--- /dev/null
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/FancyZonesCommandIds.cs
@@ -0,0 +1,92 @@
+// 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;
+
+namespace PowerToysExtension.Helpers;
+
+internal static class FancyZonesCommandIds
+{
+ private const string ApplyLayoutPrefix = "com.microsoft.powertoys.fancyzones.layout.apply:";
+ private const string AllMonitorsSuffix = ":all";
+ private const string MonitorMarker = ":monitor:";
+
+ public static string BuildApplyLayoutCommandId(FancyZonesLayoutDescriptor layout, FancyZonesMonitorDescriptor? monitor)
+ {
+ var escapedLayoutId = Uri.EscapeDataString(layout.Id);
+ return monitor is null
+ ? $"{ApplyLayoutPrefix}{escapedLayoutId}{AllMonitorsSuffix}"
+ : $"{ApplyLayoutPrefix}{escapedLayoutId}{MonitorMarker}{Uri.EscapeDataString(GetMonitorToken(monitor.Value))}";
+ }
+
+ public static bool TryParseApplyLayoutCommandId(string id, out string layoutId, out string? monitorToken)
+ {
+ layoutId = string.Empty;
+ monitorToken = null;
+
+ if (string.IsNullOrWhiteSpace(id) || !id.StartsWith(ApplyLayoutPrefix, StringComparison.Ordinal))
+ {
+ return false;
+ }
+
+ var payload = id[ApplyLayoutPrefix.Length..];
+ if (payload.EndsWith(AllMonitorsSuffix, StringComparison.Ordinal))
+ {
+ var layoutPayload = payload[..^AllMonitorsSuffix.Length];
+ if (string.IsNullOrWhiteSpace(layoutPayload))
+ {
+ return false;
+ }
+
+ try
+ {
+ layoutId = Uri.UnescapeDataString(layoutPayload);
+ }
+ catch (ArgumentException)
+ {
+ layoutId = string.Empty;
+ return false;
+ }
+
+ return !string.IsNullOrWhiteSpace(layoutId);
+ }
+
+ var monitorMarkerIndex = payload.IndexOf(MonitorMarker, StringComparison.Ordinal);
+ if (monitorMarkerIndex <= 0 || monitorMarkerIndex == payload.Length - MonitorMarker.Length)
+ {
+ return false;
+ }
+
+ var layoutPart = payload[..monitorMarkerIndex];
+ var monitorPart = payload[(monitorMarkerIndex + MonitorMarker.Length)..];
+ if (string.IsNullOrWhiteSpace(layoutPart) || string.IsNullOrWhiteSpace(monitorPart))
+ {
+ return false;
+ }
+
+ try
+ {
+ layoutId = Uri.UnescapeDataString(layoutPart);
+ monitorToken = Uri.UnescapeDataString(monitorPart);
+ }
+ catch (ArgumentException)
+ {
+ layoutId = string.Empty;
+ monitorToken = null;
+ return false;
+ }
+
+ return !string.IsNullOrWhiteSpace(layoutId) && !string.IsNullOrWhiteSpace(monitorToken);
+ }
+
+ public static string GetMonitorToken(FancyZonesMonitorDescriptor monitor)
+ {
+ if (!string.IsNullOrWhiteSpace(monitor.Data.MonitorInstanceId))
+ {
+ return $"instance:{monitor.Data.MonitorInstanceId}";
+ }
+
+ return $"fallback:{monitor.Data.Monitor}|{monitor.Data.MonitorNumber}";
+ }
+}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/FancyZonesContextHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/FancyZonesContextHelper.cs
new file mode 100644
index 0000000000..c1ea293bd7
--- /dev/null
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/FancyZonesContextHelper.cs
@@ -0,0 +1,39 @@
+// 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 System.Globalization;
+using System.Text;
+using Microsoft.CommandPalette.Extensions;
+using Microsoft.CommandPalette.Extensions.Toolkit;
+using PowerToysExtension.Commands;
+using PowerToysExtension.Properties;
+
+namespace PowerToysExtension.Helpers;
+
+internal static class FancyZonesContextHelper
+{
+ private static readonly CompositeFormat ApplyToMonitorFormat = CompositeFormat.Parse(Resources.FancyZones_ApplyTo_Format);
+
+ public static string FormatApplyToMonitorTitle(FancyZonesMonitorDescriptor monitor)
+ {
+ return string.Format(CultureInfo.CurrentCulture, ApplyToMonitorFormat, monitor.Title);
+ }
+
+ public static IContextItem[] BuildLayoutContext(FancyZonesLayoutDescriptor layout, IReadOnlyList monitors)
+ {
+ var commands = new List(monitors.Count);
+
+ foreach (var monitor in monitors)
+ {
+ commands.Add(new CommandContextItem(new ApplyFancyZonesLayoutCommand(layout, monitor))
+ {
+ Title = FormatApplyToMonitorTitle(monitor),
+ Subtitle = monitor.Subtitle,
+ });
+ }
+
+ return commands.ToArray();
+ }
+}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/FancyZonesLayoutsPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/FancyZonesLayoutsPage.cs
index 897b7fa46a..6bd037726f 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/FancyZonesLayoutsPage.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/FancyZonesLayoutsPage.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions;
@@ -70,7 +69,7 @@ internal sealed partial class FancyZonesLayoutsPage : DynamicListPage
var item = new FancyZonesLayoutListItem(defaultCommand, layout, fallbackIcon)
{
- MoreCommands = BuildLayoutContext(layout, monitors),
+ MoreCommands = FancyZonesContextHelper.BuildLayoutContext(layout, monitors),
};
items.Add(item);
@@ -84,21 +83,4 @@ internal sealed partial class FancyZonesLayoutsPage : DynamicListPage
return Array.Empty();
}
}
-
- private static IContextItem[] BuildLayoutContext(FancyZonesLayoutDescriptor layout, IReadOnlyList monitors)
- {
- var commands = new List(monitors.Count);
-
- for (var i = 0; i < monitors.Count; i++)
- {
- var monitor = monitors[i];
- commands.Add(new CommandContextItem(new ApplyFancyZonesLayoutCommand(layout, monitor))
- {
- Title = string.Format(CultureInfo.CurrentCulture, "Apply to {0}", monitor.Title),
- Subtitle = monitor.Subtitle,
- });
- }
-
- return commands.ToArray();
- }
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/PowerToysExtensionCommandsProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/PowerToysExtensionCommandsProvider.cs
index 4acac7260b..41d47bff18 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/PowerToysExtensionCommandsProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/PowerToysExtensionCommandsProvider.cs
@@ -2,10 +2,14 @@
// 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 PowerToysExtension.Properties;
namespace PowerToysExtension;
@@ -64,11 +68,54 @@ public partial class PowerToysExtensionCommandsProvider : CommandProvider
}
}
- return null;
+ return TryGetFancyZonesCommandItem(id);
}
private void RaiseModuleItemsChanged()
{
RaiseItemsChanged();
}
+
+ private static ICommandItem? TryGetFancyZonesCommandItem(string id)
+ {
+ if (!FancyZonesCommandIds.TryParseApplyLayoutCommandId(id, out var layoutId, out var monitorToken))
+ {
+ return null;
+ }
+
+ var layout = FancyZonesDataService.GetLayouts()
+ .FirstOrDefault(candidate => string.Equals(candidate.Id, layoutId, StringComparison.Ordinal));
+ if (layout is null)
+ {
+ return null;
+ }
+
+ var fallbackIcon = PowerToysResourcesHelper.IconFromSettingsIcon("FancyZones.png");
+ if (string.IsNullOrWhiteSpace(monitorToken))
+ {
+ FancyZonesDataService.TryGetMonitors(out var monitors, out _);
+ return new FancyZonesLayoutListItem(new ApplyFancyZonesLayoutCommand(layout, monitor: null), layout, fallbackIcon)
+ {
+ MoreCommands = FancyZonesContextHelper.BuildLayoutContext(layout, monitors),
+ };
+ }
+
+ if (!FancyZonesDataService.TryGetMonitors(out var availableMonitors, out _))
+ {
+ return null;
+ }
+
+ var monitor = availableMonitors
+ .FirstOrDefault(candidate => string.Equals(FancyZonesCommandIds.GetMonitorToken(candidate), monitorToken, StringComparison.Ordinal));
+
+ if (monitor.Equals(default(FancyZonesMonitorDescriptor)))
+ {
+ return null;
+ }
+
+ return new FancyZonesLayoutListItem(new ApplyFancyZonesLayoutCommand(layout, monitor), layout, fallbackIcon)
+ {
+ Subtitle = FancyZonesContextHelper.FormatApplyToMonitorTitle(monitor),
+ };
+ }
}