From 16733718c3930df5ee616347206c6027fe33ef05 Mon Sep 17 00:00:00 2001 From: "Shawn Yuan (from Dev Box)" Date: Mon, 24 Nov 2025 11:22:54 +0800 Subject: [PATCH] add status sync Signed-off-by: Shawn Yuan (from Dev Box) --- .../ViewModels/AllAppsViewModel.cs | 12 ++++ .../ViewModels/LauncherViewModel.cs | 12 ++++ .../Interfaces/ISettingsRepository`1.cs | 4 ++ .../SettingsRepository`1.cs | 72 ++++++++++++++++++- .../BackCompatTestProperties.cs | 5 ++ .../ViewModels/DashboardViewModel.cs | 11 +++ 6 files changed, 113 insertions(+), 3 deletions(-) diff --git a/src/settings-ui/QuickAccess.UI/ViewModels/AllAppsViewModel.cs b/src/settings-ui/QuickAccess.UI/ViewModels/AllAppsViewModel.cs index 7db99a50e7..09b352b4f6 100644 --- a/src/settings-ui/QuickAccess.UI/ViewModels/AllAppsViewModel.cs +++ b/src/settings-ui/QuickAccess.UI/ViewModels/AllAppsViewModel.cs @@ -11,6 +11,7 @@ using Microsoft.PowerToys.QuickAccess.Services; using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.PowerToys.Settings.UI.Library.Helpers; using Microsoft.PowerToys.Settings.UI.Library.Interfaces; +using Microsoft.UI.Dispatching; using Microsoft.Windows.ApplicationModel.Resources; namespace Microsoft.PowerToys.QuickAccess.ViewModels; @@ -20,6 +21,7 @@ public sealed class AllAppsViewModel : Observable private readonly IQuickAccessCoordinator _coordinator; private readonly ISettingsRepository _settingsRepository; private readonly ResourceLoader _resourceLoader; + private readonly DispatcherQueue _dispatcherQueue; private GeneralSettings _generalSettings; public ObservableCollection FlyoutMenuItems { get; } @@ -27,10 +29,12 @@ public sealed class AllAppsViewModel : Observable public AllAppsViewModel(IQuickAccessCoordinator coordinator) { _coordinator = coordinator; + _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); var settingsUtils = new SettingsUtils(); _settingsRepository = SettingsRepository.GetInstance(settingsUtils); _generalSettings = _settingsRepository.SettingsConfig; _generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChangedOnSettingsPage); + _settingsRepository.SettingsChanged += OnSettingsChanged; _resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader; FlyoutMenuItems = new ObservableCollection(); @@ -41,6 +45,14 @@ public sealed class AllAppsViewModel : Observable } } + private void OnSettingsChanged(GeneralSettings newSettings) + { + _dispatcherQueue.TryEnqueue(() => + { + ModuleEnabledChangedOnSettingsPage(); + }); + } + private void AddFlyoutMenuItem(ModuleType moduleType) { var gpo = ModuleHelper.GetModuleGpoConfiguration(moduleType); diff --git a/src/settings-ui/QuickAccess.UI/ViewModels/LauncherViewModel.cs b/src/settings-ui/QuickAccess.UI/ViewModels/LauncherViewModel.cs index 8d34312c3d..36a96a5de8 100644 --- a/src/settings-ui/QuickAccess.UI/ViewModels/LauncherViewModel.cs +++ b/src/settings-ui/QuickAccess.UI/ViewModels/LauncherViewModel.cs @@ -11,6 +11,7 @@ using Microsoft.PowerToys.QuickAccess.Services; using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.PowerToys.Settings.UI.Library.Helpers; using Microsoft.PowerToys.Settings.UI.Library.Interfaces; +using Microsoft.UI.Dispatching; using Microsoft.Windows.ApplicationModel.Resources; namespace Microsoft.PowerToys.QuickAccess.ViewModels; @@ -20,6 +21,7 @@ public sealed class LauncherViewModel : Observable private readonly IQuickAccessCoordinator _coordinator; private readonly ISettingsRepository _settingsRepository; private readonly ResourceLoader _resourceLoader; + private readonly DispatcherQueue _dispatcherQueue; private GeneralSettings _generalSettings; public ObservableCollection FlyoutMenuItems { get; } @@ -29,10 +31,12 @@ public sealed class LauncherViewModel : Observable public LauncherViewModel(IQuickAccessCoordinator coordinator) { _coordinator = coordinator; + _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); var settingsUtils = new SettingsUtils(); _settingsRepository = SettingsRepository.GetInstance(settingsUtils); _generalSettings = _settingsRepository.SettingsConfig; _generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChanged); + _settingsRepository.SettingsChanged += OnSettingsChanged; _resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader; FlyoutMenuItems = new ObservableCollection(); @@ -53,6 +57,14 @@ public sealed class LauncherViewModel : Observable IsUpdateAvailable = updatingSettings.State is UpdatingSettings.UpdatingState.ReadyToInstall or UpdatingSettings.UpdatingState.ReadyToDownload; } + private void OnSettingsChanged(GeneralSettings newSettings) + { + _dispatcherQueue.TryEnqueue(() => + { + ModuleEnabledChanged(); + }); + } + private void AddFlyoutMenuItem(ModuleType moduleType) { if (ModuleHelper.GetModuleGpoConfiguration(moduleType) == GpoRuleConfigured.Disabled) diff --git a/src/settings-ui/Settings.UI.Library/Interfaces/ISettingsRepository`1.cs b/src/settings-ui/Settings.UI.Library/Interfaces/ISettingsRepository`1.cs index a9cd92899a..32cb5a40f8 100644 --- a/src/settings-ui/Settings.UI.Library/Interfaces/ISettingsRepository`1.cs +++ b/src/settings-ui/Settings.UI.Library/Interfaces/ISettingsRepository`1.cs @@ -2,6 +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; + namespace Microsoft.PowerToys.Settings.UI.Library.Interfaces { public interface ISettingsRepository @@ -9,5 +11,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Interfaces T SettingsConfig { get; set; } bool ReloadSettings(); + + event Action SettingsChanged; } } diff --git a/src/settings-ui/Settings.UI.Library/SettingsRepository`1.cs b/src/settings-ui/Settings.UI.Library/SettingsRepository`1.cs index 136b92b63a..f55a9cc3f5 100644 --- a/src/settings-ui/Settings.UI.Library/SettingsRepository`1.cs +++ b/src/settings-ui/Settings.UI.Library/SettingsRepository`1.cs @@ -3,15 +3,16 @@ // See the LICENSE file in the project root for more information. using System; +using System.IO; using System.Threading; - +using ManagedCommon; using Microsoft.PowerToys.Settings.UI.Library.Interfaces; namespace Microsoft.PowerToys.Settings.UI.Library { // This Singleton class is a wrapper around the settings configurations that are accessed by viewmodels. // This class can have only one instance and therefore the settings configurations are common to all. - public class SettingsRepository : ISettingsRepository + public sealed class SettingsRepository : ISettingsRepository, IDisposable where T : class, ISettingsConfig, new() { private static readonly Lock _SettingsRepoLock = new Lock(); @@ -22,6 +23,10 @@ namespace Microsoft.PowerToys.Settings.UI.Library private T settingsConfig; + private FileSystemWatcher _watcher; + + public event Action SettingsChanged; + // Suppressing the warning as this is a singleton class and this method is // necessarily static #pragma warning disable CA1000 // Do not declare static members on generic types @@ -35,6 +40,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library { settingsRepository = new SettingsRepository(); _settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils)); + settingsRepository.InitializeWatcher(); } return settingsRepository; @@ -46,12 +52,51 @@ namespace Microsoft.PowerToys.Settings.UI.Library { } + private void InitializeWatcher() + { + try + { + var settingsItem = new T(); + var filePath = _settingsUtils.GetSettingsFilePath(settingsItem.GetModuleName()); + var directory = Path.GetDirectoryName(filePath); + var fileName = Path.GetFileName(filePath); + + if (!Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + _watcher = new FileSystemWatcher(directory, fileName); + _watcher.NotifyFilter = NotifyFilters.LastWrite; + _watcher.Changed += Watcher_Changed; + _watcher.EnableRaisingEvents = true; + } + catch (Exception ex) + { + Logger.LogError($"Failed to initialize settings watcher for {typeof(T).Name}", ex); + } + } + + private void Watcher_Changed(object sender, FileSystemEventArgs e) + { + // Wait a bit for the file write to complete and retry if needed + for (int i = 0; i < 5; i++) + { + Thread.Sleep(100); + if (ReloadSettings()) + { + SettingsChanged?.Invoke(SettingsConfig); + return; + } + } + } + public bool ReloadSettings() { try { T settingsItem = new T(); - settingsConfig = _settingsUtils.GetSettingsOrDefault(settingsItem.GetModuleName()); + settingsConfig = _settingsUtils.GetSettings(settingsItem.GetModuleName()); SettingsConfig = settingsConfig; @@ -85,5 +130,26 @@ namespace Microsoft.PowerToys.Settings.UI.Library } } } + + public void StopWatching() + { + if (_watcher != null) + { + _watcher.EnableRaisingEvents = false; + } + } + + public void StartWatching() + { + if (_watcher != null) + { + _watcher.EnableRaisingEvents = true; + } + } + + public void Dispose() + { + _watcher?.Dispose(); + } } } diff --git a/src/settings-ui/Settings.UI.UnitTests/BackwardsCompatibility/BackCompatTestProperties.cs b/src/settings-ui/Settings.UI.UnitTests/BackwardsCompatibility/BackCompatTestProperties.cs index b2048fa573..5f2dc8e9f2 100644 --- a/src/settings-ui/Settings.UI.UnitTests/BackwardsCompatibility/BackCompatTestProperties.cs +++ b/src/settings-ui/Settings.UI.UnitTests/BackwardsCompatibility/BackCompatTestProperties.cs @@ -30,6 +30,11 @@ namespace Microsoft.PowerToys.Settings.UI.UnitTests.BackwardsCompatibility private readonly ISettingsUtils _settingsUtils; private T _settingsConfig; + // Implements ISettingsRepository.SettingsChanged +#pragma warning disable CS0067 + public event System.Action SettingsChanged; +#pragma warning restore CS0067 + public MockSettingsRepository(ISettingsUtils settingsUtils) { _settingsUtils = settingsUtils; diff --git a/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs index 56437cd9f3..ff74996adb 100644 --- a/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs @@ -89,6 +89,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels _settingsRepository = settingsRepository; generalSettingsConfig = settingsRepository.SettingsConfig; generalSettingsConfig.AddEnabledModuleChangeNotification(ModuleEnabledChangedOnSettingsPage); + _settingsRepository.SettingsChanged += OnSettingsChanged; // Initialize dashboard sort order from settings _dashboardSortOrder = generalSettingsConfig.DashboardSortOrder; @@ -100,6 +101,16 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels GetShortcutModules(); } + private void OnSettingsChanged(GeneralSettings newSettings) + { + dispatcher.BeginInvoke(() => + { + generalSettingsConfig = newSettings; + generalSettingsConfig.AddEnabledModuleChangeNotification(ModuleEnabledChangedOnSettingsPage); + ModuleEnabledChangedOnSettingsPage(); + }); + } + protected override void OnConflictsUpdated(object sender, AllHotkeyConflictsEventArgs e) { dispatcher.BeginInvoke(() =>