Files
PowerToys/src/settings-ui/Settings.UI/ViewModels/PeekViewModel.cs
Noraa Junker 7cd201d355 Remove ISettingsUtils and ISettingsPath interfaces (#44331)
<!-- 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

This PR removes the ISettingsUtils and ISettingsPath interfaces to
reduce some complexity. They only existed so the classes can be used
with moq. But this is also possible by using virtual methods which is
cleaner.

<!-- 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
- [x] **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
2025-12-19 10:30:01 +08:00

371 lines
13 KiB
C#

// 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;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.IO.Abstractions;
using System.Text.Json;
using global::PowerToys.GPOWrapper;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
using Microsoft.PowerToys.Settings.UI.SerializationContext;
using Microsoft.UI.Dispatching;
using Settings.UI.Library;
namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
public class PeekViewModel : PageViewModelBase
{
protected override string ModuleName => PeekSettings.ModuleName;
private bool _isEnabled;
private bool _disposed;
private bool _settingsUpdating;
private GeneralSettings GeneralSettingsConfig { get; set; }
private readonly DispatcherQueue _dispatcherQueue;
private readonly SettingsUtils _settingsUtils;
private readonly PeekPreviewSettings _peekPreviewSettings;
private PeekSettings _peekSettings;
private GpoRuleConfigured _enabledGpoRuleConfiguration;
private bool _enabledStateIsGPOConfigured;
private Func<string, int> SendConfigMSG { get; }
private IFileSystemWatcher _watcher;
public PeekViewModel(
SettingsUtils settingsUtils,
ISettingsRepository<GeneralSettings> settingsRepository,
Func<string, int> ipcMSGCallBackFunc,
DispatcherQueue dispatcherQueue)
{
// To obtain the general settings configurations of PowerToys Settings.
ArgumentNullException.ThrowIfNull(settingsRepository);
GeneralSettingsConfig = settingsRepository.SettingsConfig;
_dispatcherQueue = dispatcherQueue ?? throw new ArgumentNullException(nameof(dispatcherQueue));
_settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
// Load the application-specific settings, including preview items.
_peekSettings = _settingsUtils.GetSettingsOrDefault<PeekSettings>(PeekSettings.ModuleName);
_peekPreviewSettings = _settingsUtils.GetSettingsOrDefault<PeekPreviewSettings>(PeekSettings.ModuleName, PeekPreviewSettings.FileName);
SetupSettingsFileWatcher();
InitializeEnabledValue();
SendConfigMSG = ipcMSGCallBackFunc;
}
/// <summary>
/// Set up the file watcher for the settings file. Used to respond to updates to the
/// ConfirmFileDelete setting by the user within the Peek application itself.
/// </summary>
private void SetupSettingsFileWatcher()
{
string settingsPath = _settingsUtils.GetSettingsFilePath(PeekSettings.ModuleName);
_watcher = Helper.GetFileWatcher(PeekSettings.ModuleName, SettingsUtils.DefaultFileName, () =>
{
try
{
_settingsUpdating = true;
var newSettings = _settingsUtils.GetSettings<PeekSettings>(PeekSettings.ModuleName);
_dispatcherQueue.TryEnqueue(() =>
{
try
{
ConfirmFileDelete = newSettings.Properties.ConfirmFileDelete.Value;
_peekSettings = newSettings;
}
finally
{
// Only clear the flag once the UI update is complete.
_settingsUpdating = false;
}
});
}
catch (Exception ex)
{
Logger.LogError($"Failed to load Peek settings: {ex.Message}", ex);
_settingsUpdating = false;
}
});
}
private void InitializeEnabledValue()
{
_enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredPeekEnabledValue();
if (_enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
{
// Get the enabled state from GPO.
_enabledStateIsGPOConfigured = true;
_isEnabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
}
else
{
_isEnabled = GeneralSettingsConfig.Enabled.Peek;
}
}
public override Dictionary<string, HotkeySettings[]> GetAllHotkeySettings()
{
var hotkeysDict = new Dictionary<string, HotkeySettings[]>
{
[ModuleName] = [ActivationShortcut],
};
return hotkeysDict;
}
public bool IsEnabled
{
get => _isEnabled;
set
{
if (_enabledStateIsGPOConfigured)
{
// If it's GPO configured, shouldn't be able to change this state.
return;
}
if (_isEnabled != value)
{
_isEnabled = value;
GeneralSettingsConfig.Enabled.Peek = value;
OnPropertyChanged(nameof(IsEnabled));
OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
SendConfigMSG(outgoing.ToString());
}
}
}
public bool IsEnabledGpoConfigured
{
get => _enabledStateIsGPOConfigured;
}
public HotkeySettings ActivationShortcut
{
get => _peekSettings.Properties.ActivationShortcut;
set
{
if (_peekSettings.Properties.ActivationShortcut != value)
{
// If space mode toggle is on, ignore external attempts to change (UI will be disabled, but defensive).
if (EnableSpaceToActivate)
{
return;
}
_peekSettings.Properties.ActivationShortcut = value ?? _peekSettings.Properties.DefaultActivationShortcut;
OnPropertyChanged(nameof(ActivationShortcut));
NotifySettingsChanged();
}
}
}
public bool AlwaysRunNotElevated
{
get => _peekSettings.Properties.AlwaysRunNotElevated.Value;
set
{
if (_peekSettings.Properties.AlwaysRunNotElevated.Value != value)
{
_peekSettings.Properties.AlwaysRunNotElevated.Value = value;
OnPropertyChanged(nameof(AlwaysRunNotElevated));
NotifySettingsChanged();
}
}
}
public bool CloseAfterLosingFocus
{
get => _peekSettings.Properties.CloseAfterLosingFocus.Value;
set
{
if (_peekSettings.Properties.CloseAfterLosingFocus.Value != value)
{
_peekSettings.Properties.CloseAfterLosingFocus.Value = value;
OnPropertyChanged(nameof(CloseAfterLosingFocus));
NotifySettingsChanged();
}
}
}
public bool ConfirmFileDelete
{
get => _peekSettings.Properties.ConfirmFileDelete.Value;
set
{
if (_peekSettings.Properties.ConfirmFileDelete.Value != value)
{
_peekSettings.Properties.ConfirmFileDelete.Value = value;
OnPropertyChanged(nameof(ConfirmFileDelete));
NotifySettingsChanged();
}
}
}
public bool EnableSpaceToActivate
{
get => _peekSettings.Properties.EnableSpaceToActivate.Value;
set
{
if (_peekSettings.Properties.EnableSpaceToActivate.Value != value)
{
_peekSettings.Properties.EnableSpaceToActivate.Value = value;
if (value)
{
// Force single space (0x20) without modifiers.
_peekSettings.Properties.ActivationShortcut = new HotkeySettings(false, false, false, false, 0x20);
}
else
{
// Revert to default (design simplification, not restoring previous custom combo).
_peekSettings.Properties.ActivationShortcut = _peekSettings.Properties.DefaultActivationShortcut;
}
OnPropertyChanged(nameof(EnableSpaceToActivate));
OnPropertyChanged(nameof(ActivationShortcut));
NotifySettingsChanged();
}
}
}
public bool SourceCodeWrapText
{
get => _peekPreviewSettings.SourceCodeWrapText.Value;
set
{
if (_peekPreviewSettings.SourceCodeWrapText.Value != value)
{
_peekPreviewSettings.SourceCodeWrapText.Value = value;
OnPropertyChanged(nameof(SourceCodeWrapText));
SavePreviewSettings();
}
}
}
public bool SourceCodeTryFormat
{
get => _peekPreviewSettings.SourceCodeTryFormat.Value;
set
{
if (_peekPreviewSettings.SourceCodeTryFormat.Value != value)
{
_peekPreviewSettings.SourceCodeTryFormat.Value = value;
OnPropertyChanged(nameof(SourceCodeTryFormat));
SavePreviewSettings();
}
}
}
public int SourceCodeFontSize
{
get => _peekPreviewSettings.SourceCodeFontSize.Value;
set
{
if (_peekPreviewSettings.SourceCodeFontSize.Value != value)
{
_peekPreviewSettings.SourceCodeFontSize.Value = value;
OnPropertyChanged(nameof(SourceCodeFontSize));
SavePreviewSettings();
}
}
}
public bool SourceCodeStickyScroll
{
get => _peekPreviewSettings.SourceCodeStickyScroll.Value;
set
{
if (_peekPreviewSettings.SourceCodeStickyScroll.Value != value)
{
_peekPreviewSettings.SourceCodeStickyScroll.Value = value;
OnPropertyChanged(nameof(SourceCodeStickyScroll));
SavePreviewSettings();
}
}
}
public bool SourceCodeMinimap
{
get => _peekPreviewSettings.SourceCodeMinimap.Value;
set
{
if (_peekPreviewSettings.SourceCodeMinimap.Value != value)
{
_peekPreviewSettings.SourceCodeMinimap.Value = value;
OnPropertyChanged(nameof(SourceCodeMinimap));
SavePreviewSettings();
}
}
}
private void NotifySettingsChanged()
{
// Do not send IPC message if the settings file has been updated by Peek itself.
if (_settingsUpdating)
{
return;
}
// This message will be intercepted by the runner, which passes the serialized JSON to
// Peek.set_config() in the C++ Peek project, which then saves it to file.
SendConfigMSG(
string.Format(
CultureInfo.InvariantCulture,
"{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
PeekSettings.ModuleName,
JsonSerializer.Serialize(_peekSettings, SourceGenerationContextContext.Default.PeekSettings)));
}
private void SavePreviewSettings()
{
_settingsUtils.SaveSettings(_peekPreviewSettings.ToJsonString(), PeekSettings.ModuleName, PeekPreviewSettings.FileName);
}
public void RefreshEnabledState()
{
InitializeEnabledValue();
OnPropertyChanged(nameof(IsEnabled));
}
protected override void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_watcher?.Dispose();
_watcher = null;
}
_disposed = true;
}
base.Dispose(disposing);
}
}
}