diff --git a/PowerToys.slnx b/PowerToys.slnx
index 30dd1df814..57ac4fff86 100644
--- a/PowerToys.slnx
+++ b/PowerToys.slnx
@@ -369,6 +369,10 @@
+
+
+
+
diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Models/AppStateModel.cs b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Models/AppStateModel.cs
index b0ece6267b..4a89d4ce69 100644
--- a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Models/AppStateModel.cs
+++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Models/AppStateModel.cs
@@ -2,57 +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 CommunityToolkit.Mvvm.ComponentModel;
-using Microsoft.CommandPalette.UI.Models;
-using Microsoft.Extensions.Logging;
-using Windows.Foundation;
+namespace Microsoft.CommandPalette.UI.Models;
-namespace Microsoft.CmdPal.UI.ViewModels;
-
-public partial class AppStateModel : ObservableObject
+public partial class AppStateModel
{
- private static string _filePath;
+ /*************************************************************************
+ * Make sure that you make the setters public (JsonSerializer.Deserialize will fail silently otherwise)!
+ * Make sure that any new types you add are added to JsonSerializationContext!
+ *************************************************************************/
- public event TypedEventHandler? StateChanged;
-
- ///////////////////////////////////////////////////////////////////////////
- // STATE HERE
- // Make sure that you make the setters public (JsonSerializer.Deserialize will fail silently otherwise)!
- // Make sure that any new types you add are added to JsonSerializationContext!
public List RunHistory { get; set; } = [];
-
- // END STATE
- ///////////////////////////////////////////////////////////////////////////
-
- static AppStateModel()
- {
- _filePath = PersistenceService.SettingsJsonPath("state.json");
- }
-
- public static AppStateModel LoadState(ILogger logger)
- {
- return PersistenceService.LoadObject(_filePath, JsonSerializationContext.Default.AppStateModel!, logger);
- }
-
- public static void SaveState(AppStateModel model, ILogger logger)
- {
- try
- {
- PersistenceService.SaveObject(
- model,
- _filePath,
- JsonSerializationContext.Default.AppStateModel!,
- JsonSerializationContext.Default.AppStateModel!.Options,
- beforeWriteMutation: null,
- afterWriteCallback: m => m.StateChanged?.Invoke(m, null),
- logger);
- }
- catch (Exception ex)
- {
- Log_SaveStateFailure(logger, _filePath, ex);
- }
- }
-
- [LoggerMessage(Level = LogLevel.Error, Message = "Failed to save application state to '{filePath}'.")]
- static partial void Log_SaveStateFailure(ILogger logger, string filePath, Exception exception);
}
diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Models/SettingsModel.cs b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Models/SettingsModel.cs
index 1ccbe05f37..70ec6c21d2 100644
--- a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Models/SettingsModel.cs
+++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Models/SettingsModel.cs
@@ -2,27 +2,15 @@
// 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.Text.Json;
-using System.Text.Json.Nodes;
-using System.Text.Json.Serialization;
-using System.Text.Json.Serialization.Metadata;
-using CommunityToolkit.Mvvm.ComponentModel;
-using Microsoft.Extensions.Logging;
-using Windows.Foundation;
-
namespace Microsoft.CommandPalette.UI.Models;
-public partial class SettingsModel : ObservableObject
+public partial class SettingsModel
{
- private const string DeprecatedHotkeyGoesHomeKey = "HotkeyGoesHome";
+ /*************************************************************************
+ * Make sure that you make the setters public (JsonSerializer.Deserialize will fail silently otherwise)!
+ * Make sure that any new types you add are added to JsonSerializationContext!
+ *************************************************************************/
- [JsonIgnore]
- private static readonly string _filePath;
-
- public event TypedEventHandler? SettingsChanged;
-
- ///////////////////////////////////////////////////////////////////////////
- // SETTINGS HERE
public static HotkeySettings DefaultActivationShortcut { get; } = new HotkeySettings(true, false, true, false, 0x20); // win+alt+space
public HotkeySettings? Hotkey { get; set; } = DefaultActivationShortcut;
@@ -52,97 +40,4 @@ public partial class SettingsModel : ObservableObject
public WindowPosition? LastWindowPosition { get; set; }
public TimeSpan AutoGoHomeInterval { get; set; } = Timeout.InfiniteTimeSpan;
-
- // END SETTINGS
- ///////////////////////////////////////////////////////////////////////////
-
- static SettingsModel()
- {
- _filePath = PersistenceService.SettingsJsonPath("settings.json");
- }
-
- private static bool ApplyMigrations(JsonObject root, SettingsModel model, ILogger logger)
- {
- var migrated = false;
-
- migrated |= TryMigrate(
- "Migration #1: HotkeyGoesHome (bool) -> AutoGoHomeInterval (TimeSpan)",
- root,
- model,
- nameof(AutoGoHomeInterval),
- DeprecatedHotkeyGoesHomeKey,
- (settingsModel, goesHome) => settingsModel.AutoGoHomeInterval = goesHome ? TimeSpan.Zero : Timeout.InfiniteTimeSpan,
- JsonSerializationContext.Default.Boolean,
- logger);
-
- return migrated;
- }
-
- private static bool TryMigrate(string migrationName, JsonObject root, SettingsModel model, string newKey, string oldKey, Action apply, JsonTypeInfo jsonTypeInfo, ILogger logger)
- {
- try
- {
- if (root.ContainsKey(newKey) && root[newKey] is not null)
- {
- return false;
- }
-
- if (root.TryGetPropertyValue(oldKey, out var oldNode) && oldNode is not null)
- {
- var value = oldNode.Deserialize(jsonTypeInfo);
- apply(model, value!);
- return true;
- }
- }
- catch (Exception ex)
- {
- Log_MigrationFailure(logger, migrationName, ex);
- }
-
- return false;
- }
-
- public static SettingsModel LoadSettings(ILogger logger)
- {
- var settings = PersistenceService.LoadObject(_filePath, JsonSerializationContext.Default.SettingsModel!, logger);
-
- var migratedAny = false;
- try
- {
- var jsonContent = File.Exists(_filePath) ? File.ReadAllText(_filePath) : "{}";
- if (JsonNode.Parse(jsonContent) is JsonObject root)
- {
- migratedAny |= ApplyMigrations(root, settings, logger);
- }
- }
- catch (Exception ex)
- {
- Log_MigrationCheckFailure(logger, ex);
- }
-
- if (migratedAny)
- {
- SaveSettings(settings, logger);
- }
-
- return settings;
- }
-
- public static void SaveSettings(SettingsModel model, ILogger logger)
- {
- PersistenceService.SaveObject(
- model,
- _filePath,
- JsonSerializationContext.Default.SettingsModel,
- JsonSerializationContext.Default.Options,
- beforeWriteMutation: obj => obj.Remove(DeprecatedHotkeyGoesHomeKey),
- afterWriteCallback: m => m.SettingsChanged?.Invoke(m, null),
- logger);
- }
-
- [LoggerMessage(Level = LogLevel.Error, Message = "Settings migration '{MigrationName}' failed.")]
- static partial void Log_MigrationFailure(ILogger logger, string MigrationName, Exception exception);
-
- [LoggerMessage(Level = LogLevel.Error, Message = "Settings migration check failed.")]
- static partial void Log_MigrationCheckFailure(ILogger logger, Exception exception);
}
diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/AppStateService.cs b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/AppStateService.cs
new file mode 100644
index 0000000000..f377aa8fb2
--- /dev/null
+++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/AppStateService.cs
@@ -0,0 +1,50 @@
+// 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 Microsoft.CommandPalette.UI.Models;
+using Microsoft.Extensions.Logging;
+using Windows.Foundation;
+
+namespace Microsoft.CommandPalette.UI.Services;
+
+public partial class AppStateService
+{
+ private readonly ILogger logger;
+ private readonly string _filePath;
+ private AppStateModel _appStateModel;
+
+ public event TypedEventHandler? StateChanged;
+
+ public AppStateModel CurrentSettings => _appStateModel;
+
+ public AppStateService(ILogger logger)
+ {
+ this.logger = logger;
+ _filePath = PersistenceService.SettingsJsonPath("state.json");
+ _appStateModel = LoadState();
+ }
+
+ private AppStateModel LoadState()
+ {
+ return PersistenceService.LoadObject(_filePath, JsonSerializationContext.Default.AppStateModel!, logger);
+ }
+
+ public void SaveSettings(AppStateModel model)
+ {
+ PersistenceService.SaveObject(
+ model,
+ _filePath,
+ JsonSerializationContext.Default.AppStateModel,
+ JsonSerializationContext.Default.Options,
+ null,
+ afterWriteCallback: m => FinalizeStateSave(m),
+ logger);
+ }
+
+ private void FinalizeStateSave(AppStateModel model)
+ {
+ _appStateModel = model;
+ StateChanged?.Invoke(model, null);
+ }
+}
diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/CmdPalLogger.cs b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/CmdPalLogger.cs
index 36a204b2cb..775191c8b8 100644
--- a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/CmdPalLogger.cs
+++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/CmdPalLogger.cs
@@ -9,7 +9,7 @@ namespace Microsoft.CommandPalette.UI.Services;
// Adapter implementing Microsoft.Extensions.Logging.ILogger,
// delegating to ManagedCommon.Logger.
-internal sealed partial class CmdPalLogger : ILogger
+public sealed partial class CmdPalLogger : ILogger
{
private static readonly AsyncLocal> _scopeStack = new();
private readonly LogLevel _minLevel;
diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/Extensions/BuiltInExtensionService.cs b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/Extensions/BuiltInExtensionService.cs
new file mode 100644
index 0000000000..f7062721ff
--- /dev/null
+++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/Extensions/BuiltInExtensionService.cs
@@ -0,0 +1,9 @@
+// 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.
+
+namespace Microsoft.CommandPalette.UI.Services.Extensions;
+
+internal class BuiltInExtensionService : IExtensionService
+{
+}
diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/Extensions/IExtensionService.cs b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/Extensions/IExtensionService.cs
new file mode 100644
index 0000000000..4ea9d89c55
--- /dev/null
+++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/Extensions/IExtensionService.cs
@@ -0,0 +1,9 @@
+// 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.
+
+namespace Microsoft.CommandPalette.UI.Services.Extensions;
+
+internal interface IExtensionService
+{
+}
diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/Extensions/WinRTExtensionService.cs b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/Extensions/WinRTExtensionService.cs
new file mode 100644
index 0000000000..4b3efc35e2
--- /dev/null
+++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/Extensions/WinRTExtensionService.cs
@@ -0,0 +1,9 @@
+// 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.
+
+namespace Microsoft.CommandPalette.UI.Services.Extensions;
+
+internal class WinRTExtensionService : IExtensionService
+{
+}
diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Models/Helpers/ResourceLoaderInstance.cs b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/Helpers/ResourceLoaderInstance.cs
similarity index 90%
rename from src/modules/Deux/UI/Microsoft.CommandPalette.UI.Models/Helpers/ResourceLoaderInstance.cs
rename to src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/Helpers/ResourceLoaderInstance.cs
index 11bed01b92..c9eaf59f9b 100644
--- a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Models/Helpers/ResourceLoaderInstance.cs
+++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/Helpers/ResourceLoaderInstance.cs
@@ -4,7 +4,7 @@
using Microsoft.Windows.ApplicationModel.Resources;
-namespace Microsoft.CommandPalette.UI.Models.Helpers;
+namespace Microsoft.CommandPalette.UI.Services.Helpers;
public static class ResourceLoaderInstance
{
diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Models/JsonSerializationContext.cs b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/JsonSerializationContext.cs
similarity index 92%
rename from src/modules/Deux/UI/Microsoft.CommandPalette.UI.Models/JsonSerializationContext.cs
rename to src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/JsonSerializationContext.cs
index 0d5a2aa646..d47303e1f6 100644
--- a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Models/JsonSerializationContext.cs
+++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/JsonSerializationContext.cs
@@ -3,9 +3,9 @@
// See the LICENSE file in the project root for more information.
using System.Text.Json.Serialization;
-using Microsoft.CmdPal.UI.ViewModels;
+using Microsoft.CommandPalette.UI.Models;
-namespace Microsoft.CommandPalette.UI.Models;
+namespace Microsoft.CommandPalette.UI.Services;
[JsonSerializable(typeof(float))]
[JsonSerializable(typeof(int))]
diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/Microsoft.CommandPalette.UI.Services.csproj b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/Microsoft.CommandPalette.UI.Services.csproj
index 4739ff7fc4..d357131640 100644
--- a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/Microsoft.CommandPalette.UI.Services.csproj
+++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/Microsoft.CommandPalette.UI.Services.csproj
@@ -29,6 +29,7 @@
+
\ No newline at end of file
diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Models/Services/PersistenceService.cs b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/PersistenceService.cs
similarity index 99%
rename from src/modules/Deux/UI/Microsoft.CommandPalette.UI.Models/Services/PersistenceService.cs
rename to src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/PersistenceService.cs
index 083f51045d..138e32f166 100644
--- a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Models/Services/PersistenceService.cs
+++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/PersistenceService.cs
@@ -9,7 +9,7 @@ using System.Text.Json.Serialization.Metadata;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Extensions.Logging;
-namespace Microsoft.CommandPalette.UI.Models;
+namespace Microsoft.CommandPalette.UI.Services;
public partial class PersistenceService
{
diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/SettingsService.cs b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/SettingsService.cs
new file mode 100644
index 0000000000..658b4a5c45
--- /dev/null
+++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/SettingsService.cs
@@ -0,0 +1,121 @@
+// 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.Text.Json;
+using System.Text.Json.Nodes;
+using System.Text.Json.Serialization.Metadata;
+using Microsoft.CommandPalette.UI.Models;
+using Microsoft.Extensions.Logging;
+using Windows.Foundation;
+
+namespace Microsoft.CommandPalette.UI.Services;
+
+public partial class SettingsService
+{
+ private readonly ILogger logger;
+ private readonly string _filePath;
+ private const string DeprecatedHotkeyGoesHomeKey = "HotkeyGoesHome";
+ private SettingsModel _settingsModel;
+
+ public event TypedEventHandler? SettingsChanged;
+
+ public SettingsModel CurrentSettings => _settingsModel;
+
+ public SettingsService(ILogger logger)
+ {
+ this.logger = logger;
+ _filePath = PersistenceService.SettingsJsonPath("settings.json");
+ _settingsModel = LoadSettings();
+ }
+
+ private SettingsModel LoadSettings()
+ {
+ var settings = PersistenceService.LoadObject(_filePath, JsonSerializationContext.Default.SettingsModel!, logger);
+
+ var migratedAny = false;
+ try
+ {
+ var jsonContent = File.Exists(_filePath) ? File.ReadAllText(_filePath) : "{}";
+ if (JsonNode.Parse(jsonContent) is JsonObject root)
+ {
+ migratedAny |= ApplyMigrations(root, settings);
+ }
+ }
+ catch (Exception ex)
+ {
+ Log_MigrationCheckFailure(ex);
+ }
+
+ if (migratedAny)
+ {
+ SaveSettings(settings);
+ }
+
+ return settings;
+ }
+
+ public void SaveSettings(SettingsModel model)
+ {
+ PersistenceService.SaveObject(
+ model,
+ _filePath,
+ JsonSerializationContext.Default.SettingsModel,
+ JsonSerializationContext.Default.Options,
+ beforeWriteMutation: obj => obj.Remove(DeprecatedHotkeyGoesHomeKey),
+ afterWriteCallback: m => FinalizeSettingsSave(m),
+ logger);
+ }
+
+ private void FinalizeSettingsSave(SettingsModel model)
+ {
+ _settingsModel = model;
+ SettingsChanged?.Invoke(model, null);
+ }
+
+ private bool ApplyMigrations(JsonObject root, SettingsModel model)
+ {
+ var migrated = false;
+
+ migrated |= TryMigrate(
+ "Migration #1: HotkeyGoesHome (bool) -> AutoGoHomeInterval (TimeSpan)",
+ root,
+ model,
+ nameof(SettingsModel.AutoGoHomeInterval),
+ DeprecatedHotkeyGoesHomeKey,
+ (settingsModel, goesHome) => settingsModel.AutoGoHomeInterval = goesHome ? TimeSpan.Zero : Timeout.InfiniteTimeSpan,
+ JsonSerializationContext.Default.Boolean);
+
+ return migrated;
+ }
+
+ private bool TryMigrate(string migrationName, JsonObject root, SettingsModel model, string newKey, string oldKey, Action apply, JsonTypeInfo jsonTypeInfo)
+ {
+ try
+ {
+ if (root.ContainsKey(newKey) && root[newKey] is not null)
+ {
+ return false;
+ }
+
+ if (root.TryGetPropertyValue(oldKey, out var oldNode) && oldNode is not null)
+ {
+ var value = oldNode.Deserialize(jsonTypeInfo);
+ apply(model, value!);
+ return true;
+ }
+ }
+ catch (Exception ex)
+ {
+ Log_MigrationFailure(migrationName, ex);
+ }
+
+ return false;
+ }
+
+ [LoggerMessage(Level = LogLevel.Error, Message = "Settings migration '{MigrationName}' failed.")]
+ partial void Log_MigrationFailure(string MigrationName, Exception exception);
+
+ [LoggerMessage(Level = LogLevel.Error, Message = "Settings migration check failed.")]
+ partial void Log_MigrationCheckFailure(Exception exception);
+}
diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/TrayIconService.cs b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/TrayIconService.cs
index e94efd1239..77d880782d 100644
--- a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/TrayIconService.cs
+++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.Services/TrayIconService.cs
@@ -8,19 +8,23 @@ using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CommandPalette.UI.Models;
using Microsoft.CommandPalette.UI.Models.Messages;
using Microsoft.UI.Xaml;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.UI.Shell;
+using Windows.Win32.UI.WindowsAndMessaging;
using WinRT.Interop;
-using RS_ = Microsoft.CommandPalette.UI.Models.Helpers.ResourceLoaderInstance;
+using RS_ = Microsoft.CommandPalette.UI.Services.Helpers.ResourceLoaderInstance;
namespace Microsoft.CommandPalette.UI.Services;
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Stylistically, window messages are WM_*")]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "Stylistically, window messages are WM_*")]
-public sealed partial class TrayIconService
+public sealed partial class TrayIconService : IDisposable
{
private const uint MY_NOTIFY_ID = 1000;
private const uint WM_TRAY_ICON = PInvoke.WM_USER + 1;
- private readonly SettingsModel _settingsModel;
+ private readonly SettingsService _settingsService;
private readonly uint WM_TASKBAR_RESTART;
private Window? _window;
@@ -31,9 +35,10 @@ public sealed partial class TrayIconService
private DestroyIconSafeHandle? _largeIcon;
private DestroyMenuSafeHandle? _popupMenu;
- public TrayIconService(SettingsModel settingsModel)
+ public TrayIconService(SettingsService settingsService)
{
- _settingsModel = settingsModel;
+ _settingsService = settingsService;
+ _settingsService.SettingsChanged += OnSettingsChanged;
// TaskbarCreated is the message that's broadcast when explorer.exe
// restarts. We need to know when that happens to be able to bring our
@@ -41,9 +46,14 @@ public sealed partial class TrayIconService
WM_TASKBAR_RESTART = PInvoke.RegisterWindowMessage("TaskbarCreated");
}
- public void SetupTrayIcon(bool? showSystemTrayIcon = null)
+ private void OnSettingsChanged(SettingsModel sender, object? args)
{
- if (showSystemTrayIcon ?? _settingsModel.ShowSystemTrayIcon)
+ SetupTrayIcon();
+ }
+
+ public void SetupTrayIcon()
+ {
+ if (_settingsService.CurrentSettings.ShowSystemTrayIcon)
{
if (_window is null)
{
@@ -205,4 +215,14 @@ public sealed partial class TrayIconService
return PInvoke.CallWindowProc(_originalWndProc, hwnd, uMsg, wParam, lParam);
}
+
+ public void Dispose()
+ {
+ if (_settingsService is not null)
+ {
+ _settingsService.SettingsChanged -= OnSettingsChanged;
+ }
+
+ Destroy();
+ }
}
diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.ViewModels/Microsoft.CommandPalette.UI.ViewModels.csproj b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.ViewModels/Microsoft.CommandPalette.UI.ViewModels.csproj
index 5580976379..020582dc91 100644
--- a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.ViewModels/Microsoft.CommandPalette.UI.ViewModels.csproj
+++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.ViewModels/Microsoft.CommandPalette.UI.ViewModels.csproj
@@ -24,6 +24,7 @@
+
\ No newline at end of file
diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.ViewModels/SettingsViewModel.cs b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.ViewModels/SettingsViewModel.cs
index e3adadbd1c..032316b01d 100644
--- a/src/modules/Deux/UI/Microsoft.CommandPalette.UI.ViewModels/SettingsViewModel.cs
+++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI.ViewModels/SettingsViewModel.cs
@@ -4,11 +4,11 @@
using System.ComponentModel;
using Microsoft.CommandPalette.UI.Models;
-using Microsoft.Extensions.Logging;
+using Microsoft.CommandPalette.UI.Services;
namespace Microsoft.CommandPalette.UI.ViewModels.Settings;
-public partial class SettingsViewModel : INotifyPropertyChanged
+public partial class SettingsViewModel : INotifyPropertyChanged, IDisposable
{
private static readonly List AutoGoHomeIntervals =
[
@@ -23,18 +23,17 @@ public partial class SettingsViewModel : INotifyPropertyChanged
TimeSpan.FromSeconds(180),
];
- private readonly ILogger logger;
- private readonly SettingsModel _settings;
- private readonly IServiceProvider _serviceProvider;
+ private readonly SettingsService _settingsService;
+ private SettingsModel _settingsModel;
public event PropertyChangedEventHandler? PropertyChanged;
public HotkeySettings? Hotkey
{
- get => _settings.Hotkey;
+ get => _settingsModel.Hotkey;
set
{
- _settings.Hotkey = value ?? SettingsModel.DefaultActivationShortcut;
+ _settingsModel.Hotkey = value ?? SettingsModel.DefaultActivationShortcut;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Hotkey)));
Save();
}
@@ -42,10 +41,10 @@ public partial class SettingsViewModel : INotifyPropertyChanged
public bool UseLowLevelGlobalHotkey
{
- get => _settings.UseLowLevelGlobalHotkey;
+ get => _settingsModel.UseLowLevelGlobalHotkey;
set
{
- _settings.UseLowLevelGlobalHotkey = value;
+ _settingsModel.UseLowLevelGlobalHotkey = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Hotkey)));
Save();
}
@@ -53,90 +52,90 @@ public partial class SettingsViewModel : INotifyPropertyChanged
public bool AllowExternalReload
{
- get => _settings.AllowExternalReload;
+ get => _settingsModel.AllowExternalReload;
set
{
- _settings.AllowExternalReload = value;
+ _settingsModel.AllowExternalReload = value;
Save();
}
}
public bool ShowAppDetails
{
- get => _settings.ShowAppDetails;
+ get => _settingsModel.ShowAppDetails;
set
{
- _settings.ShowAppDetails = value;
+ _settingsModel.ShowAppDetails = value;
Save();
}
}
public bool BackspaceGoesBack
{
- get => _settings.BackspaceGoesBack;
+ get => _settingsModel.BackspaceGoesBack;
set
{
- _settings.BackspaceGoesBack = value;
+ _settingsModel.BackspaceGoesBack = value;
Save();
}
}
public bool SingleClickActivates
{
- get => _settings.SingleClickActivates;
+ get => _settingsModel.SingleClickActivates;
set
{
- _settings.SingleClickActivates = value;
+ _settingsModel.SingleClickActivates = value;
Save();
}
}
public bool HighlightSearchOnActivate
{
- get => _settings.HighlightSearchOnActivate;
+ get => _settingsModel.HighlightSearchOnActivate;
set
{
- _settings.HighlightSearchOnActivate = value;
+ _settingsModel.HighlightSearchOnActivate = value;
Save();
}
}
public int MonitorPositionIndex
{
- get => (int)_settings.SummonOn;
+ get => (int)_settingsModel.SummonOn;
set
{
- _settings.SummonOn = (MonitorBehavior)value;
+ _settingsModel.SummonOn = (MonitorBehavior)value;
Save();
}
}
public bool ShowSystemTrayIcon
{
- get => _settings.ShowSystemTrayIcon;
+ get => _settingsModel.ShowSystemTrayIcon;
set
{
- _settings.ShowSystemTrayIcon = value;
+ _settingsModel.ShowSystemTrayIcon = value;
Save();
}
}
public bool IgnoreShortcutWhenFullscreen
{
- get => _settings.IgnoreShortcutWhenFullscreen;
+ get => _settingsModel.IgnoreShortcutWhenFullscreen;
set
{
- _settings.IgnoreShortcutWhenFullscreen = value;
+ _settingsModel.IgnoreShortcutWhenFullscreen = value;
Save();
}
}
public bool DisableAnimations
{
- get => _settings.DisableAnimations;
+ get => _settingsModel.DisableAnimations;
set
{
- _settings.DisableAnimations = value;
+ _settingsModel.DisableAnimations = value;
Save();
}
}
@@ -145,7 +144,7 @@ public partial class SettingsViewModel : INotifyPropertyChanged
{
get
{
- var index = AutoGoHomeIntervals.IndexOf(_settings.AutoGoHomeInterval);
+ var index = AutoGoHomeIntervals.IndexOf(_settingsModel.AutoGoHomeInterval);
return index >= 0 ? index : 0;
}
@@ -153,7 +152,7 @@ public partial class SettingsViewModel : INotifyPropertyChanged
{
if (value >= 0 && value < AutoGoHomeIntervals.Count)
{
- _settings.AutoGoHomeInterval = AutoGoHomeIntervals[value];
+ _settingsModel.AutoGoHomeInterval = AutoGoHomeIntervals[value];
}
Save();
@@ -162,11 +161,12 @@ public partial class SettingsViewModel : INotifyPropertyChanged
// public ObservableCollection CommandProviders { get; } = [];
// public SettingsExtensionsViewModel Extensions { get; }
- public SettingsViewModel(SettingsModel settings, IServiceProvider serviceProvider, TaskScheduler scheduler, ILogger logger)
+ public SettingsViewModel(SettingsService settingsService)
{
- _settings = settings;
- _serviceProvider = serviceProvider;
- this.logger = logger;
+ _settingsService = settingsService;
+ _settingsModel = _settingsService.CurrentSettings;
+
+ _settingsService.SettingsChanged += OnSettingsChanged;
// var activeProviders = GetCommandProviders();
// var allProviderSettings = _settings.ProviderSettings;
@@ -179,11 +179,27 @@ public partial class SettingsViewModel : INotifyPropertyChanged
// Extensions = new SettingsExtensionsViewModel(CommandProviders, scheduler);
}
+ private void OnSettingsChanged(SettingsModel sender, object? args)
+ {
+ _settingsModel = sender;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
+ }
+
// private IEnumerable GetCommandProviders()
// {
// var manager = _serviceProvider.GetService()!;
// var allProviders = manager.CommandProviders;
// return allProviders;
// }
- private void Save() => SettingsModel.SaveSettings(_settings, logger);
+ private void Save() => _settingsService.SaveSettings(_settingsModel);
+
+ public void Dispose()
+ {
+ if (_settingsService is not null)
+ {
+ _settingsService.SettingsChanged -= OnSettingsChanged;
+ }
+
+ GC.SuppressFinalize(this);
+ }
}
diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI/App.xaml.cs b/src/modules/Deux/UI/Microsoft.CommandPalette.UI/App.xaml.cs
index e5d1a58b5b..ee34e74614 100644
--- a/src/modules/Deux/UI/Microsoft.CommandPalette.UI/App.xaml.cs
+++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI/App.xaml.cs
@@ -2,9 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CommandPalette.UI.Helpers;
-using Microsoft.CommandPalette.UI.Models;
using Microsoft.CommandPalette.UI.Pages;
using Microsoft.CommandPalette.UI.Services;
using Microsoft.CommandPalette.ViewModels;
@@ -22,6 +20,7 @@ public partial class App : Application
{
private readonly ILogger logger;
private readonly GlobalErrorHandler _globalErrorHandler;
+ private readonly IServiceProvider _services;
///
/// Gets the current instance in use.
@@ -32,10 +31,6 @@ public partial class App : Application
public ETWTrace EtwTrace { get; private set; } = new ETWTrace();
- ///
- /// Gets the instance to resolve application services.
- ///
- public IServiceProvider Services { get; }
public App(ILogger logger)
{
@@ -46,7 +41,7 @@ public partial class App : Application
_globalErrorHandler.Register(this);
#endif
- Services = ConfigureServices();
+ _services = ConfigureServices();
this.InitializeComponent();
@@ -70,12 +65,10 @@ public partial class App : Application
services.AddSingleton(logger);
services.AddSingleton(TaskScheduler.FromCurrentSynchronizationContext());
- // Register settings & app state
- var settingsModel = SettingsModel.LoadSettings(logger);
- services.AddSingleton(settingsModel);
-
- var appStateModel = AppStateModel.LoadState(logger);
- services.AddSingleton(appStateModel);
+ // Register settings & app state services first
+ // because other services depend on them
+ services.AddSingleton();
+ services.AddSingleton();
// Register services
services.AddSingleton();
@@ -99,7 +92,7 @@ public partial class App : Application
{
var activatedEventArgs = Microsoft.Windows.AppLifecycle.AppInstance.GetCurrent().GetActivatedEventArgs();
- var mainWindow = Services.GetRequiredService();
+ var mainWindow = _services.GetRequiredService();
AppWindow = mainWindow;
((MainWindow)AppWindow).HandleLaunchNonUI(activatedEventArgs);
diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Converters/PlaceholderTextConverter.cs b/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Converters/PlaceholderTextConverter.cs
index ad0af40afe..2be839cd83 100644
--- a/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Converters/PlaceholderTextConverter.cs
+++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Converters/PlaceholderTextConverter.cs
@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
using Microsoft.UI.Xaml.Data;
-using RS_ = Microsoft.CommandPalette.UI.Helpers.ResourceLoaderInstance;
+using RS_ = Microsoft.CommandPalette.UI.Services.Helpers.ResourceLoaderInstance;
namespace Microsoft.CommandPalette.UI.Converters;
diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Helpers/GlobalErrorHandler.cs b/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Helpers/GlobalErrorHandler.cs
index a2c405c288..dbd6c0035d 100644
--- a/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Helpers/GlobalErrorHandler.cs
+++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Helpers/GlobalErrorHandler.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using Microsoft.CommandPalette.UI.Services;
+using Microsoft.CommandPalette.UI.Services.Helpers;
using Microsoft.Extensions.Logging;
using Windows.Win32;
using Windows.Win32.Foundation;
diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI/MainWindow.xaml.cs b/src/modules/Deux/UI/Microsoft.CommandPalette.UI/MainWindow.xaml.cs
index d58d79bdff..87f62534cd 100644
--- a/src/modules/Deux/UI/Microsoft.CommandPalette.UI/MainWindow.xaml.cs
+++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI/MainWindow.xaml.cs
@@ -11,6 +11,7 @@ using Microsoft.CommandPalette.UI.Models;
using Microsoft.CommandPalette.UI.Models.Events;
using Microsoft.CommandPalette.UI.Models.Messages;
using Microsoft.CommandPalette.UI.Pages;
+using Microsoft.CommandPalette.UI.Services;
using Microsoft.CommandPalette.UI.ViewModels.Helpers;
using Microsoft.Extensions.Logging;
using Microsoft.PowerToys.Telemetry;
@@ -35,7 +36,7 @@ using Windows.Win32.UI.Input.KeyboardAndMouse;
using Windows.Win32.UI.WindowsAndMessaging;
using WinRT;
using WinUIEx;
-using RS_ = Microsoft.CommandPalette.UI.Helpers.ResourceLoaderInstance;
+using RS_ = Microsoft.CommandPalette.UI.Services.Helpers.ResourceLoaderInstance;
namespace Microsoft.CommandPalette.UI;
@@ -47,7 +48,7 @@ public sealed partial class MainWindow : WindowEx,
{
private readonly ILogger logger;
private readonly ShellPage _shellPage;
- private readonly SettingsModel _settingsModel;
+ private readonly SettingsService _settingsService;
private readonly TrayIconService _trayIconService;
private const int DefaultWidth = 800;
private const int DefaultHeight = 480;
@@ -72,13 +73,13 @@ public sealed partial class MainWindow : WindowEx,
private WindowPosition _currentWindowPosition = new();
- public MainWindow(ShellPage shellPage, SettingsModel settingsModel, TrayIconService trayIconService, ILogger logger)
+ public MainWindow(ShellPage shellPage, SettingsService settingsService, TrayIconService trayIconService, ILogger logger)
{
InitializeComponent();
this.logger = logger;
_shellPage = shellPage;
- _settingsModel = settingsModel;
+ _settingsService = settingsService;
_trayIconService = trayIconService;
RootElement.Children.Add(_shellPage);
@@ -128,9 +129,10 @@ public sealed partial class MainWindow : WindowEx,
var hotKeyPrcPointer = Marshal.GetFunctionPointerForDelegate(_hotkeyWndProc);
_originalWndProc = Marshal.GetDelegateForFunctionPointer(PInvoke.SetWindowLongPtr(_hwnd, WINDOW_LONG_PTR_INDEX.GWL_WNDPROC, hotKeyPrcPointer));
- // Load our settings, and then also wire up a settings changed handler
- HotReloadSettings();
- _settingsModel.SettingsChanged += SettingsChangedHandler;
+ // Wire up a settings changed handler
+ _settingsService.SettingsChanged += SettingsChangedHandler;
+
+ _trayIconService.SetupTrayIcon();
// Make sure that we update the acrylic theme when the OS theme changes
RootElement.ActualThemeChanged += (s, e) => DispatcherQueue.TryEnqueue(UpdateAcrylic);
@@ -150,7 +152,7 @@ public sealed partial class MainWindow : WindowEx,
public void Receive(ShowWindowMessage message)
{
- ShowHwnd(message.Hwnd, _settingsModel.SummonOn);
+ ShowHwnd(message.Hwnd, _settingsService.CurrentSettings.SummonOn);
}
public void Receive(HideWindowMessage message)
@@ -217,7 +219,7 @@ public sealed partial class MainWindow : WindowEx,
private void RestoreWindowPosition()
{
- if (_settingsModel.LastWindowPosition is not WindowPosition savedPosition)
+ if (_settingsService.CurrentSettings.LastWindowPosition is not WindowPosition savedPosition)
{
PositionCentered();
return;
@@ -264,12 +266,11 @@ public sealed partial class MainWindow : WindowEx,
private void HotReloadSettings()
{
- SetupHotkey(_settingsModel);
- _trayIconService.SetupTrayIcon(_settingsModel.ShowSystemTrayIcon);
+ SetupHotkey(_settingsService.CurrentSettings);
- _ignoreHotKeyWhenFullScreen = _settingsModel.IgnoreShortcutWhenFullscreen;
+ _ignoreHotKeyWhenFullScreen = _settingsService.CurrentSettings.IgnoreShortcutWhenFullscreen;
- _autoGoHomeInterval = _settingsModel.AutoGoHomeInterval;
+ _autoGoHomeInterval = _settingsService.CurrentSettings.AutoGoHomeInterval;
_autoGoHomeTimer.Interval = _autoGoHomeInterval;
}
@@ -614,9 +615,10 @@ public sealed partial class MainWindow : WindowEx,
{
UpdateWindowPositionInMemory();
- if (_settingsModel is not null)
+ if (_settingsService is not null)
{
- _settingsModel.LastWindowPosition = new WindowPosition
+ var settings = _settingsService.CurrentSettings;
+ settings.LastWindowPosition = new WindowPosition
{
X = _currentWindowPosition.X,
Y = _currentWindowPosition.Y,
@@ -627,12 +629,12 @@ public sealed partial class MainWindow : WindowEx,
ScreenHeight = _currentWindowPosition.ScreenHeight,
};
- SettingsModel.SaveSettings(_settingsModel, logger);
+ _settingsService.SaveSettings(settings);
}
// var extensionService = serviceProvider.GetService()!;
// extensionService.SignalStopExtensionsAsync();
- _trayIconService.Destroy();
+ // _trayIconService.Destroy();
// WinUI bug is causing a crash on shutdown when FailFastOnErrors is set to true (#51773592).
// Workaround by turning it off before shutdown.
@@ -770,7 +772,7 @@ public sealed partial class MainWindow : WindowEx,
}
else if (uri.StartsWith("x-cmdpal://reload", StringComparison.OrdinalIgnoreCase))
{
- if (_settingsModel.AllowExternalReload == true)
+ if (_settingsService.CurrentSettings.AllowExternalReload == true)
{
Log_ExternalReloadTriggered();
WeakReferenceMessenger.Default.Send(new());
diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Microsoft.CommandPalette.UI.csproj b/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Microsoft.CommandPalette.UI.csproj
index 94978a2862..9f188f551d 100644
--- a/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Microsoft.CommandPalette.UI.csproj
+++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Microsoft.CommandPalette.UI.csproj
@@ -161,7 +161,7 @@
-
+
diff --git a/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Pages/ShellPage.xaml.cs b/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Pages/ShellPage.xaml.cs
index c1d0ed5fdf..25edc14a66 100644
--- a/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Pages/ShellPage.xaml.cs
+++ b/src/modules/Deux/UI/Microsoft.CommandPalette.UI/Pages/ShellPage.xaml.cs
@@ -4,8 +4,8 @@
using System.Text;
using CommunityToolkit.Mvvm.Messaging;
-using Microsoft.CommandPalette.UI.Helpers;
using Microsoft.CommandPalette.UI.Models;
+using Microsoft.CommandPalette.UI.Models.Helpers;
using Microsoft.CommandPalette.UI.Models.Messages;
using Microsoft.CommandPalette.UI.ViewModels;
using Microsoft.CommandPalette.ViewModels;