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);