From e148a8928869f38c335bd67aed6893eaf64c2548 Mon Sep 17 00:00:00 2001
From: Kai Tao <69313318+vanzue@users.noreply.github.com>
Date: Fri, 16 Jan 2026 10:40:32 +0800
Subject: [PATCH] Powertoys Extension: Trigger latest layout/monitor refresh
for fancyzone in cmdpal extension (#44756)
## Summary of the Pull Request
Best effort trigger refresh for the monitor/layout refresh for
fancyzones, user may experience error for first time run but will get
correct result in next visit.
This bring better experience than the always-stale-state, while still
remain performant command.
## 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
Validated locally that second time visit will bring correct monitor when
plug/unplug monitor
---
.../Helpers/FancyZonesDataService.cs | 34 +++++++++++++------
.../Helpers/FancyZonesNotifier.cs | 12 +++++++
2 files changed, 36 insertions(+), 10 deletions(-)
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/FancyZonesDataService.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/FancyZonesDataService.cs
index 639128d7f7..d81a6f9e11 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/FancyZonesDataService.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/FancyZonesDataService.cs
@@ -41,16 +41,13 @@ internal static class FancyZonesDataService
try
{
- if (!File.Exists(FZPaths.EditorParameters))
- {
- error = Resources.FancyZones_MonitorDataNotFound;
- Logger.LogWarning($"TryGetMonitors: File not found. Path={FZPaths.EditorParameters}");
- return false;
- }
-
- Logger.LogInfo($"TryGetMonitors: File exists, reading...");
- var editorParams = FancyZonesDataIO.ReadEditorParameters();
- Logger.LogInfo($"TryGetMonitors: ReadEditorParameters returned. Monitors={editorParams.Monitors?.Count ?? -1}");
+ // Request FancyZones to save current monitor configuration.
+ // The editor-parameters.json file is only written when:
+ // 1. Opening the FancyZones Editor
+ // 2. Receiving the WM_PRIV_SAVE_EDITOR_PARAMETERS message
+ // Without this, monitor changes (plug/unplug) won't be reflected in the file.
+ var editorParams = ReadEditorParametersWithRefresh();
+ Logger.LogInfo($"TryGetMonitors: ReadEditorParametersWithRefreshWithRefresh returned. Monitors={editorParams.Monitors?.Count ?? -1}");
var editorMonitors = editorParams.Monitors;
if (editorMonitors is null || editorMonitors.Count == 0)
@@ -74,6 +71,23 @@ internal static class FancyZonesDataService
}
}
+ ///
+ /// Requests FancyZones to save the current monitor configuration and reads the file.
+ /// This is a best-effort approach for performance: we send the save request and immediately
+ /// read the file without waiting. If the file hasn't been updated yet, the next call will
+ /// see the updated data since FancyZones processes the message asynchronously.
+ ///
+ private static EditorParameters.ParamsWrapper ReadEditorParametersWithRefresh()
+ {
+ // Request FancyZones to save the current monitor configuration.
+ // This is fire-and-forget for performance - we don't wait for the save to complete.
+ // If this is the first call after a monitor change, we may read stale data, but the
+ // next call will see the updated file since FancyZones will have processed the message.
+ FancyZonesNotifier.NotifySaveEditorParameters();
+
+ return FancyZonesDataIO.ReadEditorParameters();
+ }
+
public static IReadOnlyList GetLayouts()
{
Logger.LogInfo($"GetLayouts: Starting. LayoutTemplatesPath={FZPaths.LayoutTemplates} CustomLayoutsPath={FZPaths.CustomLayouts}");
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/FancyZonesNotifier.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/FancyZonesNotifier.cs
index ada8ee7b17..256331a951 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/FancyZonesNotifier.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/FancyZonesNotifier.cs
@@ -10,13 +10,25 @@ namespace PowerToysExtension.Helpers;
internal static class FancyZonesNotifier
{
private const string AppliedLayoutsFileUpdateMessage = "{2ef2c8a7-e0d5-4f31-9ede-52aade2d284d}";
+ private const string SaveEditorParametersMessage = "{d8f9c0e3-5d77-4e83-8a4f-7c704c2bfb4a}";
+
private static readonly uint WmPrivAppliedLayoutsFileUpdate = RegisterWindowMessageW(AppliedLayoutsFileUpdateMessage);
+ private static readonly uint WmPrivSaveEditorParameters = RegisterWindowMessageW(SaveEditorParametersMessage);
public static void NotifyAppliedLayoutsChanged()
{
_ = PostMessageW(new IntPtr(0xFFFF), WmPrivAppliedLayoutsFileUpdate, UIntPtr.Zero, IntPtr.Zero);
}
+ ///
+ /// Notifies FancyZones to save the current monitor configuration to editor-parameters.json.
+ /// This is needed because FancyZones only writes this file when opening the editor or when explicitly requested.
+ ///
+ public static void NotifySaveEditorParameters()
+ {
+ _ = PostMessageW(new IntPtr(0xFFFF), WmPrivSaveEditorParameters, UIntPtr.Zero, IntPtr.Zero);
+ }
+
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern uint RegisterWindowMessageW(string lpString);