PowerToys Extension: Fancyzone layout command should be able to be pinned into dock (#46198)

<!-- Enter a brief description/summary of your PR here. What does it
fix/what does it change/how was it tested (even manually, if necessary)?
-->
## Summary of the Pull Request
Currently, the layout do not have id, they can't be pinned into dock
<img width="1182" height="711" alt="image"
src="https://github.com/user-attachments/assets/67461267-4bed-4c07-99ff-7311c368ad09"
/>

This pr address this and they can be pinned into dock

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] Closes: #xxx
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **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

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## 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>
This commit is contained in:
Kai Tao
2026-03-19 13:31:10 +08:00
committed by GitHub
parent ff194c0b5f
commit 99706d4324
5 changed files with 181 additions and 20 deletions

View File

@@ -16,6 +16,7 @@ internal sealed partial class ApplyFancyZonesLayoutCommand : InvokableCommand
{ {
_layout = layout; _layout = layout;
_targetMonitor = monitor; _targetMonitor = monitor;
Id = FancyZonesCommandIds.BuildApplyLayoutCommandId(layout, monitor);
Name = monitor is null ? "Apply to all monitors" : $"Apply to Monitor {monitor.Value.Title}"; Name = monitor is null ? "Apply to all monitors" : $"Apply to Monitor {monitor.Value.Title}";

View File

@@ -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}";
}
}

View File

@@ -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<FancyZonesMonitorDescriptor> monitors)
{
var commands = new List<IContextItem>(monitors.Count);
foreach (var monitor in monitors)
{
commands.Add(new CommandContextItem(new ApplyFancyZonesLayoutCommand(layout, monitor))
{
Title = FormatApplyToMonitorTitle(monitor),
Subtitle = monitor.Subtitle,
});
}
return commands.ToArray();
}
}

View File

@@ -4,7 +4,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions;
@@ -70,7 +69,7 @@ internal sealed partial class FancyZonesLayoutsPage : DynamicListPage
var item = new FancyZonesLayoutListItem(defaultCommand, layout, fallbackIcon) var item = new FancyZonesLayoutListItem(defaultCommand, layout, fallbackIcon)
{ {
MoreCommands = BuildLayoutContext(layout, monitors), MoreCommands = FancyZonesContextHelper.BuildLayoutContext(layout, monitors),
}; };
items.Add(item); items.Add(item);
@@ -84,21 +83,4 @@ internal sealed partial class FancyZonesLayoutsPage : DynamicListPage
return Array.Empty<IListItem>(); return Array.Empty<IListItem>();
} }
} }
private static IContextItem[] BuildLayoutContext(FancyZonesLayoutDescriptor layout, IReadOnlyList<FancyZonesMonitorDescriptor> monitors)
{
var commands = new List<IContextItem>(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();
}
} }

View File

@@ -2,10 +2,14 @@
// The Microsoft Corporation licenses this file to you under the MIT license. // The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit; using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers; using PowerToysExtension.Helpers;
using PowerToysExtension.Pages;
using PowerToysExtension.Properties; using PowerToysExtension.Properties;
namespace PowerToysExtension; namespace PowerToysExtension;
@@ -64,11 +68,54 @@ public partial class PowerToysExtensionCommandsProvider : CommandProvider
} }
} }
return null; return TryGetFancyZonesCommandItem(id);
} }
private void RaiseModuleItemsChanged() private void RaiseModuleItemsChanged()
{ {
RaiseItemsChanged(); 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),
};
}
} }