mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-04 10:19:40 +01:00
Compare commits
2 Commits
dev/crutka
...
user/yeela
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ceb8434f41 | ||
|
|
30bbcf32a4 |
@@ -1,6 +1,5 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
model: 'GPT-5.1-Codex-Max'
|
||||
description: 'Generate an 80-character git commit title for the local diff'
|
||||
---
|
||||
|
||||
|
||||
1
.github/prompts/create-pr-summary.prompt.md
vendored
1
.github/prompts/create-pr-summary.prompt.md
vendored
@@ -1,6 +1,5 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
model: 'GPT-5.1-Codex-Max'
|
||||
description: 'Generate a PowerToys-ready pull request description from the local diff'
|
||||
---
|
||||
|
||||
|
||||
1
.github/prompts/fix-issue.prompt.md
vendored
1
.github/prompts/fix-issue.prompt.md
vendored
@@ -1,6 +1,5 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
model: 'GPT-5.1-Codex-Max'
|
||||
description: 'Execute the fix for a GitHub issue using the previously generated implementation plan'
|
||||
---
|
||||
|
||||
|
||||
70
.github/prompts/fix-pr-active-comments.prompt.md
vendored
Normal file
70
.github/prompts/fix-pr-active-comments.prompt.md
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
---
|
||||
description: 'Fix active pull request comments with scoped changes'
|
||||
name: 'fix-pr-active-comments'
|
||||
agent: 'agent'
|
||||
argument-hint: 'PR number or active PR URL'
|
||||
---
|
||||
|
||||
# Fix Active PR Comments
|
||||
|
||||
## Mission
|
||||
Resolve active pull request comments by applying only simple fixes. For complex refactors, write a plan instead of changing code.
|
||||
|
||||
## Scope & Preconditions
|
||||
- You must have an active pull request context or a provided PR number.
|
||||
- Only implement simple changes. Do not implement large refactors.
|
||||
- If required context is missing, request it and stop.
|
||||
|
||||
## Inputs
|
||||
- Required: ${input:pr_number:PR number or URL}
|
||||
- Optional: ${input:comment_scope:files or areas to focus on}
|
||||
- Optional: ${input:fixing_guidelines:additional fixing guidelines from the user}
|
||||
|
||||
## Workflow
|
||||
1. Locate all active (unresolved) PR review comments for the given PR.
|
||||
2. For each comment, classify the change scope:
|
||||
- Simple change: limited edits, localized fix, low risk, no broad redesign.
|
||||
- Large refactor: multi-file redesign, architecture change, or risky behavior change.
|
||||
3. For each large refactor request:
|
||||
- Do not modify code.
|
||||
- Write a planning document to Generated Files/prReview/${input:pr_number}/fixPlan/.
|
||||
4. For each simple change request:
|
||||
- Implement the fix with minimal edits.
|
||||
- Run quick checks if needed.
|
||||
- Commit and push the change.
|
||||
5. For comments that seem invalid, unclear, or not applicable (even if simple):
|
||||
- Do not change code.
|
||||
- Add the item to a summary table in Generated Files/prReview/${input:pr_number}/fixPlan/overview.md.
|
||||
- Consult back to the end user in a friendly, polite tone.
|
||||
6. Respond to each comment that you fixed:
|
||||
- Reply in the active conversation.
|
||||
- Use a polite or friendly tone.
|
||||
- Keep the response under 200 words.
|
||||
- Resolve the comment after replying.
|
||||
|
||||
## Output Expectations
|
||||
- Simple fixes: code changes committed and pushed.
|
||||
- Large refactors: a plan file saved to Generated Files/prReview/${input:pr_number}/fixPlan/.
|
||||
- Invalid or unclear comments: captured in Generated Files/prReview/${input:pr_number}/fixPlan/overview.md.
|
||||
- Each fixed comment has a reply under 200 words and is resolved.
|
||||
|
||||
## Plan File Template
|
||||
Use this template for each large refactor item:
|
||||
|
||||
# Fix Plan: <short title>
|
||||
|
||||
## Context
|
||||
- Comment link:
|
||||
- Impacted areas:
|
||||
|
||||
## Overview Table Template
|
||||
Use this table in Generated Files/prReview/${input:pr_number}/fixPlan/overview.md:
|
||||
|
||||
| Comment link | Summary | Reason not applied | Suggested follow-up |
|
||||
| --- | --- | --- | --- |
|
||||
| | | | |
|
||||
|
||||
## Quality Assurance
|
||||
- Verify plan file path exists.
|
||||
- Ensure no code changes were made for large refactor items.
|
||||
- Confirm replies are under 200 words and comments are resolved.
|
||||
1
.github/prompts/fix-spelling.prompt.md
vendored
1
.github/prompts/fix-spelling.prompt.md
vendored
@@ -1,6 +1,5 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
model: 'GPT-5.1-Codex-Max'
|
||||
description: 'Resolve Code scanning / check-spelling comments on the active PR'
|
||||
---
|
||||
|
||||
|
||||
1
.github/prompts/review-issue.prompt.md
vendored
1
.github/prompts/review-issue.prompt.md
vendored
@@ -1,6 +1,5 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
model: 'GPT-5.1-Codex-Max'
|
||||
description: 'Review a GitHub issue, score it (0-100), and generate an implementation plan'
|
||||
---
|
||||
|
||||
|
||||
1
.github/prompts/review-pr.prompt.md
vendored
1
.github/prompts/review-pr.prompt.md
vendored
@@ -1,6 +1,5 @@
|
||||
---
|
||||
agent: 'agent'
|
||||
model: 'GPT-5.1-Codex-Max'
|
||||
description: 'Perform a comprehensive PR review with per-step Markdown and machine-readable outputs'
|
||||
---
|
||||
|
||||
|
||||
@@ -7,62 +7,22 @@
|
||||
Queries Windows for all connected monitors and saves their configuration
|
||||
(position, size, DPI, primary status) to a JSON file that can be used
|
||||
for testing the CursorWrap module.
|
||||
|
||||
By default, potentially identifying information (computer name, user name,
|
||||
device names) is anonymized to protect privacy when sharing layout files.
|
||||
|
||||
.PARAMETER OutputPath
|
||||
Path where the JSON file will be saved. Default: cursorwrap_monitor_layout.json
|
||||
|
||||
.PARAMETER AddUserMachineNames
|
||||
Include computer name and user name in the output. By default these are
|
||||
blank to protect privacy when sharing layout files.
|
||||
|
||||
.PARAMETER AddDeviceNames
|
||||
Include device names (e.g., \\.\DISPLAY1) in the output. By default these
|
||||
are anonymized to "DISPLAY1", "DISPLAY2", etc. to reduce fingerprinting.
|
||||
|
||||
.PARAMETER Help
|
||||
Show this help message and exit.
|
||||
Path where the JSON file will be saved. Default: monitor_layout.json
|
||||
|
||||
.EXAMPLE
|
||||
.\Capture-MonitorLayout.ps1
|
||||
Captures layout with privacy-safe defaults (no user/machine names).
|
||||
|
||||
.EXAMPLE
|
||||
.\Capture-MonitorLayout.ps1 -OutputPath "my_setup.json"
|
||||
Saves to a custom filename.
|
||||
|
||||
.EXAMPLE
|
||||
.\Capture-MonitorLayout.ps1 -AddUserMachineNames
|
||||
Includes computer name and user name in the output.
|
||||
|
||||
.EXAMPLE
|
||||
.\Capture-MonitorLayout.ps1 -AddUserMachineNames -AddDeviceNames
|
||||
Includes all identifying information (useful for personal debugging).
|
||||
#>
|
||||
|
||||
param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string]$OutputPath = "cursorwrap_monitor_layout.json",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]$AddUserMachineNames,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]$AddDeviceNames,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[Alias("h", "?")]
|
||||
[switch]$Help
|
||||
[string]$OutputPath = "$($env:USERNAME)_monitor_layout.json"
|
||||
)
|
||||
|
||||
# Show help if requested
|
||||
if ($Help) {
|
||||
Get-Help $MyInvocation.MyCommand.Path -Detailed
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Add Windows Forms for screen enumeration
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
|
||||
@@ -177,20 +137,12 @@ function Capture-MonitorLayout {
|
||||
|
||||
$screens = [System.Windows.Forms.Screen]::AllScreens
|
||||
$monitors = @()
|
||||
$monitorIndex = 1
|
||||
|
||||
|
||||
foreach ($screen in $screens) {
|
||||
$isPrimary = $screen.Primary
|
||||
$bounds = $screen.Bounds
|
||||
$dpi = Get-MonitorDPI -Screen $screen
|
||||
|
||||
# Anonymize device name by default to reduce fingerprinting
|
||||
$deviceName = if ($AddDeviceNames) {
|
||||
$screen.DeviceName
|
||||
} else {
|
||||
"DISPLAY$monitorIndex"
|
||||
}
|
||||
|
||||
$monitor = [ordered]@{
|
||||
left = $bounds.Left
|
||||
top = $bounds.Top
|
||||
@@ -201,11 +153,10 @@ function Capture-MonitorLayout {
|
||||
dpi = $dpi
|
||||
scaling_percent = [math]::Round(($dpi / 96.0) * 100, 0)
|
||||
primary = $isPrimary
|
||||
device_name = $deviceName
|
||||
device_name = $screen.DeviceName
|
||||
}
|
||||
|
||||
$monitors += $monitor
|
||||
$monitorIndex++
|
||||
|
||||
# Display info
|
||||
$primaryTag = if ($isPrimary) { " [PRIMARY]" } else { "" }
|
||||
@@ -219,11 +170,11 @@ function Capture-MonitorLayout {
|
||||
Write-Host " Bounds: [$($bounds.Left), $($bounds.Top), $($bounds.Right), $($bounds.Bottom)]"
|
||||
}
|
||||
|
||||
# Create output object with privacy-safe defaults
|
||||
# Create output object
|
||||
$output = [ordered]@{
|
||||
captured_at = (Get-Date -Format "yyyy-MM-ddTHH:mm:sszzz")
|
||||
computer_name = if ($AddUserMachineNames) { $env:COMPUTERNAME } else { "" }
|
||||
user_name = if ($AddUserMachineNames) { $env:USERNAME } else { "" }
|
||||
computer_name = $env:COMPUTERNAME
|
||||
user_name = $env:USERNAME
|
||||
monitor_count = $monitors.Count
|
||||
monitors = $monitors
|
||||
}
|
||||
@@ -249,22 +200,10 @@ try {
|
||||
Write-Host "`n" + ("=" * 80)
|
||||
Write-Host "SUMMARY" -ForegroundColor Cyan
|
||||
Write-Host ("=" * 80)
|
||||
if ($layout.computer_name) {
|
||||
Write-Host "Configuration Name: $($layout.computer_name)"
|
||||
}
|
||||
Write-Host "Configuration Name: $($layout.computer_name)"
|
||||
Write-Host "Captured: $($layout.captured_at)"
|
||||
Write-Host "Monitors: $($layout.monitor_count)"
|
||||
|
||||
# Privacy notice
|
||||
if (-not $AddUserMachineNames -or -not $AddDeviceNames) {
|
||||
Write-Host "`nPrivacy: " -NoNewline -ForegroundColor Yellow
|
||||
$privacyNotes = @()
|
||||
if (-not $AddUserMachineNames) { $privacyNotes += "user/machine names excluded" }
|
||||
if (-not $AddDeviceNames) { $privacyNotes += "device names anonymized" }
|
||||
Write-Host ($privacyNotes -join ", ") -ForegroundColor Yellow
|
||||
Write-Host " Use -AddUserMachineNames and/or -AddDeviceNames to include." -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
# Calculate desktop dimensions
|
||||
$widths = @($layout.monitors | ForEach-Object { $_.width })
|
||||
$heights = @($layout.monitors | ForEach-Object { $_.height })
|
||||
|
||||
@@ -1,259 +0,0 @@
|
||||
// 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.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
/// <summary>
|
||||
/// Async settings repository implementation with caching and thread-safe access.
|
||||
/// Provides non-blocking settings operations suitable for UI applications.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The settings type.</typeparam>
|
||||
public sealed class AsyncSettingsRepository<T> : IAsyncSettingsRepository<T>, IDisposable
|
||||
where T : class, ISettingsConfig, new()
|
||||
{
|
||||
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly ISettingsUtils _settingsUtils;
|
||||
private readonly string _moduleName;
|
||||
private readonly string _fileName;
|
||||
|
||||
private T _cachedSettings;
|
||||
private FileSystemWatcher _watcher;
|
||||
private bool _isDisposed;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event Action<T> SettingsChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AsyncSettingsRepository{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="settingsUtils">The settings utilities instance.</param>
|
||||
/// <param name="fileName">The settings file name.</param>
|
||||
public AsyncSettingsRepository(ISettingsUtils settingsUtils, string fileName = "settings.json")
|
||||
{
|
||||
_settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
|
||||
_fileName = fileName;
|
||||
|
||||
// Get module name from type
|
||||
var settingsItem = new T();
|
||||
_moduleName = settingsItem.GetModuleName();
|
||||
|
||||
InitializeWatcher();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T SettingsConfig
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cachedSettings == null)
|
||||
{
|
||||
_semaphore.Wait();
|
||||
try
|
||||
{
|
||||
if (_cachedSettings == null)
|
||||
{
|
||||
_cachedSettings = LoadSettingsInternal();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
return _cachedSettings;
|
||||
}
|
||||
|
||||
private set => _cachedSettings = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<T> GetSettingsAsync(bool forceRefresh = false, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!forceRefresh && _cachedSettings != null)
|
||||
{
|
||||
return _cachedSettings;
|
||||
}
|
||||
|
||||
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (!forceRefresh && _cachedSettings != null)
|
||||
{
|
||||
return _cachedSettings;
|
||||
}
|
||||
|
||||
_cachedSettings = await Task.Run(() => LoadSettingsInternal(), cancellationToken).ConfigureAwait(false);
|
||||
return _cachedSettings;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask SaveSettingsAsync(T settings, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(settings);
|
||||
|
||||
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
// Temporarily stop watching to avoid self-triggered events
|
||||
StopWatching();
|
||||
|
||||
await Task.Run(
|
||||
() => _settingsUtils.SaveSettings(settings.ToJsonString(), _moduleName, _fileName),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
_cachedSettings = settings;
|
||||
}
|
||||
finally
|
||||
{
|
||||
StartWatching();
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool ReloadSettings()
|
||||
{
|
||||
_semaphore.Wait();
|
||||
try
|
||||
{
|
||||
var newSettings = LoadSettingsInternal();
|
||||
if (newSettings != null)
|
||||
{
|
||||
_cachedSettings = newSettings;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to reload settings for {_moduleName}", ex);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<bool> ReloadSettingsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var newSettings = await Task.Run(() => LoadSettingsInternal(), cancellationToken).ConfigureAwait(false);
|
||||
if (newSettings != null)
|
||||
{
|
||||
_cachedSettings = newSettings;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to reload settings for {_moduleName}", ex);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void StopWatching()
|
||||
{
|
||||
if (_watcher != null)
|
||||
{
|
||||
_watcher.EnableRaisingEvents = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void StartWatching()
|
||||
{
|
||||
if (_watcher != null)
|
||||
{
|
||||
_watcher.EnableRaisingEvents = true;
|
||||
}
|
||||
}
|
||||
|
||||
private T LoadSettingsInternal()
|
||||
{
|
||||
try
|
||||
{
|
||||
return _settingsUtils.GetSettingsOrDefault<T>(_moduleName, _fileName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to load settings for {_moduleName}", ex);
|
||||
return new T();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeWatcher()
|
||||
{
|
||||
try
|
||||
{
|
||||
var filePath = _settingsUtils.GetSettingsFilePath(_moduleName, _fileName);
|
||||
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 += OnWatcherChanged;
|
||||
_watcher.EnableRaisingEvents = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Failed to initialize settings watcher for {typeof(T).Name}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnWatcherChanged(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
// Wait a bit for the file write to complete and retry if needed
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
await Task.Delay(100).ConfigureAwait(false);
|
||||
if (await ReloadSettingsAsync().ConfigureAwait(false))
|
||||
{
|
||||
SettingsChanged?.Invoke(_cachedSettings);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
_watcher?.Dispose();
|
||||
_semaphore?.Dispose();
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for asynchronous settings repository operations.
|
||||
/// Provides non-blocking access to settings with caching support.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The settings type.</typeparam>
|
||||
public interface IAsyncSettingsRepository<T>
|
||||
where T : class, ISettingsConfig, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when the settings have been externally changed (e.g., by another process).
|
||||
/// </summary>
|
||||
event Action<T> SettingsChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current cached settings synchronously.
|
||||
/// Returns the cached value immediately, or loads if not cached.
|
||||
/// </summary>
|
||||
T SettingsConfig { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the settings asynchronously with optional refresh from disk.
|
||||
/// </summary>
|
||||
/// <param name="forceRefresh">If true, bypasses cache and reads from disk.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>The settings object.</returns>
|
||||
ValueTask<T> GetSettingsAsync(bool forceRefresh = false, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the settings asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings to save.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A task representing the save operation.</returns>
|
||||
ValueTask SaveSettingsAsync(T settings, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Reloads the settings from disk, updating the cache.
|
||||
/// </summary>
|
||||
/// <returns>True if the reload was successful.</returns>
|
||||
bool ReloadSettings();
|
||||
|
||||
/// <summary>
|
||||
/// Reloads the settings from disk asynchronously, updating the cache.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>True if the reload was successful.</returns>
|
||||
ValueTask<bool> ReloadSettingsAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Stops watching for external file changes.
|
||||
/// </summary>
|
||||
void StopWatching();
|
||||
|
||||
/// <summary>
|
||||
/// Starts watching for external file changes.
|
||||
/// </summary>
|
||||
void StartWatching();
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
// 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.PowerToys.Settings.UI.Library.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for settings utility operations to enable dependency injection and testability.
|
||||
/// </summary>
|
||||
public interface ISettingsUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if a settings file exists.
|
||||
/// </summary>
|
||||
/// <param name="powertoy">The module name.</param>
|
||||
/// <param name="fileName">The settings file name.</param>
|
||||
/// <returns>True if the settings file exists.</returns>
|
||||
bool SettingsExists(string powertoy = "", string fileName = "settings.json");
|
||||
|
||||
/// <summary>
|
||||
/// Deletes settings for the specified module.
|
||||
/// </summary>
|
||||
/// <param name="powertoy">The module name.</param>
|
||||
void DeleteSettings(string powertoy = "");
|
||||
|
||||
/// <summary>
|
||||
/// Gets settings for the specified module.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The settings type.</typeparam>
|
||||
/// <param name="powertoy">The module name.</param>
|
||||
/// <param name="fileName">The settings file name.</param>
|
||||
/// <returns>The deserialized settings object.</returns>
|
||||
T GetSettings<T>(string powertoy = "", string fileName = "settings.json")
|
||||
where T : ISettingsConfig, new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets settings for the specified module, or returns default if not found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The settings type.</typeparam>
|
||||
/// <param name="powertoy">The module name.</param>
|
||||
/// <param name="fileName">The settings file name.</param>
|
||||
/// <returns>The deserialized settings object or default.</returns>
|
||||
T GetSettingsOrDefault<T>(string powertoy = "", string fileName = "settings.json")
|
||||
where T : ISettingsConfig, new();
|
||||
|
||||
/// <summary>
|
||||
/// Saves settings to a JSON file.
|
||||
/// </summary>
|
||||
/// <param name="jsonSettings">The JSON settings string.</param>
|
||||
/// <param name="powertoy">The module name.</param>
|
||||
/// <param name="fileName">The settings file name.</param>
|
||||
void SaveSettings(string jsonSettings, string powertoy = "", string fileName = "settings.json");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file path to the settings file.
|
||||
/// </summary>
|
||||
/// <param name="powertoy">The module name.</param>
|
||||
/// <param name="fileName">The settings file name.</param>
|
||||
/// <returns>The full path to the settings file.</returns>
|
||||
string GetSettingsFilePath(string powertoy = "", string fileName = "settings.json");
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
|
||||
@@ -78,12 +77,12 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
}
|
||||
}
|
||||
|
||||
private async void Watcher_Changed(object sender, FileSystemEventArgs e)
|
||||
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++)
|
||||
{
|
||||
await Task.Delay(100).ConfigureAwait(false);
|
||||
Thread.Sleep(100);
|
||||
if (ReloadSettings())
|
||||
{
|
||||
SettingsChanged?.Invoke(SettingsConfig);
|
||||
|
||||
@@ -16,7 +16,7 @@ using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
// Some functions are marked as virtual to allow mocking in unit tests.
|
||||
public class SettingsUtils : ISettingsUtils
|
||||
public class SettingsUtils
|
||||
{
|
||||
public const string DefaultFileName = "settings.json";
|
||||
private const string DefaultModuleName = "";
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
// 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 CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Messages
|
||||
{
|
||||
/// <summary>
|
||||
/// Message sent when backup/restore operations complete.
|
||||
/// </summary>
|
||||
public sealed class BackupRestoreCompletedMessage : ValueChangedMessage<BackupRestoreCompletedMessage.ResultData>
|
||||
{
|
||||
public BackupRestoreCompletedMessage(bool success, string message, bool isBackup)
|
||||
: base(new ResultData(success, message, isBackup))
|
||||
{
|
||||
}
|
||||
|
||||
public record ResultData(bool Success, string Message, bool IsBackup);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// 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 CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Messages
|
||||
{
|
||||
/// <summary>
|
||||
/// Message sent when general settings are updated.
|
||||
/// </summary>
|
||||
public sealed class GeneralSettingsUpdatedMessage : ValueChangedMessage<GeneralSettings>
|
||||
{
|
||||
public GeneralSettingsUpdatedMessage(GeneralSettings settings)
|
||||
: base(settings)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// 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 CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Messages
|
||||
{
|
||||
/// <summary>
|
||||
/// Message sent when hotkey conflicts are detected.
|
||||
/// </summary>
|
||||
public sealed class HotkeyConflictDetectedMessage : ValueChangedMessage<HotkeyConflictDetectedMessage.ConflictData>
|
||||
{
|
||||
public HotkeyConflictDetectedMessage(string moduleName, string hotkeyDescription, bool isSystemConflict)
|
||||
: base(new ConflictData(moduleName, hotkeyDescription, isSystemConflict))
|
||||
{
|
||||
}
|
||||
|
||||
public record ConflictData(string ModuleName, string HotkeyDescription, bool IsSystemConflict);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// 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 CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Messages
|
||||
{
|
||||
/// <summary>
|
||||
/// Message sent when an IPC message is received from the PowerToys runner.
|
||||
/// </summary>
|
||||
public sealed class IPCMessageReceivedMessage : ValueChangedMessage<string>
|
||||
{
|
||||
public IPCMessageReceivedMessage(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// 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 CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Messages
|
||||
{
|
||||
/// <summary>
|
||||
/// Message sent when a module's enabled state changes.
|
||||
/// </summary>
|
||||
public sealed class ModuleEnabledChangedMessage : ValueChangedMessage<ModuleEnabledChangedMessage.ModuleStateData>
|
||||
{
|
||||
public ModuleEnabledChangedMessage(string moduleName, bool isEnabled)
|
||||
: base(new ModuleStateData(moduleName, isEnabled))
|
||||
{
|
||||
}
|
||||
|
||||
public record ModuleStateData(string ModuleName, bool IsEnabled);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// 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 CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Messages
|
||||
{
|
||||
/// <summary>
|
||||
/// Message sent to request navigation to a specific page.
|
||||
/// </summary>
|
||||
public sealed class NavigateToPageMessage : ValueChangedMessage<System.Type>
|
||||
{
|
||||
public NavigateToPageMessage(System.Type pageType)
|
||||
: base(pageType)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// 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.PowerToys.Settings.UI.Messages
|
||||
{
|
||||
/// <summary>
|
||||
/// Message sent to request a restart of PowerToys.
|
||||
/// </summary>
|
||||
public sealed class RestartRequestedMessage
|
||||
{
|
||||
public bool MaintainElevation { get; }
|
||||
|
||||
public RestartRequestedMessage(bool maintainElevation = true)
|
||||
{
|
||||
MaintainElevation = maintainElevation;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// 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 CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Messages
|
||||
{
|
||||
/// <summary>
|
||||
/// Message sent when settings are saved.
|
||||
/// </summary>
|
||||
public sealed class SettingsSavedMessage : ValueChangedMessage<SettingsSavedMessage.SettingsSaveData>
|
||||
{
|
||||
public SettingsSavedMessage(string moduleName)
|
||||
: base(new SettingsSaveData(moduleName))
|
||||
{
|
||||
}
|
||||
|
||||
public record SettingsSaveData(string ModuleName);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// 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 CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Messages
|
||||
{
|
||||
/// <summary>
|
||||
/// Message sent when the application theme changes.
|
||||
/// </summary>
|
||||
public sealed class ThemeChangedMessage : ValueChangedMessage<string>
|
||||
{
|
||||
public ThemeChangedMessage(string themeName)
|
||||
: base(themeName)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,9 +74,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.Controls.OpacityMaskView" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Animations" />
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
// 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 Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for navigation services to enable testability and abstraction
|
||||
/// from the static NavigationService class.
|
||||
/// </summary>
|
||||
public interface INavigationService
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs when navigation has completed.
|
||||
/// </summary>
|
||||
event NavigatedEventHandler Navigated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when navigation has failed.
|
||||
/// </summary>
|
||||
event NavigationFailedEventHandler NavigationFailed;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the navigation frame.
|
||||
/// </summary>
|
||||
Frame Frame { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether navigation can go back.
|
||||
/// </summary>
|
||||
bool CanGoBack { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether navigation can go forward.
|
||||
/// </summary>
|
||||
bool CanGoForward { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Navigates back in the navigation stack.
|
||||
/// </summary>
|
||||
/// <returns>True if navigation was successful.</returns>
|
||||
bool GoBack();
|
||||
|
||||
/// <summary>
|
||||
/// Navigates forward in the navigation stack.
|
||||
/// </summary>
|
||||
void GoForward();
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to the specified page type.
|
||||
/// </summary>
|
||||
/// <param name="pageType">The type of page to navigate to.</param>
|
||||
/// <param name="parameter">Optional navigation parameter.</param>
|
||||
/// <param name="infoOverride">Optional navigation transition override.</param>
|
||||
/// <returns>True if navigation was successful.</returns>
|
||||
bool Navigate(Type pageType, object parameter = null, NavigationTransitionInfo infoOverride = null);
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to the specified page type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of page to navigate to.</typeparam>
|
||||
/// <param name="parameter">Optional navigation parameter.</param>
|
||||
/// <param name="infoOverride">Optional navigation transition override.</param>
|
||||
/// <returns>True if navigation was successful.</returns>
|
||||
bool Navigate<T>(object parameter = null, NavigationTransitionInfo infoOverride = null)
|
||||
where T : Page;
|
||||
|
||||
/// <summary>
|
||||
/// Ensures a page of the specified type is selected.
|
||||
/// </summary>
|
||||
/// <param name="pageType">The type of page to ensure is selected.</param>
|
||||
void EnsurePageIsSelected(Type pageType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper around the static NavigationService to implement INavigationService.
|
||||
/// Allows for gradual migration and testability.
|
||||
/// </summary>
|
||||
public class NavigationServiceWrapper : INavigationService
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public event NavigatedEventHandler Navigated
|
||||
{
|
||||
add => NavigationService.Navigated += value;
|
||||
remove => NavigationService.Navigated -= value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event NavigationFailedEventHandler NavigationFailed
|
||||
{
|
||||
add => NavigationService.NavigationFailed += value;
|
||||
remove => NavigationService.NavigationFailed -= value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Frame Frame
|
||||
{
|
||||
get => NavigationService.Frame;
|
||||
set => NavigationService.Frame = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool CanGoBack => NavigationService.CanGoBack;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool CanGoForward => NavigationService.CanGoForward;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool GoBack() => NavigationService.GoBack();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void GoForward() => NavigationService.GoForward();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Navigate(Type pageType, object parameter = null, NavigationTransitionInfo infoOverride = null)
|
||||
=> NavigationService.Navigate(pageType, parameter, infoOverride);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Navigate<T>(object parameter = null, NavigationTransitionInfo infoOverride = null)
|
||||
where T : Page
|
||||
=> NavigationService.Navigate<T>(parameter, infoOverride);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void EnsurePageIsSelected(Type pageType) => NavigationService.EnsurePageIsSelected(pageType);
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
// 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.Extensions.DependencyInjection;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
using Microsoft.PowerToys.Settings.UI.ViewModels;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for configuring services in the dependency injection container.
|
||||
/// </summary>
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds core Settings UI services to the service collection.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection.</param>
|
||||
/// <returns>The service collection for chaining.</returns>
|
||||
public static IServiceCollection AddSettingsServices(this IServiceCollection services)
|
||||
{
|
||||
// Register singleton services
|
||||
services.AddSingleton<ISettingsUtils>(SettingsUtils.Default);
|
||||
services.AddSingleton<ThemeService>(App.ThemeService);
|
||||
services.AddSingleton<INavigationService, NavigationServiceWrapper>();
|
||||
|
||||
// Register settings repositories as singletons
|
||||
services.AddSingleton(sp =>
|
||||
{
|
||||
var settingsUtils = sp.GetRequiredService<ISettingsUtils>();
|
||||
return SettingsRepository<GeneralSettings>.GetInstance((SettingsUtils)settingsUtils);
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds ViewModel registrations to the service collection.
|
||||
/// ViewModels are registered as transient to create new instances per request.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection.</param>
|
||||
/// <returns>The service collection for chaining.</returns>
|
||||
public static IServiceCollection AddViewModels(this IServiceCollection services)
|
||||
{
|
||||
// Register ViewModels as transient for fresh instances
|
||||
// These can be migrated incrementally as each ViewModel is updated
|
||||
|
||||
// Tier 1 ViewModels (low complexity) - to be migrated first
|
||||
// services.AddTransient<FileLocksmithViewModel>();
|
||||
// services.AddTransient<RegistryPreviewViewModel>();
|
||||
// services.AddTransient<CropAndLockViewModel>();
|
||||
|
||||
// Tier 2 ViewModels (medium complexity)
|
||||
// services.AddTransient<ColorPickerViewModel>();
|
||||
// services.AddTransient<AlwaysOnTopViewModel>();
|
||||
// services.AddTransient<PowerOcrViewModel>();
|
||||
// services.AddTransient<HostsViewModel>();
|
||||
|
||||
// Tier 3 ViewModels (medium-high complexity)
|
||||
// services.AddTransient<FancyZonesViewModel>();
|
||||
// services.AddTransient<PowerLauncherViewModel>();
|
||||
// services.AddTransient<KeyboardManagerViewModel>();
|
||||
|
||||
// Tier 4 ViewModels (high complexity) - migrate last
|
||||
// services.AddTransient<GeneralViewModel>();
|
||||
// services.AddTransient<DashboardViewModel>();
|
||||
// services.AddTransient<ShellViewModel>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
// 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 Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a static wrapper around <see cref="Microsoft.Extensions.DependencyInjection.ServiceProvider"/>
|
||||
/// to enable gradual migration to dependency injection.
|
||||
/// </summary>
|
||||
public static class ServiceProvider
|
||||
{
|
||||
private static readonly object _lock = new object();
|
||||
private static IServiceProvider _serviceProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the service provider has been initialized.
|
||||
/// </summary>
|
||||
public static bool IsInitialized => _serviceProvider != null;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the service provider with the specified service collection.
|
||||
/// This should be called once during application startup.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection containing all registered services.</param>
|
||||
/// <exception cref="InvalidOperationException">Thrown if the service provider is already initialized.</exception>
|
||||
public static void Initialize(IServiceCollection services)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_serviceProvider != null)
|
||||
{
|
||||
throw new InvalidOperationException("ServiceProvider is already initialized.");
|
||||
}
|
||||
|
||||
_serviceProvider = services.BuildServiceProvider();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a service of the specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of service to get.</typeparam>
|
||||
/// <returns>The service instance, or null if not registered.</returns>
|
||||
public static T GetService<T>()
|
||||
where T : class
|
||||
{
|
||||
EnsureInitialized();
|
||||
return _serviceProvider.GetService<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a required service of the specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of service to get.</typeparam>
|
||||
/// <returns>The service instance.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown if the service is not registered.</exception>
|
||||
public static T GetRequiredService<T>()
|
||||
where T : class
|
||||
{
|
||||
EnsureInitialized();
|
||||
return _serviceProvider.GetRequiredService<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a service of the specified type.
|
||||
/// </summary>
|
||||
/// <param name="serviceType">The type of service to get.</param>
|
||||
/// <returns>The service instance, or null if not registered.</returns>
|
||||
public static object GetService(Type serviceType)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(serviceType);
|
||||
EnsureInitialized();
|
||||
return _serviceProvider.GetService(serviceType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a required service of the specified type.
|
||||
/// </summary>
|
||||
/// <param name="serviceType">The type of service to get.</param>
|
||||
/// <returns>The service instance.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown if the service is not registered.</exception>
|
||||
public static object GetRequiredService(Type serviceType)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(serviceType);
|
||||
EnsureInitialized();
|
||||
return _serviceProvider.GetRequiredService(serviceType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new scope for scoped services.
|
||||
/// </summary>
|
||||
/// <returns>A new service scope.</returns>
|
||||
public static IServiceScope CreateScope()
|
||||
{
|
||||
EnsureInitialized();
|
||||
return _serviceProvider.CreateScope();
|
||||
}
|
||||
|
||||
private static void EnsureInitialized()
|
||||
{
|
||||
if (_serviceProvider == null)
|
||||
{
|
||||
throw new InvalidOperationException("ServiceProvider has not been initialized. Call Initialize() first.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using ManagedCommon;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events;
|
||||
@@ -84,9 +83,6 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage;
|
||||
}
|
||||
|
||||
// Initialize dependency injection
|
||||
InitializeServices();
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
UnhandledException += App_UnhandledException;
|
||||
@@ -99,14 +95,6 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
});
|
||||
}
|
||||
|
||||
private static void InitializeServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddSettingsServices();
|
||||
services.AddViewModels();
|
||||
Services.ServiceProvider.Initialize(services);
|
||||
}
|
||||
|
||||
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
Logger.LogError("Unhandled exception", e.Exception);
|
||||
|
||||
@@ -22,19 +22,15 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
{
|
||||
public sealed partial class MainWindow : WindowEx
|
||||
{
|
||||
private bool _isFullyInitialized;
|
||||
private bool _createHidden;
|
||||
|
||||
public MainWindow(bool createHidden = false)
|
||||
{
|
||||
_createHidden = createHidden;
|
||||
|
||||
var bootTime = new System.Diagnostics.Stopwatch();
|
||||
bootTime.Start();
|
||||
|
||||
this.Activated += Window_Activated_SetIcon;
|
||||
|
||||
App.ThemeService.ThemeChanged += OnThemeChanged;
|
||||
App.ThemeService.ApplyTheme();
|
||||
|
||||
this.ExtendsContentIntoTitleBar = true;
|
||||
|
||||
@@ -47,13 +43,8 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
{
|
||||
placement.ShowCmd = NativeMethods.SW_HIDE;
|
||||
|
||||
// Defer full initialization until window is shown
|
||||
this.Activated += Window_Activated_LazyInit;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Full initialization for visible windows
|
||||
CompleteInitialization();
|
||||
// Restore the last known placement on the first activation
|
||||
this.Activated += Window_Activated;
|
||||
}
|
||||
|
||||
NativeMethods.SetWindowPlacement(hWnd, ref placement);
|
||||
@@ -61,16 +52,6 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
var loader = ResourceLoaderInstance.ResourceLoader;
|
||||
Title = App.IsElevated ? loader.GetString("SettingsWindow_AdminTitle") : loader.GetString("SettingsWindow_Title");
|
||||
|
||||
// IPC callbacks must be set up immediately so messages can be received even when hidden
|
||||
SetupIPCCallbacks();
|
||||
|
||||
bootTime.Stop();
|
||||
|
||||
PowerToysTelemetry.Log.WriteEvent(new SettingsBootEvent() { BootTimeMs = bootTime.ElapsedMilliseconds });
|
||||
}
|
||||
|
||||
private void SetupIPCCallbacks()
|
||||
{
|
||||
// send IPC Message
|
||||
ShellPage.SetDefaultSndMessageCallback(msg =>
|
||||
{
|
||||
@@ -147,10 +128,13 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
App.GetScoobeWindow().Activate();
|
||||
});
|
||||
|
||||
this.InitializeComponent();
|
||||
SetAppTitleBar();
|
||||
|
||||
// receive IPC Message
|
||||
App.IPCMessageReceivedCallback = (string msg) =>
|
||||
{
|
||||
if (ShellPage.ShellHandler?.IPCResponseHandleList != null)
|
||||
if (ShellPage.ShellHandler.IPCResponseHandleList != null)
|
||||
{
|
||||
var success = JsonObject.TryParse(msg, out JsonObject json);
|
||||
if (success)
|
||||
@@ -166,33 +150,10 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void CompleteInitialization()
|
||||
{
|
||||
if (_isFullyInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
bootTime.Stop();
|
||||
|
||||
_isFullyInitialized = true;
|
||||
|
||||
App.ThemeService.ApplyTheme();
|
||||
this.InitializeComponent();
|
||||
SetAppTitleBar();
|
||||
}
|
||||
|
||||
private void Window_Activated_LazyInit(object sender, WindowActivatedEventArgs args)
|
||||
{
|
||||
if (args.WindowActivationState != WindowActivationState.Deactivated && !_isFullyInitialized)
|
||||
{
|
||||
CompleteInitialization();
|
||||
|
||||
// After lazy init, also restore placement
|
||||
var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
|
||||
var placement = WindowHelper.DeserializePlacementOrDefault(hWnd);
|
||||
NativeMethods.SetWindowPlacement(hWnd, ref placement);
|
||||
}
|
||||
PowerToysTelemetry.Log.WriteEvent(new SettingsBootEvent() { BootTimeMs = bootTime.ElapsedMilliseconds });
|
||||
}
|
||||
|
||||
private void SetAppTitleBar()
|
||||
@@ -204,8 +165,6 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
|
||||
public void NavigateToSection(System.Type type)
|
||||
{
|
||||
// Ensure full initialization before navigation
|
||||
CompleteInitialization();
|
||||
ShellPage.Navigate(type);
|
||||
}
|
||||
|
||||
@@ -263,8 +222,6 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
|
||||
internal void EnsurePageIsSelected()
|
||||
{
|
||||
// Ensure full initialization before page selection
|
||||
CompleteInitialization();
|
||||
ShellPage.EnsurePageIsSelected();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,9 +41,9 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
Action stateUpdatingAction = () =>
|
||||
{
|
||||
this.DispatcherQueue.TryEnqueue(async () =>
|
||||
this.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
await ViewModel.RefreshUpdatingStateAsync().ConfigureAwait(true);
|
||||
ViewModel.RefreshUpdatingState();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -92,16 +92,9 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
CheckBugReportStatus();
|
||||
|
||||
this.Loaded += (s, e) =>
|
||||
{
|
||||
ViewModel.OnPageLoaded();
|
||||
doRefreshBackupRestoreStatus(100);
|
||||
|
||||
// Defer backup status check to after page is loaded with low priority
|
||||
this.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
doRefreshBackupRestoreStatus(100);
|
||||
});
|
||||
};
|
||||
this.Loaded += (s, e) => ViewModel.OnPageLoaded();
|
||||
}
|
||||
|
||||
private void OpenColorsSettings_Click(object sender, RoutedEventArgs e)
|
||||
@@ -130,11 +123,11 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
private void RefreshBackupRestoreStatus(int delayMs = 0)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
Task.Run(() =>
|
||||
{
|
||||
if (delayMs > 0)
|
||||
{
|
||||
await Task.Delay(delayMs).ConfigureAwait(false);
|
||||
Thread.Sleep(delayMs);
|
||||
}
|
||||
|
||||
var settingsBackupAndRestoreUtils = SettingsBackupAndRestoreUtils.Instance;
|
||||
@@ -178,7 +171,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
private async void ViewDiagnosticData_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await ViewModel.ViewDiagnosticDataAsync().ConfigureAwait(false);
|
||||
await Task.Run(ViewModel.ViewDiagnosticData);
|
||||
}
|
||||
|
||||
private void BugReportToolClicked(object sender, RoutedEventArgs e)
|
||||
|
||||
@@ -1,92 +1,43 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// 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.Globalization;
|
||||
using System.Text.Json;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
using global::PowerToys.GPOWrapper;
|
||||
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.SerializationContext;
|
||||
using Microsoft.PowerToys.Settings.UI.Services;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// ViewModel for the File Locksmith settings page.
|
||||
/// Uses CommunityToolkit.Mvvm for MVVM pattern implementation.
|
||||
/// </summary>
|
||||
public partial class FileLocksmithViewModel : ObservableObject
|
||||
public partial class FileLocksmithViewModel : Observable
|
||||
{
|
||||
private GeneralSettings GeneralSettingsConfig { get; set; }
|
||||
|
||||
private readonly ISettingsUtils _settingsUtils;
|
||||
private readonly SettingsUtils _settingsUtils;
|
||||
|
||||
private FileLocksmithSettings Settings { get; set; }
|
||||
|
||||
private const string ModuleNameConst = FileLocksmithSettings.ModuleName;
|
||||
private const string ModuleName = FileLocksmithSettings.ModuleName;
|
||||
|
||||
private string _settingsConfigFileFolder = string.Empty;
|
||||
|
||||
private GpoRuleConfigured _enabledGpoRuleConfiguration;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(IsEnabledGpoConfigured))]
|
||||
private bool _enabledStateIsGPOConfigured;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isFileLocksmithEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _enabledOnContextExtendedMenu;
|
||||
|
||||
private Func<string, int> SendConfigMSG { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FileLocksmithViewModel"/> class
|
||||
/// using dependency injection (for new code).
|
||||
/// </summary>
|
||||
/// <param name="settingsUtils">The settings utilities.</param>
|
||||
/// <param name="generalSettingsRepository">The general settings repository.</param>
|
||||
/// <param name="sendConfigMSG">The IPC message callback.</param>
|
||||
public FileLocksmithViewModel(
|
||||
ISettingsUtils settingsUtils,
|
||||
ISettingsRepository<GeneralSettings> generalSettingsRepository,
|
||||
Func<string, int> sendConfigMSG)
|
||||
{
|
||||
_settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
|
||||
ArgumentNullException.ThrowIfNull(generalSettingsRepository);
|
||||
|
||||
GeneralSettingsConfig = generalSettingsRepository.SettingsConfig;
|
||||
SendConfigMSG = sendConfigMSG ?? throw new ArgumentNullException(nameof(sendConfigMSG));
|
||||
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FileLocksmithViewModel"/> class
|
||||
/// (backward compatible constructor for existing code).
|
||||
/// </summary>
|
||||
public FileLocksmithViewModel(SettingsUtils settingsUtils, ISettingsRepository<GeneralSettings> settingsRepository, Func<string, int> ipcMSGCallBackFunc, string configFileSubfolder = "")
|
||||
{
|
||||
_settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
|
||||
|
||||
// To obtain the general settings configurations of PowerToys Settings.
|
||||
ArgumentNullException.ThrowIfNull(settingsRepository);
|
||||
|
||||
_settingsConfigFileFolder = configFileSubfolder;
|
||||
GeneralSettingsConfig = settingsRepository.SettingsConfig;
|
||||
SendConfigMSG = ipcMSGCallBackFunc;
|
||||
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
private void LoadSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
FileLocksmithLocalProperties localSettings = ((SettingsUtils)_settingsUtils).GetSettingsOrDefault<FileLocksmithLocalProperties>(GetSettingsSubPath(), "file-locksmith-settings.json");
|
||||
FileLocksmithLocalProperties localSettings = _settingsUtils.GetSettingsOrDefault<FileLocksmithLocalProperties>(GetSettingsSubPath(), "file-locksmith-settings.json");
|
||||
Settings = new FileLocksmithSettings(localSettings);
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -97,12 +48,16 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
|
||||
InitializeEnabledValue();
|
||||
EnabledOnContextExtendedMenu = Settings.Properties.ExtendedContextMenuOnly.Value;
|
||||
|
||||
// set the callback functions value to handle outgoing IPC message.
|
||||
SendConfigMSG = ipcMSGCallBackFunc;
|
||||
|
||||
_fileLocksmithEnabledOnContextExtendedMenu = Settings.Properties.ExtendedContextMenuOnly.Value;
|
||||
}
|
||||
|
||||
public string GetSettingsSubPath()
|
||||
{
|
||||
return _settingsConfigFileFolder + "\\" + ModuleNameConst;
|
||||
return _settingsConfigFileFolder + "\\" + ModuleName;
|
||||
}
|
||||
|
||||
private void InitializeEnabledValue()
|
||||
@@ -111,40 +66,65 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
if (_enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
|
||||
{
|
||||
// Get the enabled state from GPO.
|
||||
EnabledStateIsGPOConfigured = true;
|
||||
IsFileLocksmithEnabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
|
||||
_enabledStateIsGPOConfigured = true;
|
||||
_isFileLocksmithEnabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsFileLocksmithEnabled = GeneralSettingsConfig.Enabled.FileLocksmith;
|
||||
_isFileLocksmithEnabled = GeneralSettingsConfig.Enabled.FileLocksmith;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the enabled state is configured by GPO.
|
||||
/// </summary>
|
||||
public bool IsEnabledGpoConfigured => EnabledStateIsGPOConfigured;
|
||||
|
||||
partial void OnIsFileLocksmithEnabledChanged(bool value)
|
||||
public bool IsFileLocksmithEnabled
|
||||
{
|
||||
if (EnabledStateIsGPOConfigured)
|
||||
get => _isFileLocksmithEnabled;
|
||||
set
|
||||
{
|
||||
// If it's GPO configured, shouldn't be able to change this state.
|
||||
return;
|
||||
if (_enabledStateIsGPOConfigured)
|
||||
{
|
||||
// If it's GPO configured, shouldn't be able to change this state.
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isFileLocksmithEnabled != value)
|
||||
{
|
||||
_isFileLocksmithEnabled = value;
|
||||
|
||||
GeneralSettingsConfig.Enabled.FileLocksmith = value;
|
||||
OnPropertyChanged(nameof(IsFileLocksmithEnabled));
|
||||
|
||||
OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
|
||||
SendConfigMSG(outgoing.ToString());
|
||||
|
||||
// TODO: Implement when this module has properties.
|
||||
NotifySettingsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
GeneralSettingsConfig.Enabled.FileLocksmith = value;
|
||||
|
||||
OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
|
||||
SendConfigMSG(outgoing.ToString());
|
||||
|
||||
NotifySettingsChanged();
|
||||
}
|
||||
|
||||
partial void OnEnabledOnContextExtendedMenuChanged(bool value)
|
||||
public bool EnabledOnContextExtendedMenu
|
||||
{
|
||||
Settings.Properties.ExtendedContextMenuOnly.Value = value;
|
||||
NotifySettingsChanged();
|
||||
get
|
||||
{
|
||||
return _fileLocksmithEnabledOnContextExtendedMenu;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value != _fileLocksmithEnabledOnContextExtendedMenu)
|
||||
{
|
||||
_fileLocksmithEnabledOnContextExtendedMenu = value;
|
||||
Settings.Properties.ExtendedContextMenuOnly.Value = value;
|
||||
OnPropertyChanged(nameof(EnabledOnContextExtendedMenu));
|
||||
|
||||
NotifySettingsChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEnabledGpoConfigured
|
||||
{
|
||||
get => _enabledStateIsGPOConfigured;
|
||||
}
|
||||
|
||||
private void NotifySettingsChanged()
|
||||
@@ -158,10 +138,13 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
JsonSerializer.Serialize(Settings, SourceGenerationContextContext.Default.FileLocksmithSettings)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the enabled state by re-reading GPO configuration.
|
||||
/// </summary>
|
||||
[RelayCommand]
|
||||
private Func<string, int> SendConfigMSG { get; }
|
||||
|
||||
private GpoRuleConfigured _enabledGpoRuleConfiguration;
|
||||
private bool _enabledStateIsGPOConfigured;
|
||||
private bool _isFileLocksmithEnabled;
|
||||
private bool _fileLocksmithEnabledOnContextExtendedMenu;
|
||||
|
||||
public void RefreshEnabledState()
|
||||
{
|
||||
InitializeEnabledValue();
|
||||
|
||||
@@ -213,20 +213,14 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
_fileWatcher = Helper.GetFileWatcher(string.Empty, UpdatingSettings.SettingsFile, dispatcherAction);
|
||||
}
|
||||
|
||||
// Defer diagnostic data cleanup to background task to avoid blocking UI
|
||||
_ = Task.Run(CleanupDiagnosticDataAsync);
|
||||
|
||||
InitializeLanguages();
|
||||
}
|
||||
|
||||
private void CleanupDiagnosticDataAsync()
|
||||
{
|
||||
// Diagnostic data retention policy - runs on background thread
|
||||
// Diagnostic data retention policy
|
||||
string etwDirPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft\\PowerToys\\etw");
|
||||
DeleteDiagnosticDataOlderThan28Days(etwDirPath);
|
||||
|
||||
string localLowEtwDirPath = Path.Combine(Environment.GetEnvironmentVariable("USERPROFILE"), "AppData", "LocalLow", "Microsoft", "PowerToys", "etw");
|
||||
DeleteDiagnosticDataOlderThan28Days(localLowEtwDirPath);
|
||||
|
||||
InitializeLanguages();
|
||||
}
|
||||
|
||||
// Supported languages. Taken from Resources.wxs + default + en-US
|
||||
@@ -1362,54 +1356,52 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
NotifyAllBackupAndRestoreProperties();
|
||||
}
|
||||
|
||||
public async Task RefreshUpdatingStateAsync()
|
||||
public void RefreshUpdatingState()
|
||||
{
|
||||
// Load settings with retry on background thread
|
||||
var config = await Task.Run(async () =>
|
||||
object oLock = new object();
|
||||
lock (oLock)
|
||||
{
|
||||
var loadedConfig = UpdatingSettings.LoadSettings();
|
||||
var config = UpdatingSettings.LoadSettings();
|
||||
|
||||
// Retry loading if failed
|
||||
for (int i = 0; i < 3 && loadedConfig == null; i++)
|
||||
for (int i = 0; i < 3 && config == null; i++)
|
||||
{
|
||||
await Task.Delay(100).ConfigureAwait(false);
|
||||
loadedConfig = UpdatingSettings.LoadSettings();
|
||||
System.Threading.Thread.Sleep(100);
|
||||
config = UpdatingSettings.LoadSettings();
|
||||
}
|
||||
|
||||
return loadedConfig;
|
||||
}).ConfigureAwait(true);
|
||||
if (config == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (config == null)
|
||||
{
|
||||
return;
|
||||
UpdatingSettingsConfig = config;
|
||||
|
||||
if (PowerToysUpdatingState != config.State)
|
||||
{
|
||||
IsNewVersionDownloading = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool dateChanged = UpdateCheckedDate == UpdatingSettingsConfig.LastCheckedDateLocalized;
|
||||
bool fileDownloaded = string.IsNullOrEmpty(UpdatingSettingsConfig.DownloadedInstallerFilename);
|
||||
IsNewVersionDownloading = !(dateChanged || fileDownloaded);
|
||||
}
|
||||
|
||||
PowerToysUpdatingState = UpdatingSettingsConfig.State;
|
||||
PowerToysNewAvailableVersion = UpdatingSettingsConfig.NewVersion;
|
||||
PowerToysNewAvailableVersionLink = UpdatingSettingsConfig.ReleasePageLink;
|
||||
UpdateCheckedDate = UpdatingSettingsConfig.LastCheckedDateLocalized;
|
||||
|
||||
_isNoNetwork = PowerToysUpdatingState == UpdatingSettings.UpdatingState.NetworkError;
|
||||
NotifyPropertyChanged(nameof(IsNoNetwork));
|
||||
NotifyPropertyChanged(nameof(IsNewVersionDownloading));
|
||||
NotifyPropertyChanged(nameof(IsUpdatePanelVisible));
|
||||
_isNewVersionChecked = PowerToysUpdatingState == UpdatingSettings.UpdatingState.UpToDate && !IsNewVersionDownloading;
|
||||
NotifyPropertyChanged(nameof(IsNewVersionCheckedAndUpToDate));
|
||||
|
||||
NotifyPropertyChanged(nameof(IsDownloadAllowed));
|
||||
}
|
||||
|
||||
UpdatingSettingsConfig = config;
|
||||
|
||||
if (PowerToysUpdatingState != config.State)
|
||||
{
|
||||
IsNewVersionDownloading = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool dateChanged = UpdateCheckedDate == UpdatingSettingsConfig.LastCheckedDateLocalized;
|
||||
bool fileDownloaded = string.IsNullOrEmpty(UpdatingSettingsConfig.DownloadedInstallerFilename);
|
||||
IsNewVersionDownloading = !(dateChanged || fileDownloaded);
|
||||
}
|
||||
|
||||
PowerToysUpdatingState = UpdatingSettingsConfig.State;
|
||||
PowerToysNewAvailableVersion = UpdatingSettingsConfig.NewVersion;
|
||||
PowerToysNewAvailableVersionLink = UpdatingSettingsConfig.ReleasePageLink;
|
||||
UpdateCheckedDate = UpdatingSettingsConfig.LastCheckedDateLocalized;
|
||||
|
||||
_isNoNetwork = PowerToysUpdatingState == UpdatingSettings.UpdatingState.NetworkError;
|
||||
NotifyPropertyChanged(nameof(IsNoNetwork));
|
||||
NotifyPropertyChanged(nameof(IsNewVersionDownloading));
|
||||
NotifyPropertyChanged(nameof(IsUpdatePanelVisible));
|
||||
_isNewVersionChecked = PowerToysUpdatingState == UpdatingSettings.UpdatingState.UpToDate && !IsNewVersionDownloading;
|
||||
NotifyPropertyChanged(nameof(IsNewVersionCheckedAndUpToDate));
|
||||
|
||||
NotifyPropertyChanged(nameof(IsDownloadAllowed));
|
||||
}
|
||||
|
||||
private void InitializeLanguages()
|
||||
@@ -1497,7 +1489,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task ViewDiagnosticDataAsync()
|
||||
internal void ViewDiagnosticData()
|
||||
{
|
||||
string localLowEtwDirPath = Path.Combine(Environment.GetEnvironmentVariable("USERPROFILE"), "AppData", "LocalLow", "Microsoft", "PowerToys", "etw");
|
||||
string etwDirPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft\\PowerToys\\etw");
|
||||
@@ -1530,7 +1522,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
string tracerptPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "system32");
|
||||
|
||||
ETLConverter converter = new ETLConverter(etwDirPath, tracerptPath);
|
||||
await converter.ConvertDiagnosticsETLsAsync().ConfigureAwait(false);
|
||||
Task.Run(() => converter.ConvertDiagnosticsETLsAsync()).Wait();
|
||||
|
||||
if (Directory.Exists(etwDirPath))
|
||||
{
|
||||
|
||||
@@ -1,242 +0,0 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.HotkeyConflicts;
|
||||
using Microsoft.PowerToys.Settings.UI.Services;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for module-specific ViewModels that provides common functionality
|
||||
/// such as enabled state, GPO management, and hotkey conflict handling.
|
||||
/// </summary>
|
||||
public abstract partial class ModuleViewModelBase : ViewModelBase
|
||||
{
|
||||
private readonly Dictionary<string, bool> _hotkeyConflictStatus = new Dictionary<string, bool>();
|
||||
private readonly Dictionary<string, string> _hotkeyConflictTooltips = new Dictionary<string, string>();
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(IsEnabledGpoConfigured))]
|
||||
private bool _isEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isGpoManaged;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModuleViewModelBase"/> class.
|
||||
/// </summary>
|
||||
protected ModuleViewModelBase()
|
||||
{
|
||||
if (GlobalHotkeyConflictManager.Instance != null)
|
||||
{
|
||||
GlobalHotkeyConflictManager.Instance.ConflictsUpdated += OnConflictsUpdated;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModuleViewModelBase"/> class with a custom messenger.
|
||||
/// </summary>
|
||||
/// <param name="messenger">The messenger instance to use.</param>
|
||||
protected ModuleViewModelBase(IMessenger messenger)
|
||||
: base(messenger)
|
||||
{
|
||||
if (GlobalHotkeyConflictManager.Instance != null)
|
||||
{
|
||||
GlobalHotkeyConflictManager.Instance.ConflictsUpdated += OnConflictsUpdated;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the module name used for settings and conflict detection.
|
||||
/// </summary>
|
||||
protected abstract string ModuleName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the enabled state is configured by GPO.
|
||||
/// </summary>
|
||||
public bool IsEnabledGpoConfigured => IsGpoManaged;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void OnPageLoaded()
|
||||
{
|
||||
base.OnPageLoaded();
|
||||
Debug.WriteLine($"=== PAGE LOADED: {ModuleName} ===");
|
||||
GlobalHotkeyConflictManager.Instance?.RequestAllConflicts();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all hotkey settings for this module.
|
||||
/// Override in derived classes to return module-specific hotkey settings.
|
||||
/// </summary>
|
||||
/// <returns>A dictionary of module names to hotkey settings arrays.</returns>
|
||||
public virtual Dictionary<string, HotkeySettings[]> GetAllHotkeySettings()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles updates to hotkey conflicts for the module.
|
||||
/// </summary>
|
||||
protected virtual void OnConflictsUpdated(object sender, AllHotkeyConflictsEventArgs e)
|
||||
{
|
||||
UpdateHotkeyConflictStatus(e.Conflicts);
|
||||
var allHotkeySettings = GetAllHotkeySettings();
|
||||
|
||||
void UpdateConflictProperties()
|
||||
{
|
||||
if (allHotkeySettings != null)
|
||||
{
|
||||
foreach (KeyValuePair<string, HotkeySettings[]> kvp in allHotkeySettings)
|
||||
{
|
||||
var module = kvp.Key;
|
||||
var hotkeySettingsList = kvp.Value;
|
||||
|
||||
for (int i = 0; i < hotkeySettingsList.Length; i++)
|
||||
{
|
||||
var key = $"{module.ToLowerInvariant()}_{i}";
|
||||
hotkeySettingsList[i].HasConflict = GetHotkeyConflictStatus(key);
|
||||
hotkeySettingsList[i].ConflictDescription = GetHotkeyConflictTooltip(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var settingsWindow = App.GetSettingsWindow();
|
||||
settingsWindow.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal, UpdateConflictProperties);
|
||||
}
|
||||
catch
|
||||
{
|
||||
UpdateConflictProperties();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets module-related conflicts from all conflicts data.
|
||||
/// </summary>
|
||||
protected ModuleConflictsData GetModuleRelatedConflicts(AllHotkeyConflictsData allConflicts)
|
||||
{
|
||||
var moduleConflicts = new ModuleConflictsData();
|
||||
|
||||
if (allConflicts.InAppConflicts != null)
|
||||
{
|
||||
foreach (var conflict in allConflicts.InAppConflicts)
|
||||
{
|
||||
if (IsModuleInvolved(conflict))
|
||||
{
|
||||
moduleConflicts.InAppConflicts.Add(conflict);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allConflicts.SystemConflicts != null)
|
||||
{
|
||||
foreach (var conflict in allConflicts.SystemConflicts)
|
||||
{
|
||||
if (IsModuleInvolved(conflict))
|
||||
{
|
||||
moduleConflicts.SystemConflicts.Add(conflict);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return moduleConflicts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the hotkey conflict status based on all conflicts data.
|
||||
/// </summary>
|
||||
protected virtual void UpdateHotkeyConflictStatus(AllHotkeyConflictsData allConflicts)
|
||||
{
|
||||
_hotkeyConflictStatus.Clear();
|
||||
_hotkeyConflictTooltips.Clear();
|
||||
|
||||
if (allConflicts.InAppConflicts.Count > 0)
|
||||
{
|
||||
foreach (var conflictGroup in allConflicts.InAppConflicts)
|
||||
{
|
||||
foreach (var conflict in conflictGroup.Modules)
|
||||
{
|
||||
if (string.Equals(conflict.ModuleName, ModuleName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var keyName = $"{conflict.ModuleName.ToLowerInvariant()}_{conflict.HotkeyID}";
|
||||
_hotkeyConflictStatus[keyName] = true;
|
||||
_hotkeyConflictTooltips[keyName] = ResourceLoaderInstance.ResourceLoader.GetString("InAppHotkeyConflictTooltipText");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allConflicts.SystemConflicts.Count > 0)
|
||||
{
|
||||
foreach (var conflictGroup in allConflicts.SystemConflicts)
|
||||
{
|
||||
foreach (var conflict in conflictGroup.Modules)
|
||||
{
|
||||
if (string.Equals(conflict.ModuleName, ModuleName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var keyName = $"{conflict.ModuleName.ToLowerInvariant()}_{conflict.HotkeyID}";
|
||||
_hotkeyConflictStatus[keyName] = true;
|
||||
_hotkeyConflictTooltips[keyName] = ResourceLoaderInstance.ResourceLoader.GetString("SysHotkeyConflictTooltipText");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether a specific hotkey has a conflict.
|
||||
/// </summary>
|
||||
protected virtual bool GetHotkeyConflictStatus(string key)
|
||||
{
|
||||
return _hotkeyConflictStatus.ContainsKey(key) && _hotkeyConflictStatus[key];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the conflict tooltip for a specific hotkey.
|
||||
/// </summary>
|
||||
protected virtual string GetHotkeyConflictTooltip(string key)
|
||||
{
|
||||
return _hotkeyConflictTooltips.TryGetValue(key, out string value) ? value : null;
|
||||
}
|
||||
|
||||
private bool IsModuleInvolved(HotkeyConflictGroupData conflict)
|
||||
{
|
||||
if (conflict.Modules == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return conflict.Modules.Any(module =>
|
||||
string.Equals(module.ModuleName, ModuleName, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (GlobalHotkeyConflictManager.Instance != null)
|
||||
{
|
||||
GlobalHotkeyConflictManager.Instance.ConflictsUpdated -= OnConflictsUpdated;
|
||||
}
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
// 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.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for all ViewModels using CommunityToolkit.Mvvm.
|
||||
/// Provides lifecycle methods and messaging support.
|
||||
/// </summary>
|
||||
public abstract class ViewModelBase : ObservableRecipient, IDisposable
|
||||
{
|
||||
private bool _isDisposed;
|
||||
private bool _isActive;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ViewModelBase"/> class.
|
||||
/// </summary>
|
||||
protected ViewModelBase()
|
||||
: base(WeakReferenceMessenger.Default)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ViewModelBase"/> class with a custom messenger.
|
||||
/// </summary>
|
||||
/// <param name="messenger">The messenger instance to use.</param>
|
||||
protected ViewModelBase(IMessenger messenger)
|
||||
: base(messenger)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this ViewModel is currently active (visible).
|
||||
/// </summary>
|
||||
public bool IsViewModelActive => _isActive;
|
||||
|
||||
/// <summary>
|
||||
/// Called when the view associated with this ViewModel is navigated to.
|
||||
/// Override this method to perform initialization when the page is displayed.
|
||||
/// </summary>
|
||||
public virtual void OnNavigatedTo()
|
||||
{
|
||||
_isActive = true;
|
||||
IsActive = true; // Activates message subscriptions in ObservableRecipient
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the view associated with this ViewModel is navigated to.
|
||||
/// Override this method to perform async initialization when the page is displayed.
|
||||
/// </summary>
|
||||
/// <returns>A task representing the async operation.</returns>
|
||||
public virtual Task OnNavigatedToAsync()
|
||||
{
|
||||
OnNavigatedTo();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the view associated with this ViewModel is navigated away from.
|
||||
/// Override this method to perform cleanup when the page is no longer displayed.
|
||||
/// </summary>
|
||||
public virtual void OnNavigatedFrom()
|
||||
{
|
||||
_isActive = false;
|
||||
IsActive = false; // Deactivates message subscriptions in ObservableRecipient
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the page is loaded. This is triggered from the Page's Loaded event.
|
||||
/// </summary>
|
||||
public virtual void OnPageLoaded()
|
||||
{
|
||||
// Default implementation does nothing.
|
||||
// Override in derived classes to perform actions when the page is loaded.
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and optionally managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing">True to release both managed and unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Deactivate message subscriptions
|
||||
IsActive = false;
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user