diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000000..5a6991232f
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,7 @@
+[submodule "deps/spdlog"]
+ path = deps/spdlog
+ url = https://github.com/gabime/spdlog.git
+
+[submodule "deps/cxxopts"]
+ path = deps/cxxopts
+ url = https://github.com/jarro2783/cxxopts.git
diff --git a/.pipelines/build-localization.cmd b/.pipelines/build-localization.cmd
index 91803a6959..c479f0dfbe 100644
--- a/.pipelines/build-localization.cmd
+++ b/.pipelines/build-localization.cmd
@@ -26,4 +26,8 @@ dotnet "%XLocPath%\tools\netcore\Microsoft.Localization.XLoc.dll" /f "%LocProjec
echo Localization build finished with exit code '%errorlevel%'.
+rem Move UWP resource files to correct file paths as per file path expected by UWP project
+cd %RepoRootWithoutBackslash%
+powershell -NonInteractive -executionpolicy Unrestricted ".\tools\localization\move_uwp_resources.ps1"
+
exit /b %errorlevel%
diff --git a/.pipelines/ci/templates/build-powertoys-steps.yml b/.pipelines/ci/templates/build-powertoys-steps.yml
index 4102ac53c1..730c16e8fd 100644
--- a/.pipelines/ci/templates/build-powertoys-steps.yml
+++ b/.pipelines/ci/templates/build-powertoys-steps.yml
@@ -81,6 +81,7 @@ steps:
configuration: '$(BuildConfiguration)'
testSelector: 'testAssemblies'
testAssemblyVer2: |
+ **\Microsoft.Plugin.Folder.UnitTest.dll
**\Microsoft.Plugin.Program.UnitTests.dll
**\Microsoft.Plugin.Calculator.UnitTest.dll
**\Microsoft.Plugin.Uri.UnitTests.dll
diff --git a/.pipelines/pipeline.user.windows.yml b/.pipelines/pipeline.user.windows.yml
index c526082ed3..007a9a0bca 100644
--- a/.pipelines/pipeline.user.windows.yml
+++ b/.pipelines/pipeline.user.windows.yml
@@ -41,6 +41,15 @@ restore:
build:
commands:
+ # Localize the files after the build procedure to avoid existing localized files from getting overwritten. To be moved before the Build PowerToys step once the lcl files have been checked in. Tracked at https://github.com/microsoft/PowerToys/issues/6046
+ - !!buildcommand
+ name: 'Localize Power Toys'
+ command: '.pipelines\build-localization.cmd'
+ artifacts:
+ - from: 'out\loc'
+ to: 'loc'
+ include:
+ - '**/*'
- !!buildcommand
name: 'Build Power Toys'
command: '.pipelines\build.cmd'
@@ -56,25 +65,7 @@ build:
- 'action_runner.exe'
- 'modules\ColorPicker\ColorPicker.dll'
- 'modules\ColorPicker\ColorPicker.exe'
- - 'modules\ImageResizer\ar\ImageResizer.resources.dll'
- - 'modules\ImageResizer\bg\ImageResizer.resources.dll'
- - 'modules\ImageResizer\ca\ImageResizer.resources.dll'
- - 'modules\ImageResizer\cs\ImageResizer.resources.dll'
- - 'modules\ImageResizer\de\ImageResizer.resources.dll'
- - 'modules\ImageResizer\es\ImageResizer.resources.dll'
- - 'modules\ImageResizer\eu-ES\ImageResizer.resources.dll'
- - 'modules\ImageResizer\fr\ImageResizer.resources.dll'
- - 'modules\ImageResizer\he\ImageResizer.resources.dll'
- - 'modules\ImageResizer\hu\ImageResizer.resources.dll'
- - 'modules\ImageResizer\it\ImageResizer.resources.dll'
- - 'modules\ImageResizer\nb-NO\ImageResizer.resources.dll'
- - 'modules\ImageResizer\nl\ImageResizer.resources.dll'
- - 'modules\ImageResizer\pl\ImageResizer.resources.dll'
- - 'modules\ImageResizer\pt-BR\ImageResizer.resources.dll'
- - 'modules\ImageResizer\ru\ImageResizer.resources.dll'
- - 'modules\ImageResizer\sk\ImageResizer.resources.dll'
- - 'modules\ImageResizer\tr\ImageResizer.resources.dll'
- - 'modules\ImageResizer\zh-Hans\ImageResizer.resources.dll'
+ - '**\*.resources.dll'
- 'modules\FancyZones\fancyzones.dll'
- 'modules\FancyZones\FancyZonesEditor.exe'
- 'modules\FileExplorerPreview\MarkdownPreviewHandler.dll'
@@ -152,15 +143,6 @@ build:
- 'PowerToysSetup-*.exe'
signing_options:
sign_inline: true # This does signing a soon as this command completes
- # Localize the files after the build procedure to avoid existing localized files from getting overwritten. To be moved before the Build PowerToys step once the lcl files have been checked in. Tracked at https://github.com/microsoft/PowerToys/issues/6046
- - !!buildcommand
- name: 'Localize Power Toys'
- command: '.pipelines\build-localization.cmd'
- artifacts:
- - from: 'out\loc'
- to: 'loc'
- include:
- - '**/*'
#package:
@@ -192,4 +174,8 @@ static_analysis_options:
- '**/webpack.config.js'
- '**/webpack.serve.config.js'
- '**/dist/bundle.js'
+ policheck_options:
+ files_to_scan:
+ - exclude:
+ - '**/*.lcl'
diff --git a/community.md b/COMMUNITY.md
similarity index 100%
rename from community.md
rename to COMMUNITY.md
diff --git a/contributing.md b/CONTRIBUTING.md
similarity index 100%
rename from contributing.md
rename to CONTRIBUTING.md
diff --git a/PowerToys.sln b/PowerToys.sln
index e16a3fac32..c44829d472 100644
--- a/PowerToys.sln
+++ b/PowerToys.sln
@@ -267,6 +267,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Setting
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.Calculator.UnitTest", "src\modules\launcher\Plugins\Microsoft.Plugin.Calculator.UnitTest\Microsoft.Plugin.Calculator.UnitTest.csproj", "{632BBE62-5421-49EA-835A-7FFA4F499BD6}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.Folder.UnitTests", "src\modules\launcher\Plugins\Microsoft.Plugin.Folder.UnitTests\Microsoft.Plugin.Folder.UnitTests.csproj", "{4FA206A5-F69F-4193-BF8F-F6EEB496734C}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "logging", "src\logging\logging.vcxproj", "{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@@ -537,6 +541,14 @@ Global
{632BBE62-5421-49EA-835A-7FFA4F499BD6}.Debug|x64.Build.0 = Debug|x64
{632BBE62-5421-49EA-835A-7FFA4F499BD6}.Release|x64.ActiveCfg = Release|x64
{632BBE62-5421-49EA-835A-7FFA4F499BD6}.Release|x64.Build.0 = Release|x64
+ {4FA206A5-F69F-4193-BF8F-F6EEB496734C}.Debug|x64.ActiveCfg = Debug|x64
+ {4FA206A5-F69F-4193-BF8F-F6EEB496734C}.Debug|x64.Build.0 = Debug|x64
+ {4FA206A5-F69F-4193-BF8F-F6EEB496734C}.Release|x64.ActiveCfg = Release|x64
+ {4FA206A5-F69F-4193-BF8F-F6EEB496734C}.Release|x64.Build.0 = Release|x64
+ {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.ActiveCfg = Debug|x64
+ {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.Build.0 = Debug|x64
+ {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x64.ActiveCfg = Release|x64
+ {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -614,6 +626,8 @@ Global
{B81FB7B6-D30E-428F-908A-41422EFC1172} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
{0F85E674-34AE-443D-954C-8321EB8B93B1} = {C3081D9A-1586-441A-B5F4-ED815B3719C1}
{632BBE62-5421-49EA-835A-7FFA4F499BD6} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
+ {4FA206A5-F69F-4193-BF8F-F6EEB496734C} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
+ {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F} = {1AFB6476-670D-4E80-A464-657E01DFF482}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
diff --git a/README.md b/README.md
index 64c79e9f4f..d2f955ffa4 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,9 @@
Microsoft PowerToys is a set of utilities for power users to tune and streamline their Windows experience for greater productivity. Inspired by the [Windows 95 era PowerToys project](https://en.wikipedia.org/wiki/Microsoft_PowerToys), this reboot provides power users with ways to squeeze more efficiency out of the Windows 10 shell and customize it for individual workflows. A great overview of the Windows 95 PowerToys can be found [here](https://socket3.wordpress.com/2016/10/22/using-windows-95-powertoys/).
-[What's Happening](#whats-happening) | [Downloading & Release notes][github-release-link] | [Contributing to PowerToys](#contributing) | [Known issues](#known-issues)
+For a video overview of PowerToys, including install steps and a walkthrough of the available utilities, check out the [PowerToys: Utilities to customize Windows 10](https://www.youtube.com/watch?v=F-d7KiwpnMA) episode of Tabs vs Spaces on YouTube.
+
+[Downloading & Release notes][github-release-link] | [What's Happening](#whats-happening) | [Contributing to PowerToys](#contributing) | [Known issues](#known-issues)
## Build status
@@ -80,7 +82,7 @@ Preview Pane is an existing feature in the File Explorer. To enable it, you jus
[
](https://aka.ms/PowerToysOverview_VideoConference) [Video Conference Mute](https://aka.ms/PowerToysOverview_VideoConference) is a quick and easy way to do a global "mute" of both your microphone and webcam via Win+N. Just set your webcam in the target application to the PowerToys VideoConference camera.
-**Note:** This is only included in the [pre-release version of PowerToys installer][github-prerelease-link]. This PowerToy requires Windows 10 1903 (build 18362) or later.
+**Note:** This is only included in the [pre-release experimental version of PowerToys installer][github-prerelease-link]. This PowerToy requires Windows 10 1903 (build 18362) or later.
@@ -97,7 +99,7 @@ Preview Pane is an existing feature in the File Explorer. To enable it, you jus
### Via GitHub with EXE [Recommended]
-Install from the [Microsoft PowerToys GitHub releases page][github-release-link]. Click on `Assets` to show the files available in the release and then click on `PowerToysSetup-0.21.1-x64.exe` to download the PowerToys installer.
+Install from the [Microsoft PowerToys GitHub releases page][github-release-link]. Click on `Assets` to show the files available in the release and then click on `PowerToysSetup-0.23.2-x64.exe` to download the PowerToys installer.
This is our preferred method.
@@ -108,9 +110,13 @@ Download PowerToys from [WinGet](https://github.com/microsoft/winget-cli/release
WinGet install powertoys
```
+### Experiential PowerToys utility with Video conference muting
+
+Install the [pre-release experimental version of PowerToys][github-prerelease-link] to try out this version. It includes all improvements from 0.23 in addition to the Video conference utility. Click on `Assets` to show the files available in the release and then download the .exe installer.
+
### Other install methods
-There are [community driven install methods](./doc/unofficalInstallMethods.md) such as Chocolatey and Scoop. If these are your perferred install solutions, this will have the install instructions.
+There are [community driven install methods](./doc/unofficalInstallMethods.md) such as Chocolatey and Scoop. If these are your preferred install solutions, this will have the install instructions.
### Known issues
@@ -126,74 +132,77 @@ We currently support the matrix below.
## What's Happening
-### August 2020 Update
+### September 2020 Update
-Our goals for 0.21 release cycle was to focus on stability, localization and quality of life improvements for both the development team and our end users.
+Our goals for 0.23 release cycle was to focus on stability, accessibility, localization and quality of life improvements for both the development team and our end users. We have a full accessibility pass being done starting end of September to audit all of PowerToys. Our localization efforts now had data flowing both directions as well.
-One of the longer term goal items we have made progress on is the Out of Box experience / initial onboarding experience (OOBE) improvements.[@Niels9001](https://github.com/niels9001/) created a [wicked awesome proof of concept of what the OOBE experience could be](https://github.com/microsoft/PowerToys/issues/1285#issuecomment-679268558). We are pretty stoked about this since it handles one an important item, critical shortcut default adjustments.
+Our [prioritized roadmap][roadmap] of features and utilites that the core team is focusing on.
-#### Highlights from August
+#### Highlights from September
-- We shipped [v0.21][github-release-link]!
-- [Video conference muting first public release][vidConfOverview]
+- We shipped [v0.23][github-release-link]! (0.24 Experimental build coming shortly)
-**PT Run:**
-- Removed need for space in action keywords. This means you now can type `>ipconfig`
-- Icon caches fixed and now has colored icons
-- Improved font rendering via ClearType (Shout out to [@AnuthaDev](https://github.com/AnuthaDev) doing the heavy lifting here)
-- Result speed improvements
-- URLs are supported
-- Fixed bugs including calculating bugs
+**General**
-**FancyZone:**
-- Win+Arrow key is directional based on zone rect
-- Fixed bugs
+- Localization pipeline is flowing from our Github to the loc system and back. 0.25 should be localized now.
+- The EXE installer should be at parity now with the MSI. Please go to the wiki for [installer args](https://github.com/microsoft/PowerToys/wiki/Installer-arguments-for-exe)
-**Runner:**
-- Fixed toast notifications running elevated from non-admin account
+**FancyZones**
-**Shortcut Guide:**
-- Improved vkey catching which will fix some use cases of it not showing up
+- Fixed bug on not seeing a newly attached screen
+- Fixed spanning across monitors bug
+- Added in default layout for new users, a Priority Grid
+- Added keyboard support to grow / shrink to multiple zones
+- General bug fixes
-**SVG in File Explorer:**
-- Embedded image tags will now render in Explorer
+**PT Run**
-**Color Picker:**
-- Fixed bug where it would launch via false positive keystrokes
+- Multiple crash bugs fixed. Prioritized any users reported along with top hits from Watson reporting
+- Stopped PT Run from interfering with an install
+- Fixed folder bug if it had a # in it (Thanks @jjw24 for the PR!)
+- Fixed a screen flicker for
+- General bug fixes
+- Allow Command Line args in PowerToys Run (Thanks @@royvou)
-**Accessibility:**
-- Settings, PT Run and KBM undergoing improvements
+**Keyboard manager**
-**Localization:**
-- Pipeline is now setup and will be doing a full E2E pass on all utilities shortly.
+- Multiple crash bugs fixed. Prioritized any users reported along with top hits from Watson reporting
+- Fixed multiple accessibility issues.
+- General bug fixes
-**Dev quality of life improvements:**
-- Continued warning count reduction. This release ~80 removed
-- StyleCop enabled E2E
-- FxCop starting to be added in E2E
+**Preview Pane**
-#### New experiential PowerToys utility - Video conference muting:
+- Added in Frontmatter and better (but still basic) latex support.
-**Note:** This is only included in the [pre-release version of PowerToys installer][github-prerelease-link]. This PowerToy requires Windows 10 1903 (build 18362) or later.
+**Settings**
-Back in the June timeframe, we started prototyping an idea. With COVID-19, we're all multi-tasking and trying to make the best of everything and being able to quickly mute while on a conference call is critical regardless of where you are on your computer and what application has focus.
+- Fixed scaling issue for responsive design on Image Resizer
+- Fixed crash on empty color value.
+- Fixed crash for toggling FancyZones on/off
+- Fixed 0x00 NFTS crash for settings
+- Fixed multiple accessibility issues.
+- Layout adjustments (Thanks @niels9001)
+- General bug fixes
-The utility will mute not just your audio but your video as well with a single keystroke. You can do audio, video both. We knew this would impact our roadmap and goals but felt extremely strong that this is the right decision. We're all multi-tasking and trying to make the best of everything and being able to quickly mute while on a conference call is critical regardless of where you are on your computer.
+**Dev related**
-We know we have some issues and we have a [master tracking issue - #6246](https://github.com/microsoft/PowerToys/issues/6246). We know a certain laptops currently the video forwarding does not work and are proactively working on fixing this.
+- FxCop is being rolled out across all PowerToys. This should catch a lot of possible leaks.
+- Unified PT Run's log system
+- PT Run's calc plugin now has unit tests (Thanks @P-Storm)
+- Dev setup install script now supports VS preview (Thanks @TobiasSekan)
+- @CaelestisZ, @kameshkotwani, @adriancampos, @RahulDas782 for doc tweaks
+- Thanks @Aaron-Junker, @jay-o-way and @htcfreek for helping triage!
+- Thanks for everyone that filled an issue. It really does help us prioritize
-To use:
+#### Video / GIF capture functional spec for public review
-- Set your camera to the PowerToys Video driver in the target application
-- Win+N to toggle both Audio and Video at the same time
-- Win+Shift+O to toggle video
-- Win+Shift+A to toggle microphone
+Deondre Davis created our [functional spec for creating a light weight, video / GIF recording tool](https://github.com/microsoft/PowerToys/pull/6900). We encourage everyone to review it and please leave comments in the pull request so we can adjust as needed. We'll be closing it for feedback on October 12th, 2020.
-For a more information, head over to the [Video conference mute overview][vidConfOverview]
+This is for work [post-stabilization of current roadmap work](https://github.com/microsoft/PowerToys/wiki/Roadmap#post-stabilization) and is only the spec for what we are thinking about support. Just want to set expectations here.
-### What is being planned for 0.23
+### What is being planned for 0.25
-For [0.23](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen+is%3Aissue+project%3Amicrosoft%2FPowerToys%2F12), we are proactively working on:
+For [0.25](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen+is%3Aissue+project%3Amicrosoft%2FPowerToys%2F13), we are proactively working on:
- Stability
- Localization
@@ -202,7 +211,7 @@ For [0.23](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen+is%3Aissue+
### PowerToys roadmap
-Our [prioritized roadmap][roadmap] of features and utilites that the core team is focusing on..
+Our [prioritized roadmap][roadmap] of features and utilites that the core team is focusing on.
## Developer Guidance
@@ -212,7 +221,7 @@ Please read the [developer docs](/doc/devdocs) for a detailed breakdown.
This project welcomes contributions of all types. Help spec'ing, design, documentation, finding bugs are ways everyone can help on top of coding features / bug fixes. We are excited to work with the power user community to build a set of tools for helping you get the most out of Windows.
-We ask that **before you start work on a feature that you would like to contribute**, please read our [Contributor's Guide](contributing.md). We will be happy to work with you to figure out the best approach, provide guidance and mentorship throughout feature development, and help avoid any wasted or duplicate effort.
+We ask that **before you start work on a feature that you would like to contribute**, please read our [Contributor's Guide](CONTRIBUTING.md). We will be happy to work with you to figure out the best approach, provide guidance and mentorship throughout feature development, and help avoid any wasted or duplicate effort.
### ⚠ State of code ⚠
@@ -233,7 +242,7 @@ The application logs basic telemetry. Our Telemetry Data page (Coming Soon) has
[oss-CLA]: https://cla.opensource.microsoft.com
[oss-conduct-code]: CODE_OF_CONDUCT.md
[github-release-link]: https://github.com/microsoft/PowerToys/releases/
-[github-prerelease-link]: https://github.com/microsoft/PowerToys/releases/tag/v0.22.0-Experimental
+[github-prerelease-link]: https://github.com/microsoft/PowerToys/releases/tag/v0.24.0-Experimental
[roadmap]: https://github.com/microsoft/PowerToys/wiki/Roadmap
[privacyLink]: http://go.microsoft.com/fwlink/?LinkId=521839
[vidConfOverview]: https://aka.ms/PowerToysOverview_VideoConference
diff --git a/SUPPORT.md b/SUPPORT.md
new file mode 100644
index 0000000000..ce239dc591
--- /dev/null
+++ b/SUPPORT.md
@@ -0,0 +1,18 @@
+# Support
+
+## How to file issues and get help
+
+This project uses [GitHub Issues][gh-issue] to [track bugs][gh-bug] and [feature requests][gh-feature]. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or
+feature request as a new Issue.
+
+For help and questions about using this project, please look at our Wiki for using PowerToys and our [Contributor's Guide][contributor] if you want to work on PowerToys.
+
+## Microsoft Support Policy
+
+Support for PowerToys is limited to the resources listed above.
+
+[gh-issue]: https://github.com/microsoft/PowerToys/issues/new/choose
+[gh-bug]: https://github.com/microsoft/PowerToys/issues/new?assignees=&labels=Issue-Bug&template=bug_report.md&title=
+[gh-feature]: https://github.com/microsoft/PowerToys/issues/new?assignees=&labels=&template=feature_request.md&title=
+[wiki]: https://github.com/microsoft/PowerToys/wiki
+[contributor]: https://github.com/microsoft/PowerToys/blob/master/CONTRIBUTING.md
diff --git a/deps/cxxopts b/deps/cxxopts
new file mode 160000
index 0000000000..12e496da3d
--- /dev/null
+++ b/deps/cxxopts
@@ -0,0 +1 @@
+Subproject commit 12e496da3d486b87fa9df43edea65232ed852510
diff --git a/deps/cxxopts.props b/deps/cxxopts.props
new file mode 100644
index 0000000000..25cc72b199
--- /dev/null
+++ b/deps/cxxopts.props
@@ -0,0 +1,7 @@
+
+
+
+ $(MSBuildThisFileDirectory)cxxopts\include;%(AdditionalIncludeDirectories)
+
+
+
diff --git a/deps/spdlog b/deps/spdlog
new file mode 160000
index 0000000000..cbe9448650
--- /dev/null
+++ b/deps/spdlog
@@ -0,0 +1 @@
+Subproject commit cbe9448650176797739dbab13961ef4c07f4290f
diff --git a/deps/spdlog.props b/deps/spdlog.props
new file mode 100644
index 0000000000..2689f24231
--- /dev/null
+++ b/deps/spdlog.props
@@ -0,0 +1,8 @@
+
+
+
+ $(MSBuildThisFileDirectory)spdlog\include;%(AdditionalIncludeDirectories)
+ SPDLOG_WCHAR_TO_UTF8_SUPPORT;SPDLOG_COMPILED_LIB;%(PreprocessorDefinitions)
+
+
+
diff --git a/doc/devdocs/logging.md b/doc/devdocs/logging.md
new file mode 100644
index 0000000000..ba3a50dd2c
--- /dev/null
+++ b/doc/devdocs/logging.md
@@ -0,0 +1,9 @@
+# How to use
+
+We use the awesome [spdlog](https://github.com/gabime/spdlog) library for logging as a git submodule under the `deps` directory. To use it in your project, just include [spdlog.props](../../deps/spdlog.props) in a .vcxproj like this:
+
+```xml
+
+```
+It'll add the required include dirs and link the library binary itself.
+You can see many example usage of the library in its repository or in the [bootstrapper project](../../installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp).
diff --git a/doc/devdocs/modules/launcher/architecture.md b/doc/devdocs/modules/launcher/architecture.md
new file mode 100644
index 0000000000..f0287f56dc
--- /dev/null
+++ b/doc/devdocs/modules/launcher/architecture.md
@@ -0,0 +1,48 @@
+# Architecture
+
+## Overview
+`PowerToys Run` is a plugin-based .net core desktop application. It is written in WPF using `Model-View-ViewModel (MVVM)` structural design pattern. This article provides an overview of `PowerToys Run` architecture and introduces major components in the data flow.
+
+Note : We refer to base application without plugins as `PowerLauncher`, which is same as the name of startup WPF project.
+
+## UI
+PowerToys Run UI is written in the WPF framework. The UI code is present in the Powerlauncher project and is spanned across three high-level components: [`MainWindow.xaml`](/src/modules/launcher/PowerLauncher/MainWindow.xaml), [`LauncherControl.xaml`](/src/modules/launcher/PowerLauncher/LauncherControl.xaml) and [`ResultList.xaml`](/src/modules/launcher/PowerLauncher/LauncherControl.xaml). These components are discussed below.
+
+
+**Fig 1: PowerToys Run UI architecture**
+
+1. **[`MainWindow.xaml`](/src/modules/launcher/PowerLauncher/MainWindow.xaml)**: This is the outermost-level UI control. It is composed of lower-level UI components such as [`LauncherControl.xaml`](/src/modules/launcher/PowerLauncher/LauncherControl.xaml) and [`ResultList.xaml`](/src/modules/launcher/PowerLauncher/LauncherControl.xaml). The corresponding code-behind file implements all the UI related functionalities such as autosuggest, key-bindings, toggling visibility of WPF window and animations.
+2. **[`LauncherControl.xaml`](/src/modules/launcher/PowerLauncher/LauncherControl.xaml)**: This control implements the UI component for editing query text.(marked in red in Fig 1) It consists of two overlapping WPF controls, `TextBox` and `TextBlock`. The outer `TextBox` is used for editing query whereas the inner `TextBlock` is used to display autosuggest text.
+3. **[`ResultList.xaml`](/src/modules/launcher/PowerLauncher/LauncherControl.xaml)**: This control implements the UI component for displaying results (marked in green in Fig 1). It consists of a `ListView` WPF control with a custom `ItemTemplate` to display application logo, name, tooltip text, and context menu.
+
+## Data flow
+The backend code is written using the `Model-View-ViewModel (MVVM)` structural design pattern. Plugins act as `Model` in this project. A detailed overview of the project's structure is given [here](/doc/devdocs/modules/launcher/project_structure.md).
+
+#### Flow of data between UI(view) and ViewModels
+Data flow between View and ViewModel follows typical `MVVM` scheme. Properties in viewModels are bound to WPF controls and when these properties are updated, `INotifyPropertyChanged` handler is invoked, which in turn updates UI. The diagram below provides a rough sketch of the components involved.
+
+**Fig 2: Flow of data between UI and ViewModels.**
+
+#### Flow of data between ViewModels and Plugins(Model)
+`PowerLauncher` interact with plugins using [`IPlugin`](/src/modules/launcher/Wox.Plugin/IPlugin.cs) and `IDelayedExecutionPlugin` interface. [`IPlugin`](/src/modules/launcher/Wox.Plugin/IPlugin.cs) is used for initialization and making queries which are fast (typically return results in less than 100ms).[`IDelayedExecutionPlugin`](/src/modules/launcher/Wox.Plugin/IDelayedExecutionPlugin.cs) is used for long-running queries and is implemented only when required. For example, [`IDelayedExecutionPlugin`](/src/modules/launcher/Wox.Plugin/IDelayedExecutionPlugin.cs) is implemented by indexer plugin for searching files with names of form \*abc\*.
+```
+ public interface IPlugin
+ {
+ // Query plugin
+ List Query(Query query);
+
+ // Initialize plugin
+ void Init(PluginInitContext context);
+ }
+
+ public interface IDelayedExecutionPlugin : IFeatures
+ {
+ // Query plugin
+ List Query(Query query, bool delayedExecution);
+ }
+```
+
+**Fig 3: Flow of data between ViewModels and Plugins.**
+
+#### Requesting services from powerlauncher
+Plugins could use the [`IPublicAPI`](/src/modules/launcher/Wox.Plugin/IPublicAPI.cs) interface to request services such as getting the current theme (for deciding logo background), displaying messages to the user, and toggling the visibility of PowerLauncher.
\ No newline at end of file
diff --git a/doc/devdocs/modules/launcher/debugging.md b/doc/devdocs/modules/launcher/debugging.md
new file mode 100644
index 0000000000..018d36e2c4
--- /dev/null
+++ b/doc/devdocs/modules/launcher/debugging.md
@@ -0,0 +1,20 @@
+# Debugging
+`PowerToys Run` is a single exe file associated with `launcher.exe` process and debugger should be attached to this process. There are two approaches to debug `PowerToys Run`. Both these approaches differ in the compile-time and the range of functionalities that could be debugged. These methods are discussed in detail in the following sections.
+
+
+## Debugging Prerequisite
+Setup development environment for PowerToys by following instruction [here.](https://github.com/microsoft/PowerToys/tree/master/doc/devdocs#prerequisites-for-compiling-powertoys)
+
+## Direct debugging
+This approach is used to test UI, plugins, and core `PowerToys Run` functionality. This **cannot** be used to test `PowerToys Run` settings. The approach is significantly faster compared to `Debugging with runner`, as it requires compiling projects relevant to `PowerToys Run`. Please follow the steps below for direct debugging.
+1. Right-click on `modules->launcher->PowerLauncher` and select `Set as startup Project`.
+2. Press `F5` to start debugging.
+
+## Debugging with runner
+This approach can be used to test UI, plugins, core `PowerToys Run` functionality and `PowerToys Run` settings. This approach **cannot** be used to debugg functions that execute on starting `launcher.exe` process. This requires building runner along with all the other modules on first compile, making it slower than `Direct debugging` approach. The subsequent compilations should be fast.
+1. Right-click on `runner` and select `Set as startup Project`.
+2. Press `F5` to start debugging.
+3. Attach debugger to `launcher.exe` process.
+ 1. Go to `Debug->Attach to process..`
+ 2. Filter and select `launcher.exe` process.
+ 3. Click on `Attach`.
\ No newline at end of file
diff --git a/doc/devdocs/modules/launcher/plugins/calculator.md b/doc/devdocs/modules/launcher/plugins/calculator.md
new file mode 100644
index 0000000000..8d5c44fa2b
--- /dev/null
+++ b/doc/devdocs/modules/launcher/plugins/calculator.md
@@ -0,0 +1,23 @@
+# Calculator Plugin
+The Calculator plugin as the name suggests is used to perform calculations on the user entered query.
+
+
+
+### [`CalculateHelper`](src/modules/launcher/Plugins/Microsoft.Plugin.Calculator/CalculateHelper.cs)
+- The [`CalculateHelper.cs`](src/modules/launcher/Plugins/Microsoft.Plugin.Calculator/CalculateHelper.cs) class checks to see if the user entered query is a valid input to the calculator and only if the input is valid does it perform the operation.
+- It does so by matching the user query to a valid regex.
+
+### [`CalculateEngine`](src/modules/launcher/Plugins/Microsoft.Plugin.Calculator/CalculateEngine.cs)
+- The main computation is done in the [`CalculateEngine.cs`](src/modules/launcher/Plugins/Microsoft.Plugin.Calculator/CalculateEngine.cs) file using the `Mages` library.
+
+```csharp
+var result = CalculateEngine.Interpret(query.Search, CultureInfo.CurrentUICulture);
+```
+
+### [`CalculateResult`](src/modules/launcher/Plugins/Microsoft.Plugin.Calculator/CalculateResult.cs)
+- The class which encapsulates the result of the computation.
+- It comprises of the `Result` and `RoundedResult` properties.
+
+### Score
+The score of each result from the calculator plugin is `300`.
+
diff --git a/doc/devdocs/modules/launcher/plugins/folder.md b/doc/devdocs/modules/launcher/plugins/folder.md
new file mode 100644
index 0000000000..2248b16d86
--- /dev/null
+++ b/doc/devdocs/modules/launcher/plugins/folder.md
@@ -0,0 +1,17 @@
+# Folder Plugin
+The Folder plugin is used to navigate the directory structure and display the sub-folders and files within a folder.
+
+
+### [`FolderHelper.cs`](src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Sources/Path/FolderHelper.cs)
+- The [`FolderHelper`](src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Sources/Path/FolderHelper.cs) class leverages the `DriveInformation` and `folderLinks` to get the folder results for a user query.
+- The [`DriveInformation`](src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Sources/Path/DriveInformation.cs) class gets the list of all drives on the system.
+- The [`FolderLink`](src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Sources/FolderLink.cs) object corresponds to a user created link for frequently accessed projects. This was inherited from Wox but is presently not functional as we don't have the UI setup in settings to get this user input. Each folderLink object has a `nickname`, which is the name of the folder and this can be used to directly access that folder instead of entering the entire path.
+
+### [`IFolderProcessor.cs`](src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Sources/IFolderProcessor.cs)
+The `IFolderProcessor` utilizes the `FolderHelper` class to extract the folders and return the results.
+There are two types of Folder Processors, based on the type of information they are processing -
+1. [`UserFolderProcessor`](src/modules/launcher/Plugins/Microsoft.Plugin.Folder/UserFolderProcessor.cs) - This Processor is currently not used in PT Run but it is used to process the user created folder links.
+2. [`InternalDirectoryProcessor`](src/modules/launcher/Plugins/Microsoft.Plugin.Folder/InternalDirectoryProcessor.cs) - This processor is used to retrieve the files and folders located within the current drive or shared folder.
+
+### Score
+The first result is of score 500 and the following results are scored 10.
\ No newline at end of file
diff --git a/doc/devdocs/modules/launcher/plugins/indexer.md b/doc/devdocs/modules/launcher/plugins/indexer.md
new file mode 100644
index 0000000000..63519a0583
--- /dev/null
+++ b/doc/devdocs/modules/launcher/plugins/indexer.md
@@ -0,0 +1,39 @@
+# Indexer Plugin
+The indexer plugin is used to search for files within the indexed locations of the system.
+
+
+
+### [Drive Detection](src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/DriveDetection)
+- There are two indexing modes in Windows:
+ 1. **Classic mode**: Only the desktop and certain customizable locations in the system are indexed. All the systems have the classic mode enabled by default.
+ 2. **Enhanced Mode**: This mode indexes the entire PC when enabled. The user can exclude certain locations from being indexed in this mode from the Windows Search settings options.
+- A drive detection warning is displayed to the users when only the custom mode is enabled on the system informing the user that not all the locations on their PC are indexed as this could lead to some results not showing up.
+- The [`IndexerDriveDetection.cs`](src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/DriveDetection/IndexerDriveDetection.cs) file gets the status of the drive detection checkbox in the settings UI and depending on whether the enhanced mode is enabled or disabled, displays the warning.
+- To determine whether the `EnhancedMode` is enabled or not, we check the local machine registry entry for `EnableFindMyFiles`. If it is set to 1, the enhanced mode is enabled.
+
+### [`OleDBSearch`](src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/SearchHelper/OleDBSearch.cs)
+- The `Query` function within the [`OleDBSearch.cs`](src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/SearchHelper/OleDBSearch.cs) class takes in the query and the connection string to the SystemIndex catalog as arguments and returns a list of results.
+- It first opens a [connection][OLEDBConnection] to the Windows Indexer database, creates an [OleDB command][OLEDBCommand] and executes the command to get a list of results.
+
+### [`WindowsSearchAPI`](src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/SearchHelper/WindowsSearchAPI.cs)
+- The [`WindowsSearchAPI`](src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/SearchHelper/WindowsSearchAPI.cs) class leverages the [`OleDBSearch.cs`](src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/SearchHelper/OleDBSearch.cs) class to execute the query.
+- It initializes the `QueryHelper` in the `InitQueryHelper()` function by creating a catalog manager to the SystemIndex catalog.
+- The metadata is initialized within the query helper, such as the number of results to retrive, the type of information to retrieve for each file (currently we retrieve the item URL, the file name and the file attributes).
+- The query helper matches results using the name of the file only and they are sorted by the last modified date, ensuring that the recently modified files are ranked higher.
+- The File attributes are utilized to filter out hidden files from being displayed.
+
+### Additional Information
+- There are two major types of queries generated by the indexer plugin:
+ 1. Full Text predicates - eg: [CONTAINS][Contains]
+ 2. Non-Full Text predicates - eg: [LIKE][Like]
+- The Full text predicates are much faster than non-full text predicates as they are based on finding matches rather than comparing the query with each item in the indexer database. Hence, queries which have the `CONTAINS` keyword are much faster than those which contain the `LIKE` keyword.
+- To prevent the indexer query from taking a long time and blocking the UI thread, there are two types of indexer queries which are executed. A simplified query and a full query, without and with the `LIKE` keyword respectively.
+- The result list is updated with the results of the full query once they are obtained.
+
+### Score
+Each of the indexer plugin results has a score set to 0 so they are present at the bottom of the list.
+
+[OLEDBCommand]: https://docs.microsoft.com/en-us/dotnet/api/system.data.oledb.oledbcommand?view=dotnet-plat-ext-3.1
+[OLEDBConnection]: https://docs.microsoft.com/en-us/dotnet/api/system.data.oledb.oledbconnection?view=dotnet-plat-ext-3.1
+[Contains]: https://docs.microsoft.com/en-us/windows/win32/search/-search-sql-contains
+[Like]: https://docs.microsoft.com/en-us/windows/win32/search/-search-sql-like
\ No newline at end of file
diff --git a/doc/devdocs/modules/launcher/plugins/overview.md b/doc/devdocs/modules/launcher/plugins/overview.md
new file mode 100644
index 0000000000..ea6233c6c4
--- /dev/null
+++ b/doc/devdocs/modules/launcher/plugins/overview.md
@@ -0,0 +1,35 @@
+# Structural Overview
+The following basic functions are common to each of the plugins. They perform some rudimentary operations such as initialization of the plugin, executing the query that has been entered, loading context menu icons, updating settings when configurations are altered in the settings UI, and updating the theme of the icons when the theme changed event is triggered.
+
+## IPlugin Interface
+Each plugin implements the `IPlugin` interface which comprises of the `Init()` and `Query()` functions.
+
+### `Init`
+- The `Init()` function initializes the context, storage and settings of each plugin. This is equivalent to a contructor and is the first function to be called in the `Main.cs` file for each plugin.
+
+### `Query`
+- For every query that the user enters into PT Run, the `PluginManager.cs` executes the `Query()` function in the `Main.cs` file corresponding to each Plugin.
+
+### Context Menu Icons
+- The `ContextMenus` are loaded for each result based on the type of the result.
+- The various types of `ContextMenu` functionalities are:
+ - Open containing folder
+ - Run as Administrator
+ - Open in console
+ - Copy path
+
+### UpdateSettings
+- This function updates the settings of each plugin based on the changes made by the user in the settings UI.
+- Eg: To disable drive detection in the indexer plugin, when the user checks or unchecks the drive detection check box, the `UpdateSettings()` function dispatches the changes in the check box to the plugin.
+
+### ThemeChanged
+- This function is invoked when there is a change in the theme of PT Run.
+- It is used to update the `IconPath` for each plugin based on the theme.
+
+### Save
+- This function saves the configurations of each plugin so that they can be loaded the next time.
+
+### Score
+- The user query is executed against each of the plugins and the result list view is updated with results from each of the plugins.
+- The ordering of the results is based on the `Score` of each Result.
+- Each plugin assigns a score to a result based on it's relevance. The results with higher scores are displayed higher in the list view and vice versa.
\ No newline at end of file
diff --git a/doc/devdocs/modules/launcher/plugins/program.md b/doc/devdocs/modules/launcher/plugins/program.md
new file mode 100644
index 0000000000..80eb221797
--- /dev/null
+++ b/doc/devdocs/modules/launcher/plugins/program.md
@@ -0,0 +1,43 @@
+# Program Plugin
+The program plugin as the name suggests is used to search for programs installed on the system.
+
+
+
+There are broadly two different categories of applications:
+
+1. Packaged applications
+2. Win32 applications
+
+### [UWP](src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/UWP.cs)
+- The logic for indexing Packaged applications is present within the [`UWP.cs`](src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/UWP.cs) file.
+- There can be multiple applications present within a package. The [`UWPApplication.cs`](src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/UWPApplication.cs) file encapsulates the properties of a packaged application.
+- To index packaged applications, the `PackageManager` retrives all the packages for the current user and indexes all the applications.
+- To retrieve the app icon for packaged applications, the assets path is retrieved from the `Application Manifest` file. There are multiple icons corresponding to each scale, target size and theme. The best icon is chosen given the theme of powerToys Run.
+
+### [Win32Program](src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/Win32Program.cs)
+- Win32 programs in the following locations are indexed by PT Run-
+ 1. Desktop
+ 2. Public Desktop (Applications present on the desktop of all the users)
+ 3. Registry (Some programs)
+ 4. Start Menu
+ 5. Common start menu (Applications which are common to all users)
+ 8. Locations pointed to by the PATH environment variable.
+- To prevent applications and shortcuts present in multiple locations from showing up as duplicate results, we consider apps with the same name, executable name and full path to be the same.
+- The subtitle of the application result is set based on it's application type. It could be one of the following:
+ 1. Lnk Shortcuts
+ 2. Appref files
+ 3. Internet shortcut - steam and epic games
+ 4. PWAs
+ 5. Run commands - these are indexed by the PATH environment variable
+
+### Score
+- The score for each application result is based on the how many letters are matched, how close the matched letters are to the actual word and the index of the matched characters.
+- There is a threshold score to decide the apps which are to be displayed and applications which have a lower score are not displayed by PT Run.
+
+### Update Program List in Runtime
+- Packaged and Win32 app helpers exist to reflect changes in the list of indexed apps when applications are installed on the system while PT Run is executing.
+- Packaged applications trigger events when the package is being installed and uninstalled. PT Run listens to those events to index applications which are newly installed or to delete an app which no longer exists from the database.
+- No such events exist for Win32 applications. We therefore use FileSystem Watchers to monitor the locations that we index for newly created, deleted or renamed application files and update the indexed Win32 catalog accordingly.
+
+### Additional Notes
+- Arguments can be provided to the program plugin by entering them after `--` (a double dash).
\ No newline at end of file
diff --git a/doc/devdocs/modules/launcher/plugins/shell.md b/doc/devdocs/modules/launcher/plugins/shell.md
new file mode 100644
index 0000000000..1b9b5b4672
--- /dev/null
+++ b/doc/devdocs/modules/launcher/plugins/shell.md
@@ -0,0 +1,14 @@
+# Shell Plugin
+- Shell plugin emulates the Windows Run Prompt (Win+R).
+- Shell Plugin is one of the non-global plugins which has an action keyword set to `>`.
+
+
+
+### Functionality
+- The Shell command expands environment variables, so `>%appdata%` works as expected.
+- On inheriting the Shell plugin from Wox, there are three different ways of executing a command, using the command prompt, powershell or the run prompt. To uphold the name of PT Run, the Shell plugin always executes commands as the Run prompt would.
+- The Shell plugin has a concept of history where the previously executed commands show up in the drop down list along with the number of times they have been executed.
+- The Run prompt has the folder plugin function where we can navigate to different locations and entering the path to a directory displays all the sub-directories. To prevent reimplementing this logic, the shell plugin references the folder plugin to implement this functionality.
+
+### Score
+The Shell plugin results have a very high score of 5000. Hence, they are one of the first results in the list.
diff --git a/doc/devdocs/modules/launcher/plugins/uri.md b/doc/devdocs/modules/launcher/plugins/uri.md
new file mode 100644
index 0000000000..dec3362729
--- /dev/null
+++ b/doc/devdocs/modules/launcher/plugins/uri.md
@@ -0,0 +1,19 @@
+# URI Plugin
+The URI Plugin, as the name suggests is used to dierctly run the URI that has been entered by the user as a query. This is done by parsing the entry and validating the URI, followed by executing it.
+
+
+
+### [`URI Parser`](src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/ExtendedUriParser.cs)
+- The [`ExtendedUriParser.cs`](src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/ExtendedUriParser.cs) file tries to parse the user input and returns a `System.Uri` result by using the `UriBuilder`.
+- It also captures other cases which the UriBuilder does not handle such as when the input ends with a `:`, `.` or `:/`.
+
+### [`URI Resolver`](src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/UriResolver.cs)
+- The [`UriResolver.cs`](src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/UriResolver.cs) file returns true for Valid hosts.
+- Currently there is no additional logic for filtering out invalid hosts and it always returns true for a valid Uri that was created by parsing the user query. It can be expanded in the future to filter out certain hosts.
+
+### Default Browser Icon
+- The icon for each uri result is that of the default browser set by the user.
+- These details are obtained from the user registry and updated each time the theme of PT Run is changed.
+
+### Score
+- All uri plugin results have a score of 0 which indicates that they would show up after each of the other plugins, other than the indexer plugin which also has a score of 0.
diff --git a/doc/devdocs/modules/launcher/plugins/windowwalker.md b/doc/devdocs/modules/launcher/plugins/windowwalker.md
new file mode 100644
index 0000000000..401cfa307a
--- /dev/null
+++ b/doc/devdocs/modules/launcher/plugins/windowwalker.md
@@ -0,0 +1,18 @@
+# Window Walker plugin
+The window walker plugin matches the user entered query with the open windows on the system.
+
+
+
+### [`OpenWindows.cs`](src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/OpenWindows.cs)
+- The window walker plugin uses the `EnumWindows` function to enumerate all the open windows in the [`OpenWindows.cs`](src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/OpenWindows.cs) class.
+
+
+### [`SearchController.cs`](src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/SearchController.cs)
+- The [`SearchController`](src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/SearchController.cs) encapsulates the functions needed to search and find matches.
+- It is responsible for updating the search text and performing a fuzzy search on all the open windows in an asynchronous manner.
+
+### [`Window.cs`](src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/Window.cs)
+- The [`Window`](src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/Window.cs) class represents a specific window and has functions to get the name of the process, the state of the window (whether it is visible or not), and the `SwitchTowindow` function which switches the desktop focus to the selected window. This action is performed when the user clicks on a window walker plugin result.
+
+### Score
+The window walker plugin uses [`FuzzyMatching`](src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Components/FuzzyMatching.cs) to get the matching indices and calculates the score by creating a 2 dimensional array of the window and the query text.
diff --git a/doc/devdocs/modules/launcher/project_structure.md b/doc/devdocs/modules/launcher/project_structure.md
new file mode 100644
index 0000000000..b76976d5c5
--- /dev/null
+++ b/doc/devdocs/modules/launcher/project_structure.md
@@ -0,0 +1,24 @@
+# Project Structure
+## Overview
+`PowerToys Run` is divided across several projects to keep a logical separation between plugins and core functionality. The following sections provide a brief overview of each project.
+
+
+Fig 1. Project along with their dependencies in `PowerToys Run` ecosystem.
+
+## Project Description
+#### [`PowerLauncher`](/src/modules/launcher/PowerLauncher)
+This is the startup project for the `PowerToys Run.` It is a WPF desktop application and follows the `Model-View-ViewModel (MVVM)` design pattern. Plugins play the role of `Model` and provide data to `ViewModel.`
+
+#### [`PowerLauncher.Telemetry`](/src/modules/launcher/PowerLauncher.Telemetry)
+[`PowerLauncher.Telemetry`](/src/modules/launcher/PowerLauncher.Telemetry) is a .net core project that contains telemetry events generated by `PowerLauncher.` These events have been discussed in detail [here](/doc/devdocs/modules/launcher/telemetry.md).
+
+#### [`Wox.Core`](/src/modules/launcher/Wox.Core)
+[`Wox.Core`](/src/modules/launcher/Wox.Core) is a .net core project that contains helper classes required by the `PowerLauncher` project. Two major functionalities encapsulated in this project are [`PluginManager`](/src/modules/launcher/Wox.Core/Plugin/PluginManager.cs) and [`Query Builder.`](/src/modules/launcher/Wox.Core/Plugin/QueryBuilder.cs) [`PluginManager`](/src/modules/launcher/Wox.Core/Plugin/PluginManager.cs) provides an interface for managing C# plugins. [`Query Builder.`](/src/modules/launcher/Wox.Core/Plugin/QueryBuilder.cs) decimate user-typed query string and creates a [`Query`](/src/modules/launcher/Wox.Plugin/Query.cs) object. [`Query`](/src/modules/launcher/Wox.Plugin/Query.cs) object contains the action keyword and cleaned query, which is then sent to all plugins.
+
+#### [`Wox.Infrastructure`](/src/modules/launcher/Wox.Infrastructure)
+[`Wox.Infrastructure`](/src/modules/launcher/Wox.Infrastructure) is a .net core project that contains helper classes required for logging, image manipulation, and storage by the `PowerLauncher` project and the plugins. [`ImageLoader.cs`](/src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs) class is used to load icons for `Win32` program. It also provides caching functionality to speed up image loading for frequently queried programs. [`Log.cs`](/src/modules/launcher/Wox.Infrastructure/Logger/Log.cs) provides an abstraction for logging error, information, and output to text files. These files are stored at `%userprofile%/appdata/local/microsoft/powertoys/powertoys run/Logs.`
+
+#### [`Wox.Plugin`](/src/modules/launcher/Wox.Plugin)
+[`Wox.Plugin`](/src/modules/launcher/Wox.Plugin) contains interfaces that facilitate communication between `PowerLauncher` and plugins. These interfaces have been discussed in detail [here](/doc/devdocs/modules/launcher/architecture.md#flow-of-data-between-viewmodels-and-pluginsmodel).
+
+
diff --git a/doc/devdocs/modules/launcher/readme.md b/doc/devdocs/modules/launcher/readme.md
new file mode 100644
index 0000000000..2267b8b688
--- /dev/null
+++ b/doc/devdocs/modules/launcher/readme.md
@@ -0,0 +1,14 @@
+# Table of Contents
+1. [Architecture](/doc/devdocs/modules/launcher/architecture.md)
+2. [Debugging](/doc/devdocs/modules/launcher/debugging.md)
+3. [Project Structure](/doc/devdocs/modules/launcher/project_structure.md)
+4. [Telemetry](/doc/devdocs/modules/launcher/telemetry.md)
+5. Plugins
+ - [Overview](/doc/devdocs/modules/launcher/plugins/overview.md)
+ - [Calculator](/doc/devdocs/modules/launcher/plugins/calculator.md)
+ - [Folder](/doc/devdocs/modules/launcher/plugins/folder.md)
+ - [Indexer](/doc/devdocs/modules/launcher/plugins/indexer.md)
+ - [Program](/doc/devdocs/modules/launcher/plugins/program.md)
+ - [Shell](/doc/devdocs/modules/launcher/plugins/shell.md)
+ - [Uri](/doc/devdocs/modules/launcher/plugins/uri.md)
+ - [Window Walker](/doc/devdocs/modules/launcher/plugins/windowwalker.md)
\ No newline at end of file
diff --git a/doc/devdocs/modules/launcher/telemetry.md b/doc/devdocs/modules/launcher/telemetry.md
new file mode 100644
index 0000000000..3e0c233517
--- /dev/null
+++ b/doc/devdocs/modules/launcher/telemetry.md
@@ -0,0 +1,14 @@
+# Telemetry
+## Overview
+`PowerLauncher.Telemetry` project contains telemetry events generated by `PowerToys Run.` These event classes are derived from the [`EventBase`](/src/common/ManagedTelemetry/Telemetry/Events/EventBase.cs) class and [`IEvent`](/src/common/ManagedTelemetry/Telemetry/Events/IEvent.cs) class. [`IEvent`](/src/common/ManagedTelemetry/Telemetry/Events/IEvent.cs) class provides the lowest level abstraction, containing attributes such as privacy tags needed for every telemetry data. [`EventBase`](/src/common/ManagedTelemetry/Telemetry/Events/EventBase.cs) class provides a higher-level abstraction, having attributes common to all `PowerToys` telemetry events.
+
+## Events
+The following events are generated by `PowerLauncher`:
+1. [`LauncherBootEvent`](/src/modules/launcher/PowerLauncher.Telemetry/Events/LauncherBootEvent.cs): This event captures the time taken by `PowerLauncher` to load all plugins, perform cold start, and setup UI environment.
+2. [`LauncherHideEvent`](/src/modules/launcher/PowerLauncher.Telemetry/Events/LauncherHideEvent.cs): This event is generated when the `PowerLauncher` window is hidden.
+3. [`LauncherColdStateHotkeyEvent`](/src/modules/launcher/PowerLauncher.Telemetry/Events/LauncherColdStateHotkeyEvent.cs): This event logs time from the first Alt+Space press till the `PowerLauncher` window is visible.
+4. [`LauncherFirstDeleteEvent`](/src/modules/launcher/PowerLauncher.Telemetry/Events/LauncherFirstDeleteEvent.cs): This event is generated after the first delete is pressed after `PowerLauncher` is visible.
+5. [`LauncherQueryEvent`](/src/modules/launcher/PowerLauncher.Telemetry/Events/LauncherQueryEvent.cs): This event is generated for every query that is typed in the searchbox. It logs query time, number of results, and query length.
+6. [`LauncherResultActionEvent`](/src/modules/launcher/PowerLauncher.Telemetry/Events/LauncherResultActionEvent.cs): This event is generated when a context menu action is triggered.
+7. [`LauncherShowEvent`](/src/modules/launcher/PowerLauncher.Telemetry/Events/LauncherShowEvent.cs): This event is generated when the `PowerLauncher` window is shown.
+8. [`LauncherWarmStateHotkeyEvent`](/src/modules/launcher/PowerLauncher.Telemetry/Events/LauncherWarmStateHotkeyEvent.cs): This event logs time from the Alt+Space press until the PT Run window is visible.
\ No newline at end of file
diff --git a/doc/devdocs/readme.md b/doc/devdocs/readme.md
index 4cf78f41ea..cd873fda77 100644
--- a/doc/devdocs/readme.md
+++ b/doc/devdocs/readme.md
@@ -57,6 +57,7 @@ Various tools used by PowerToys. Includes the Visual Studio 2019 project templat
```shell
cd "%ProgramFiles(x86)%\Microsoft Visual Studio\2019"
SET targetFolder="\"
+IF EXIST Preview\NUL (SET targetFolder=Preview)
IF EXIST Enterprise\NUL (SET targetFolder=Enterprise)
IF EXIST Professional\NUL (SET targetFolder=Professional)
IF EXIST Community\NUL (SET targetFolder=Community)
diff --git a/doc/devdocs/settingsv2/communication-with-modules.md b/doc/devdocs/settingsv2/communication-with-modules.md
new file mode 100644
index 0000000000..d857e36936
--- /dev/null
+++ b/doc/devdocs/settingsv2/communication-with-modules.md
@@ -0,0 +1,15 @@
+# Communication with modules
+
+## Through runner
+- The settings process communicates changes in the UI to most modules using the runner through delegates.
+- More details on this are mentioned in [`runner-ipc.md`](settingsv2/runner-ipc.md).
+
+## PT Run
+- Any changes to the UI are saved by the settings process in the `settings.json` file located within the `/Local/Microsoft/PowerToys/Launcher/` folder.
+- PT Run watches for any changes within this file and updates it's general settings or propagates the information to the plugins, depending on the type of information.
+Eg: The maximum number of results drop down updates the maximum number of rows in the results list which updates the general settings of PT Run whereas the drive detection checkbox details are dispatched to the indexer plugin.
+
+## Keyboard Manager
+- The Settings process and keyboard manager share access to a common `default.json` file which contains information about the remapped keys and shortcuts.
+- To ensure that there is no contention while both processes try to access the common file, there is a named file mutex.
+- The settings process expects the keyboard manager process to create the `default.json` file if it does not exist. It does not create the file in case it is not present.
\ No newline at end of file
diff --git a/doc/devdocs/settingsv2/compatibility-legacy-settings.md b/doc/devdocs/settingsv2/compatibility-legacy-settings.md
new file mode 100644
index 0000000000..392591c50c
--- /dev/null
+++ b/doc/devdocs/settingsv2/compatibility-legacy-settings.md
@@ -0,0 +1,12 @@
+# Compatibility with legacy settings and runner
+The following must be kept in mind regarding compatibility with settings v1 and runner.
+
+### 1. Folder Naming structure
+- Each of the modules has a folder within the `Local/Microsoft/PowerToys` directory which contains the module configurations within the `settings.json` file. The name of this folder must be the same across settingsv1 and settingsv2.
+- The name of the settings folder for each powertoy is the same as the `ModuleName`. It is set within each of the viewModel files. This name must not be changed to ensure that the user configurations for each of the powertoys rolls over on update.
+
+### 2. Communication with runner
+- The status of each of the modules is communicated with the runner in the form of a json object. The names of all the powerToys is set in the [`EnableModules.cs`](src/core/Microsoft.PowerToys.Settings.UI.Lib/EnabledModules.cs) file. The `JsonPropertyName` must not be changed to ensure that the information is dispatched properly to all the modules by the runner.
+
+### ImageResizer anomaly
+All the powertoys have the same folder name as well as JsonPropertyName to communicate information with the runner. However that is not the case with ImageResizer. The folder name is `ImageResizer` whereas the JsonPropertyName is `Image Resizer`(Note the additional space). This should not be changed to ensure backward compatibity as well as proper functioning of the module.
\ No newline at end of file
diff --git a/doc/devdocs/settingsv2/hotkeycontrol.md b/doc/devdocs/settingsv2/hotkeycontrol.md
new file mode 100644
index 0000000000..b6e5e52ff6
--- /dev/null
+++ b/doc/devdocs/settingsv2/hotkeycontrol.md
@@ -0,0 +1,46 @@
+# Custom HotKey Control
+
+The Settings project provides a custom hotkey control which consumes key presses. This control can be used to set the hotkey of any PowerToy.
+
+## HotKey Control in FancyZones
+
+
+## Hotkey related files
+
+#### [`HotkeySettingsControlHook.cs`](/src/core/Microsoft.PowerToys.Settings.UI.Lib/HotkeySettingsControlHook.cs)
+
+- This function initializes and starts the [`keyboardHook`](src/common/interop/KeyboardHook.cpp) for the hotkey control.
+
+```csharp
+ public HotkeySettingsControlHook(KeyEvent keyDown, KeyEvent keyUp, IsActive isActive, FilterAccessibleKeyboardEvents filterAccessibleKeyboardEvents)
+ {
+ _keyDown = keyDown;
+ _keyUp = keyUp;
+ _isActive = isActive;
+ _filterKeyboardEvent = filterAccessibleKeyboardEvents;
+ _hook = new KeyboardHook(HotkeySettingsHookCallback, IsActive, FilterKeyboardEvents);
+ _hook.Start();
+ }
+
+```
+
+#### [`HotkeySettingsControl.xaml.cs`](/src/core/Microsoft.PowerToys.Settings.UI/HotkeySettingsControl.xaml.cs)
+
+- The function of this class is to update the state of the keys being pressed within the custom control. This information is stored in `internalSettings`.
+
+- It provides the following callbacks to the `HotKeySettingsControlHook`:
+
+ - `KeyUp`: Resets the key state in `internalSettings` when a key is released.
+ - `KeyDown`: Updates the user facing text of the hotkey control as soon as a key is pressed.
+ - `isActive`: Sets the current status of the keyboard hook.
+ - `FilterAccessibleKeyboardEvents`: This function is used to ignore the `Tab` and `Shift+Tab` key presses to meet the accessibility requirements.
+
+#### [`HotkeySettings.cs`](/src/core/Microsoft.PowerToys.Settings.UI.Lib/HotkeySettings.cs)
+
+- Contains the structure of a HotKey where it is represented as a combination of one of the modifier keys (`Alt`, `Shift`, `Win` and `Ctrl`) and a non-modifier key.
+
+#### Note
+- The control displays all key presses to the user (except Tab and Shift+Tab which move focus out of the control). However, when the focus is being lost from the control, the `lastValidHotkeySettings` is set as the user facing text.
+
+
+
diff --git a/doc/devdocs/settingsv2/project-overview.md b/doc/devdocs/settingsv2/project-overview.md
new file mode 100644
index 0000000000..23baa985da
--- /dev/null
+++ b/doc/devdocs/settingsv2/project-overview.md
@@ -0,0 +1,17 @@
+# Overview
+`Settingsv2` is WPF .net core desktop application. It uses the `WindowsXamlHost` control from the Windows Community Toolkit to host UWP controls from `WinUI3` library. More details about WinUI can be found [here](https://microsoft.github.io/microsoft-ui-xaml/about.html#what-is-it).
+
+## Settings V2 Project structure
+The Settings project is a XAML island based project which
+follows the [MVVM architectural pattern][MVVM] where the graphical user interface is separated from the view models.
+
+#### [UI Components:](/src/core/Microsoft.PowerToys.Settings.UI)
+The Settings.UI project contains the xaml files for each of the UI components. It also contains the Hotkey logic for the settings control.
+
+#### [Viewmodels:](/src/core/Microsoft.PowerToys.Settings.UI.Lib)
+The Settings.UI.Lib project contains the data that is to be rendered by the UI components.
+
+#### [Settings Runner:](/src/core/Microsoft.PowerToys.Settings.UI.Runner)
+The function of the settings runner project is to communicate all changes that the user makes in the user interface, to the runner so that it can be dispatched and reflected in all the modules.
+
+[MVVM]: https://docs.microsoft.com/en-us/windows/uwp/data-binding/data-binding-and-mvvm
\ No newline at end of file
diff --git a/doc/devdocs/settingsv2/readme.md b/doc/devdocs/settingsv2/readme.md
new file mode 100644
index 0000000000..c9cb7945be
--- /dev/null
+++ b/doc/devdocs/settingsv2/readme.md
@@ -0,0 +1,12 @@
+# Table of Contents
+1. [Settings overview](/doc/devdocs/settingsv2/project-overview.md)
+2. [UI Architecture](/doc/devdocs/settingsv2/ui-architecture.md)
+3. [ViewModels](/doc/devdocs/settingsv2/viewmodels.md)
+4. Data flow
+ - [Inter-Process Communication with runner](/doc/devdocs/settingsv2/runner-ipc.md)
+ - [Communication with modules](/doc/devdocs/settingsv2/communication-with-modules.md)
+5. [Settings Utilities](/doc/devdocs/settingsv2/settings-utilities.md)
+6. [Custom Hotkey control and keyboard hook handling](hotkeycontrol.md)
+7. [Compatibility with legacy settings and runner](/doc/devdocs/settingsv2/compatibility-legacy-settings.md)
+8. [XAML Island tweaks](/doc/devdocs/settingsv2/xaml-island-tweaks.md)
+9. [Telemetry](/doc/devdocs/settingsv2/telemetry.md)
diff --git a/doc/devdocs/settingsv2/runner-ipc.md b/doc/devdocs/settingsv2/runner-ipc.md
new file mode 100644
index 0000000000..d10dcd7848
--- /dev/null
+++ b/doc/devdocs/settingsv2/runner-ipc.md
@@ -0,0 +1,46 @@
+# Inter-Process Communication with Runner
+
+The Settings v2 process uses two way IPC to communicate with the runner process.
+
+## Initialization
+- On the settings' side, the two way IPC delegates are contained with the [`ShellPage.xaml.cs`](/src/core/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml.cs) file. The delegates are static and the views for all the powerToys send the ipc information to the viewmodels as `ShellPage.DefaultSndMSGCallBack`.
+- These delegates are initialized within the [`Mainwindow.xaml.cs`](/src/core/Microsoft.PowerToys.Settings.UI.Runner/MainWindow.xaml.cs) file in the `Settings.Runner` project.
+
+
+## Types of IPC delegates
+- There are three types of delegates for the settings to communicate with the runner:
+1. `SendDefaultMessage` - This is used by all the viewmodels to communicate changes in the UI to the runner so that the information can be dispatched to the modules.
+2. `RestartAsAdmin`
+3. `CheckForUpdates`
+
+## Sending information to runner
+- The settings process communicates with the runner by using the delegates defined within the [`ShellPage.xaml.cs`](/src/core/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml.cs) file.
+- Depending on the type of object sending the information, the json is created accordingly.
+- If any information has been modified by the user in the GeneralSettings page, then the json file sent to the runner has the name set to `general`, whereas if any information has been modified by the user in any powertoy related settings page, the name of the json file being communicated with the runner is set to `powertoy`.
+
+## Receiving information from runner
+- The `ShellPage`object has a `IPCResponseHandleList` which is a list of functions which handle IPC responses.
+
+```csharp
+// receive IPC Message
+Program.IPCMessageReceivedCallback = (string msg) =>
+{
+ if (ShellPage.ShellHandler.IPCResponseHandleList != null)
+ {
+ try
+ {
+ JsonObject json = JsonObject.Parse(msg);
+ foreach (Action handle in ShellPage.ShellHandler.IPCResponseHandleList)
+ {
+ handle(json);
+ }
+ }
+ catch (Exception)
+ {
+ }
+ }
+};
+```
+
+- Whenever any information is sent from the runner each of the functions in the handle list perform their action on that json object.
+- One example of where information sent from the runner is being processed by the settings is in [`GeneralPage.xaml.cs`](/src/core/Microsoft.PowerToys.Settings.UI/Views/GeneralPage.xaml.cs) when the user clicks the check for updates button. The information displayed after, such as the user has the latest version installed is a result of this handle.
diff --git a/doc/devdocs/settingsv2/settings-utilities.md b/doc/devdocs/settingsv2/settings-utilities.md
new file mode 100644
index 0000000000..e0f07b62f5
--- /dev/null
+++ b/doc/devdocs/settingsv2/settings-utilities.md
@@ -0,0 +1,13 @@
+# Settings Utilities
+
+- Abstractions for each of the file/folder related operations are present in [`SettingsUtils.cs`](src/core/Microsoft.PowerToys.Settings.UI.Lib/SettingsUtils.cs).
+- To reduce contention between the settings process and runner while trying to access the `settings.json` file of any powertoy, the settings process tries to access the file only when it needs to load the information for the first time. However, there is still no mechanism in place which ensures that both the settings and runner processes do not access the information simultaneously leading to `IOExceptions`.
+
+## Utilities
+
+### `GetSettings(powertoy, filename)`
+
+- The GetSettings function tries to read the file in the powertoy settings folder and creates a new file with default configurations if it does not exist.
+- Ideally this function should only be called by the [`SettingsRepository`](src/core/Microsoft.PowerToys.Settings.UI.Lib/SettingsRepository`1.cs) which would be accessed only when a powertoy settings object is being loaded for the first time.
+- The reason behind ensuring that it is not accessed elsewhere is to avoid contention with the runner during file access.
+- Each of the objects which are deserialized using this function must implement the `ISettingsConfig` interface.
diff --git a/doc/devdocs/settingsv2/telemetry.md b/doc/devdocs/settingsv2/telemetry.md
new file mode 100644
index 0000000000..8ae1070d14
--- /dev/null
+++ b/doc/devdocs/settingsv2/telemetry.md
@@ -0,0 +1,8 @@
+# Telemetry
+## Overview
+[`Microsoft.PowerToys.Settings.UI.Lib/Telemetry`](/src/core/Microsoft.PowerToys.Settings.UI.Lib/Telemetry) contains telemetry events generated by `Settingsv2.` These event classes are derived from the [`EventBase`](/src/common/ManagedTelemetry/Telemetry/Events/EventBase.cs) class and [`IEvent`](/src/common/ManagedTelemetry/Telemetry/Events/IEvent.cs) class. [`IEvent`](/src/common/ManagedTelemetry/Telemetry/Events/IEvent.cs) class provides the lowest level abstraction, containing attributes such as privacy tags needed for every telemetry data. [`EventBase`](/src/common/ManagedTelemetry/Telemetry/Events/EventBase.cs) class provides a higher-level abstraction, having attributes common to all `PowerToys` telemetry events.
+
+## Events
+The following events are generated by `Settingsv2`:
+1. [`SettingsBootEvent`](/src/core/Microsoft.PowerToys.Settings.UI.Lib/Telemetry/Events/SettingsBootEvent.cs): This event captures the time taken by `Settingsv2` to initialize `MainWindow` UI control.
+2. [`SettingsEnabledEvent.cs`](/src/core/Microsoft.PowerToys.Settings.UI.Lib/Telemetry/Events/SettingsEnabledEvent.cs): This event is generated when a module is enabled or disabled.
\ No newline at end of file
diff --git a/doc/devdocs/settingsv2/ui-architecture.md b/doc/devdocs/settingsv2/ui-architecture.md
new file mode 100644
index 0000000000..ffebc17107
--- /dev/null
+++ b/doc/devdocs/settingsv2/ui-architecture.md
@@ -0,0 +1,9 @@
+# UI Architecture
+
+ The UI code is distributed between two projects: [`Microsoft.PowerToys.Settings.UI.Runner`](/src/core/Microsoft.PowerToys.Settings.UI.Runner) and [`Microsoft.PowerToys.Settings.UI`](/src/core/Microsoft.PowerToys.Settings.UI.Lib). [`Microsoft.PowerToys.Settings.UI.Runner`](/src/core/Microsoft.PowerToys.Settings.UI.Runner) is a WPF .net core application. It contains the parent display window and corresponding code is present in [`MainWindow.xaml.`](/src/core/Microsoft.PowerToys.Settings.UI.Runner/MainWindow.xaml) [`Microsoft.PowerToys.Settings.UI`](/src/core/Microsoft.PowerToys.Settings.UI.Lib) is UWP applications and contains views for base navigation and modules. Fig 1 provides a description of the UI controls hierarchy and each of the controls have been summarized below :
+- [`MainWindow.xaml`](/src/core/Microsoft.PowerToys.Settings.UI.Runner/MainWindow.xaml) is the parent WPF control.
+- `WindowsXamlHost` control is used to host UWP control to [`MainWindow.xaml`](/src/core/Microsoft.PowerToys.Settings.UI.Runner/MainWindow.xaml) parent control.
+- [`ShellPage.xaml`](/src/core/Microsoft.PowerToys.Settings.UI/Views/ShellPage.xaml) is a UWP control, consisting of a side navigation panel with an icon for each module. Clicking on a module icon loads the corresponding `setting.json` file and displays the data in the UI.
+
+
+**Fig 1: UI Architecture for settingsv2**
diff --git a/doc/devdocs/settingsv2/viewmodels.md b/doc/devdocs/settingsv2/viewmodels.md
new file mode 100644
index 0000000000..1f9cd54d95
--- /dev/null
+++ b/doc/devdocs/settingsv2/viewmodels.md
@@ -0,0 +1,26 @@
+# Viewmodels
+The viewmodels are located within the [`Microsoft.PowerToys.Settings.UI.Lib`](/src/core/Microsoft.PowerToys.Settings.UI.Lib) project.
+
+## Components
+- Each viewmodel takes in the general `settingsRepository`, the `moduleSettingsRepository` if it exists and the delegates for IPC communication.
+- The general `settingsRepository` contains the general configurations of all powertoys whereas the `moduleSettingsRepository` is spcific to the module. This is to ensure that the configuration details are shared amongst the viewmodels without having to re-open the `settings.json` file.
+- Whenever there is a change in the UI, the `OnPropertyChanged` event is invoked and the viewmodel sends a corresponding IPC message to the runner which would perform the designated action such as dispatching the change to the modules or enabling/disabling the powertoy etc.
+
+#### Difference between viewmodels
+- The [`GeneralViewModel`](/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/GeneralViewModel.cs) is different from the rest of the view models with regard to the IPC communication wherein it sends special IPC messages to the runner to check for updates and to restart as admin.
+- Each of the powerToy viewmodels have two types of IPC communications, one for the general status of the powerToy and the other for communication powerToy specific change in properties to the runner.
+
+## [`SettingsRepository`](src/core/Microsoft.PowerToys.Settings.UI.Lib/SettingsRepository`1.cs)
+- The [`SettingsRepository`](src/core/Microsoft.PowerToys.Settings.UI.Lib/SettingsRepository`1.cs) is a generic singleton which contains the configurations for each viewmodel.
+- As it is a generic singleton, there can only be one instance of the settings repository of a particular type. This ensures that all the viewmodels are modifying a common object and a change made in one locations reflects everywhere.
+- The singleton implementation is thread-safe. Unit tests have been added for the same.
+
+### Settings viewmodel anomalies
+- The reason behind using the `SettingsRepository` is to ensure that the settings process does not try to access the `settings.json` files directly but rather does it through this class which encapsulates all the file operations from the viewmodels.
+- However, this could not be expanded to all the viewmodels directly for the following reasons. Some refactoring must be done to unify these cases and to bring them under the same model:
+ - The PowerRename viewodel does not save the settings configurations in the same format as the rest of the powertoys, ie. {name, version, properties}. However, it only stores the properties directly.
+ - Some viewmodels expect the runner to create the file instead of creating the file themselves, like in keyboard manager.
+ - The colorpicker powertoy creates the `settings.json` within the module. This must be taken care of when encapsulated within the settingsRepository.
+- Currently, all modules use the `SettingsRepository` to access the General Settings config.
+- However, only Fancyzones, ShortcutGuide and PowerPreview use the `SettingsRepository` to access the module properties.
+
diff --git a/doc/devdocs/settingsv2/xaml-island-tweaks.md b/doc/devdocs/settingsv2/xaml-island-tweaks.md
new file mode 100644
index 0000000000..e0b134b4f3
--- /dev/null
+++ b/doc/devdocs/settingsv2/xaml-island-tweaks.md
@@ -0,0 +1,30 @@
+# XAML Island Tweaks
+Few tweaks were made to fix issues with Xaml Islands. These tweaks should be removed after migrating to WINUI3. The tweaks are listed below:
+1. Workaround to ensure XAML Island application terminates if attempted to close from taskbar while minimized:
+```
+private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
+{
+ isOpen = false;
+
+ // XAML Islands: If the window is closed while minimized, exit the process. Required to avoid process not terminating issue - https://github.com/microsoft/PowerToys/issues/4430
+ if (WindowState == WindowState.Minimized)
+ {
+ // Run Environment.Exit on a separate task to avoid performance impact
+ System.Threading.Tasks.Task.Run(() => { Environment.Exit(0); });
+ }
+}
+```
+2. Workaround to hide the XAML Island blank icon in the taskbar when the XAML Island application is loading:
+```
+var coreWindow = Windows.UI.Core.CoreWindow.GetForCurrentThread();
+var coreWindowInterop = Interop.GetInterop(coreWindow);
+Interop.ShowWindow(coreWindowInterop.WindowHandle, Interop.SW_HIDE);
+```
+3. Workaround to prevent XAML Island failing to render on Nvidia workstation graphics cards:
+```
+ // XAML Islands: If the window is open, explicity force it to be shown to solve the blank dialog issue https://github.com/microsoft/PowerToys/issues/3384
+ if (isOpen)
+ {
+ Show();
+ }
+ ```
\ No newline at end of file
diff --git a/doc/images/Logo-HiRes.png b/doc/images/Logo/Vintage/Logo-HiRes.png
similarity index 100%
rename from doc/images/Logo-HiRes.png
rename to doc/images/Logo/Vintage/Logo-HiRes.png
diff --git a/doc/images/Logo.jpg b/doc/images/Logo/Vintage/Logo.jpg
similarity index 100%
rename from doc/images/Logo.jpg
rename to doc/images/Logo/Vintage/Logo.jpg
diff --git a/doc/images/Logo.svg b/doc/images/Logo/Vintage/Logo.svg
similarity index 100%
rename from doc/images/Logo.svg
rename to doc/images/Logo/Vintage/Logo.svg
diff --git a/doc/images/launcher/launcher_dependency.PNG b/doc/images/launcher/launcher_dependency.PNG
new file mode 100644
index 0000000000..dd28b85b0d
Binary files /dev/null and b/doc/images/launcher/launcher_dependency.PNG differ
diff --git a/doc/images/launcher/plugins/calculator.png b/doc/images/launcher/plugins/calculator.png
new file mode 100644
index 0000000000..7d88107774
Binary files /dev/null and b/doc/images/launcher/plugins/calculator.png differ
diff --git a/doc/images/launcher/plugins/folder.png b/doc/images/launcher/plugins/folder.png
new file mode 100644
index 0000000000..ae707b1e22
Binary files /dev/null and b/doc/images/launcher/plugins/folder.png differ
diff --git a/doc/images/launcher/plugins/indexer.png b/doc/images/launcher/plugins/indexer.png
new file mode 100644
index 0000000000..a4f7bbf9f0
Binary files /dev/null and b/doc/images/launcher/plugins/indexer.png differ
diff --git a/doc/images/launcher/plugins/program.png b/doc/images/launcher/plugins/program.png
new file mode 100644
index 0000000000..a7583088b8
Binary files /dev/null and b/doc/images/launcher/plugins/program.png differ
diff --git a/doc/images/launcher/plugins/shell.png b/doc/images/launcher/plugins/shell.png
new file mode 100644
index 0000000000..556e82045a
Binary files /dev/null and b/doc/images/launcher/plugins/shell.png differ
diff --git a/doc/images/launcher/plugins/uri.png b/doc/images/launcher/plugins/uri.png
new file mode 100644
index 0000000000..b990a724f0
Binary files /dev/null and b/doc/images/launcher/plugins/uri.png differ
diff --git a/doc/images/launcher/plugins/windowwalker.png b/doc/images/launcher/plugins/windowwalker.png
new file mode 100644
index 0000000000..cd1368f56a
Binary files /dev/null and b/doc/images/launcher/plugins/windowwalker.png differ
diff --git a/doc/images/launcher/pt_run_ui.png b/doc/images/launcher/pt_run_ui.png
new file mode 100644
index 0000000000..98be7138d4
Binary files /dev/null and b/doc/images/launcher/pt_run_ui.png differ
diff --git a/doc/images/launcher/ui_vm_interaction.PNG b/doc/images/launcher/ui_vm_interaction.PNG
new file mode 100644
index 0000000000..2aea3a6094
Binary files /dev/null and b/doc/images/launcher/ui_vm_interaction.PNG differ
diff --git a/doc/images/launcher/vm_plugin_interaction.PNG b/doc/images/launcher/vm_plugin_interaction.PNG
new file mode 100644
index 0000000000..2a00496bbd
Binary files /dev/null and b/doc/images/launcher/vm_plugin_interaction.PNG differ
diff --git a/doc/images/settingsv2/settingshotkeycontrol.png b/doc/images/settingsv2/settingshotkeycontrol.png
new file mode 100644
index 0000000000..efe8d1a954
Binary files /dev/null and b/doc/images/settingsv2/settingshotkeycontrol.png differ
diff --git a/doc/images/settingsv2/ui-architecture.png b/doc/images/settingsv2/ui-architecture.png
new file mode 100644
index 0000000000..d7a029f438
Binary files /dev/null and b/doc/images/settingsv2/ui-architecture.png differ
diff --git a/installer/PowerToysBootstrapper/PowerToysBootstrapper.sln b/installer/PowerToysBootstrapper/PowerToysBootstrapper.sln
index f935a035e8..119aa75d00 100644
--- a/installer/PowerToysBootstrapper/PowerToysBootstrapper.sln
+++ b/installer/PowerToysBootstrapper/PowerToysBootstrapper.sln
@@ -9,6 +9,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "common", "..\..\src\common\
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bootstrapper", "bootstrapper\bootstrapper.vcxproj", "{D194E3AA-F824-4CA9-9A58-034DD6B7D022}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "logging", "..\..\src\logging\logging.vcxproj", "{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@@ -27,6 +29,10 @@ Global
{D194E3AA-F824-4CA9-9A58-034DD6B7D022}.Debug|x64.Build.0 = Debug|x64
{D194E3AA-F824-4CA9-9A58-034DD6B7D022}.Release|x64.ActiveCfg = Release|x64
{D194E3AA-F824-4CA9-9A58-034DD6B7D022}.Release|x64.Build.0 = Release|x64
+ {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.ActiveCfg = Debug|x64
+ {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Debug|x64.Build.0 = Debug|x64
+ {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x64.ActiveCfg = Release|x64
+ {7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp b/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp
index cc22f38b3d..2cbc5184ab 100644
--- a/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp
+++ b/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.cpp
@@ -10,16 +10,22 @@
#include
#include
-
#include
extern "C" IMAGE_DOS_HEADER __ImageBase;
+#define STR_HELPER(x) #x
+#define STR(x) STR_HELPER(x)
namespace
{
const wchar_t APPLICATION_ID[] = L"PowerToysInstaller";
const wchar_t TOAST_TAG[] = L"PowerToysInstallerProgress";
+ const char LOG_FILENAME[] = "powertoys-bootstrapper-" STR(VERSION_MAJOR) "." STR(VERSION_MINOR) "." STR(VERSION_REVISION) ".log";
+ const char MSI_LOG_FILENAME[] = "powertoys-bootstrapper-msi-" STR(VERSION_MAJOR) "." STR(VERSION_MINOR) "." STR(VERSION_REVISION) ".log";
+
}
+#undef STR
+#undef STR_HELPER
namespace localized_strings
{
@@ -59,63 +65,89 @@ std::optional extractIcon()
return iconRes->saveAsFile(icoPath) ? std::make_optional(std::move(icoPath)) : std::nullopt;
}
-enum class CmdArgs
+void setup_log(const spdlog::level::level_enum severity)
{
- silent,
- noFullUI,
- noStartPT,
- skipDotnetInstall,
- showHelp
-};
-
-namespace
-{
- const std::unordered_map knownArgs = {
- { L"--help", CmdArgs::showHelp },
- { L"--no_full_ui", CmdArgs::noFullUI },
- { L"--silent", CmdArgs::silent },
- { L"--no_start_pt", CmdArgs::noStartPT },
- { L"--skip_dotnet_install", CmdArgs::skipDotnetInstall }
- };
-}
-
-std::unordered_set parseCmdArgs(const int nCmdArgs, LPWSTR* argList)
-{
- std::unordered_set result;
- for (size_t i = 1; i < nCmdArgs; ++i)
+ try
{
- if (auto it = knownArgs.find(argList[i]); it != end(knownArgs))
+ std::shared_ptr logger;
+ if (severity != spdlog::level::off)
{
- result.emplace(it->second);
+ logger = spdlog::basic_logger_mt("file", LOG_FILENAME);
+
+ std::error_code _;
+ const DWORD msiSev = severity == spdlog::level::debug ? INSTALLLOGMODE_VERBOSE : INSTALLLOGMODE_ERROR;
+ const auto msiLogPath = fs::current_path(_) / MSI_LOG_FILENAME;
+ MsiEnableLogW(msiSev, msiLogPath.c_str(), INSTALLLOGATTRIBUTES_APPEND);
}
+ else
+ {
+ logger = spdlog::null_logger_mt("null");
+ }
+ logger->set_pattern("[%L][%d-%m-%C-%T] %v");
+ logger->set_level(severity);
+ spdlog::set_default_logger(std::move(logger));
+ spdlog::set_level(severity);
+ spdlog::flush_every(std::chrono::seconds(5));
+ }
+ catch (...)
+ {
}
- return result;
}
int bootstrapper()
{
using namespace localized_strings;
winrt::init_apartment();
-
- int nCmdArgs = 0;
- LPWSTR* argList = CommandLineToArgvW(GetCommandLineW(), &nCmdArgs);
- const auto cmdArgs = parseCmdArgs(nCmdArgs, argList);
- std::wostringstream oss;
- if (cmdArgs.contains(CmdArgs::showHelp))
+ cxxopts::Options options{ "PowerToysBootstrapper" };
+ // clang-format off
+ options.add_options()
+ ("h,help", "Show help")
+ ("no_full_ui", "Use reduced UI for MSI")
+ ("s,silent", "Suppress MSI UI and notifications")
+ ("no_start_pt", "Do not launch PowerToys after the installation is complete")
+ ("skip_dotnet_install", "Skip dotnet 3.X installation even if it's not detected")
+ ("log_level", "Log level. Possible values: off|debug|error", cxxopts::value()->default_value("off"));
+ // clang-format on
+ cxxopts::ParseResult cmdArgs;
+ options.allow_unrecognised_options();
+ try
{
- oss << "Supported arguments:\n\n";
- for (auto [arg, _] : knownArgs)
- {
- oss << arg << '\n';
- }
- MessageBoxW(nullptr, oss.str().c_str(), L"Help", MB_OK | MB_ICONINFORMATION);
+ cmdArgs = options.parse(__argc, const_cast(__argv));
+ }
+ catch (...)
+ {
+ }
+ const bool showHelp = cmdArgs["help"].as();
+ const bool noFullUI = cmdArgs["no_full_ui"].as();
+ const bool silent = cmdArgs["silent"].as();
+ const bool skipDotnetInstall = cmdArgs["skip_dotnet_install"].as();
+ const bool noStartPT = cmdArgs["no_start_pt"].as();
+ const auto logLevel = cmdArgs["log_level"].as();
+ spdlog::level::level_enum severity = spdlog::level::off;
+
+ if (logLevel == "debug")
+ {
+ severity = spdlog::level::debug;
+ }
+ else if (logLevel == "error")
+ {
+ severity = spdlog::level::err;
+ }
+ setup_log(severity);
+ if (showHelp)
+ {
+ std::ostringstream helpMsg;
+ helpMsg << options.help();
+ MessageBoxA(nullptr, helpMsg.str().c_str(), "Help", MB_OK | MB_ICONINFORMATION);
return 0;
}
- if (!cmdArgs.contains(CmdArgs::noFullUI))
+ spdlog::debug("PowerToys Bootstrapper is launched!\nnoFullUI: {}\nsilent: {}\nno_start_pt: {}\nskip_dotnet_install: {}\nlog_level: {}", noFullUI, silent, noStartPT, skipDotnetInstall, logLevel);
+
+ if (!noFullUI)
{
MsiSetInternalUI(INSTALLUILEVEL_FULL, nullptr);
}
- if (cmdArgs.contains(CmdArgs::silent))
+ if (silent)
{
if (is_process_elevated())
{
@@ -123,8 +155,12 @@ int bootstrapper()
}
else
{
- // MSI fails to run in silent mode due to a suppressed UAC w/o elevation, so we restart elevated
+ spdlog::debug("MSI doesn't support silent mode without elevation => restarting elevated");
+ // MSI fails to run in silent mode due to a suppressed UAC w/o elevation,
+ // so we restart ourselves elevated with the same args
std::wstring params;
+ int nCmdArgs = 0;
+ LPWSTR* argList = CommandLineToArgvW(GetCommandLineW(), &nCmdArgs);
for (int i = 1; i < nCmdArgs; ++i)
{
params += argList[i];
@@ -136,6 +172,7 @@ int bootstrapper()
const auto processHandle = run_elevated(argList[0], params.c_str());
if (!processHandle)
{
+ spdlog::error("Couldn't restart elevated to enable silent mode! ({})", GetLastError());
return 1;
}
if (WaitForSingleObject(processHandle, 3600000) == WAIT_OBJECT_0)
@@ -146,6 +183,7 @@ int bootstrapper()
}
else
{
+ spdlog::error("Elevated setup process timed out after 60m => using basic MSI UI ({})", GetLastError());
// Couldn't install using the completely silent mode in an hour, use basic UI.
TerminateProcess(processHandle, 0);
MsiSetInternalUI(INSTALLUILEVEL_BASIC, nullptr);
@@ -163,16 +201,17 @@ int bootstrapper()
auto instanceMutex = createAppMutex(POWERTOYS_BOOTSTRAPPER_MUTEX_NAME);
if (!instanceMutex)
{
+ spdlog::error("Couldn't acquire PowerToys global mutex. That means setup couldn't kill PowerToys.exe process");
return 1;
}
notifications::override_application_id(APPLICATION_ID);
-
+ spdlog::debug("Extracting icon for toast notifications");
fs::path iconPath{ L"C:\\" };
if (auto extractedIcon = extractIcon())
{
iconPath = std::move(*extractedIcon);
}
-
+ spdlog::debug("Registering app id for toast notifications");
notifications::register_application_id(TOAST_TITLE, iconPath.c_str());
auto removeShortcut = wil::scope_exit([&] {
@@ -186,6 +225,7 @@ int bootstrapper()
auto msi_path = updating::get_msi_package_path();
if (!msi_path.empty())
{
+ spdlog::error(L"Detected a newer {} version => launching its installer", installedVersion->toWstring());
MsiInstallProductW(msi_path.c_str(), nullptr);
return 0;
}
@@ -196,19 +236,22 @@ int bootstrapper()
progressParams.progress = 0.0f;
progressParams.progress_title = EXTRACTING_INSTALLER;
notifications::toast_params params{ TOAST_TAG, false, std::move(progressParams) };
- if (!cmdArgs.contains(CmdArgs::silent))
+ if (!silent)
{
+ spdlog::debug("Launching progress toast notification");
notifications::show_toast_with_activations({}, TOAST_TITLE, {}, {}, std::move(params));
}
auto processToasts = wil::scope_exit([&] {
+ spdlog::debug("Processing HWND messages for 2s so toast have time to show up");
run_message_loop(true, 2);
});
- if (!cmdArgs.contains(CmdArgs::silent))
+ if (!silent)
{
// Worker thread to periodically increase progress and keep the progress toast from losing focus
std::thread{ [&] {
+ spdlog::debug("Started worker thread for progress bar update");
for (;; Sleep(3000))
{
std::scoped_lock lock{ progressLock };
@@ -223,7 +266,7 @@ int bootstrapper()
}
auto updateProgressBar = [&](const float value, const wchar_t* title) {
- if (cmdArgs.contains(CmdArgs::silent))
+ if (silent)
{
return;
}
@@ -233,13 +276,15 @@ int bootstrapper()
notifications::update_progress_bar_toast(TOAST_TAG, progressParams);
};
+ spdlog::debug("Extracting embedded MSI installer");
const auto installerPath = extractEmbeddedInstaller();
if (!installerPath)
{
- if (!cmdArgs.contains(CmdArgs::silent))
+ if (!silent)
{
notifications::show_toast(INSTALLER_EXTRACT_ERROR, TOAST_TITLE);
}
+ spdlog::error("Couldn't install the MSI installer ({})", GetLastError());
return 1;
}
auto removeExtractedInstaller = wil::scope_exit([&] {
@@ -248,12 +293,22 @@ int bootstrapper()
});
updateProgressBar(.25f, UNINSTALLING_PREVIOUS_VERSION);
+ spdlog::debug("Acquiring existing MSI package path");
const auto package_path = updating::get_msi_package_path();
- if (!package_path.empty() && !updating::uninstall_msi_version(package_path) && !cmdArgs.contains(CmdArgs::silent))
+ if (!package_path.empty())
{
+ spdlog::debug(L"Existing MSI package path: {}", package_path);
+ }
+ else
+ {
+ spdlog::debug("Existing MSI package path not found");
+ }
+ if (!package_path.empty() && !updating::uninstall_msi_version(package_path) && !silent)
+ {
+ spdlog::error("Couldn't install the existing MSI package ({})", GetLastError());
notifications::show_toast(UNINSTALL_PREVIOUS_VERSION_ERROR, TOAST_TITLE);
}
- const bool installDotnet = !cmdArgs.contains(CmdArgs::skipDotnetInstall);
+ const bool installDotnet = !skipDotnetInstall;
if (installDotnet)
{
updateProgressBar(.5f, INSTALLING_DOTNET);
@@ -261,16 +316,22 @@ int bootstrapper()
try
{
- if (installDotnet &&
- !updating::dotnet_is_installed() &&
- !updating::install_dotnet(cmdArgs.contains(CmdArgs::silent)) &&
- !cmdArgs.contains(CmdArgs::silent))
+ if (installDotnet)
{
- notifications::show_toast(DOTNET_INSTALL_ERROR, TOAST_TITLE);
+ spdlog::debug("Detecting if dotnet is installed");
+ const bool dotnetInstalled = updating::dotnet_is_installed();
+ spdlog::debug("Dotnet is installed: {}", dotnetInstalled);
+ if (!dotnetInstalled &&
+ !updating::install_dotnet(silent) &&
+ !silent)
+ {
+ notifications::show_toast(DOTNET_INSTALL_ERROR, TOAST_TITLE);
+ }
}
}
catch (...)
{
+ spdlog::error("Unknown exception during dotnet installation");
MessageBoxW(nullptr, L".NET Core installation", L"Unknown exception encountered!", MB_OK | MB_ICONERROR);
}
@@ -278,18 +339,23 @@ int bootstrapper()
// Always skip dotnet install, because we should've installed it from here earlier
std::wstring msiProps = L"SKIPDOTNETINSTALL=1 ";
+ spdlog::debug("Launching MSI installation for new package {}", installerPath->string());
const bool installationDone = MsiInstallProductW(installerPath->c_str(), msiProps.c_str()) == ERROR_SUCCESS;
updateProgressBar(1.f, installationDone ? NEW_VERSION_INSTALLATION_DONE : NEW_VERSION_INSTALLATION_ERROR);
if (!installationDone)
{
+ spdlog::error("Couldn't install new MSI package ({})", GetLastError());
return 1;
}
+ spdlog::debug("Installation completed");
- if (!cmdArgs.contains(CmdArgs::noStartPT) && !cmdArgs.contains(CmdArgs::silent))
+ if (!noStartPT && !silent)
{
+ spdlog::debug("Starting the newly installed PowerToys.exe");
auto newPTPath = updating::get_msi_package_installed_path();
if (!newPTPath)
{
+ spdlog::error("Couldn't determine new MSI package install location ({})", GetLastError());
return 1;
}
*newPTPath += L"\\PowerToys.exe";
diff --git a/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.vcxproj b/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.vcxproj
index 3966dac233..4691001006 100644
--- a/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.vcxproj
+++ b/installer/PowerToysBootstrapper/bootstrapper/bootstrapper.vcxproj
@@ -28,6 +28,8 @@
bootstrapper
+
+
Application
true
@@ -78,7 +80,7 @@
true
MultiThreaded
true
- ../../../src/
+ ../../../src/;%(AdditionalIncludeDirectories)
Use
pch.h
@@ -99,7 +101,7 @@
true
MultiThreadedDebug
true
- ../../../src/
+ ../../../src/;%(AdditionalIncludeDirectories)
Use
pch.h
@@ -137,6 +139,9 @@
{17da04df-e393-4397-9cf0-84dabe11032e}
+
+ {7e1e3f13-2bd6-3f75-a6a7-873a2b55c60f}
+
diff --git a/installer/PowerToysBootstrapper/bootstrapper/pch.h b/installer/PowerToysBootstrapper/bootstrapper/pch.h
index 11dc82b4f6..b9367228c1 100644
--- a/installer/PowerToysBootstrapper/bootstrapper/pch.h
+++ b/installer/PowerToysBootstrapper/bootstrapper/pch.h
@@ -14,3 +14,10 @@
#include
#include
#include
+
+#include
+#include
+#include
+#include
+
+#include
diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs
index cddcbe03d4..ecf368a3ca 100644
--- a/installer/PowerToysSetup/Product.wxs
+++ b/installer/PowerToysSetup/Product.wxs
@@ -219,25 +219,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -817,27 +799,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
diff --git a/src/common/UnitTests-CommonLib/Settings.Tests.cpp b/src/common/UnitTests-CommonLib/Settings.Tests.cpp
index dbc848186b..6d9148817c 100644
--- a/src/common/UnitTests-CommonLib/Settings.Tests.cpp
+++ b/src/common/UnitTests-CommonLib/Settings.Tests.cpp
@@ -53,11 +53,12 @@ namespace UnitTestsCommonLib
private:
const std::wstring m_json = L"{\"name\":\"Module Name\",\"properties\" : {\"bool_toggle_true\":{\"value\":true},\"bool_toggle_false\":{\"value\":false},\"color_picker\" : {\"value\":\"#ff8d12\"},\"int_spinner\" : {\"value\":10},\"string_text\" : {\"value\":\"a quick fox\"}},\"version\" : \"1.0\" }";
const std::wstring m_moduleName = L"Module Name";
+ const std::wstring m_moduleKey = L"Module Key";
public:
TEST_METHOD (LoadFromJsonBoolTrue)
{
- PowerToyValues values = PowerToyValues::from_json_string(m_json);
+ PowerToyValues values = PowerToyValues::from_json_string(m_json, m_moduleKey);
auto value = values.get_bool_value(L"bool_toggle_true");
Assert::IsTrue(value.has_value());
Assert::AreEqual(true, *value);
@@ -65,7 +66,7 @@ namespace UnitTestsCommonLib
TEST_METHOD (LoadFromJsonBoolFalse)
{
- PowerToyValues values = PowerToyValues::from_json_string(m_json);
+ PowerToyValues values = PowerToyValues::from_json_string(m_json, m_moduleKey);
auto value = values.get_bool_value(L"bool_toggle_false");
Assert::IsTrue(value.has_value());
Assert::AreEqual(false, *value);
@@ -73,7 +74,7 @@ namespace UnitTestsCommonLib
TEST_METHOD (LoadFromJsonInt)
{
- PowerToyValues values = PowerToyValues::from_json_string(m_json);
+ PowerToyValues values = PowerToyValues::from_json_string(m_json, m_moduleKey);
auto value = values.get_int_value(L"int_spinner");
Assert::IsTrue(value.has_value());
Assert::AreEqual(10, *value);
@@ -81,7 +82,7 @@ namespace UnitTestsCommonLib
TEST_METHOD (LoadFromJsonString)
{
- PowerToyValues values = PowerToyValues::from_json_string(m_json);
+ PowerToyValues values = PowerToyValues::from_json_string(m_json, m_moduleKey);
auto value = values.get_string_value(L"string_text");
Assert::IsTrue(value.has_value());
@@ -91,7 +92,7 @@ namespace UnitTestsCommonLib
TEST_METHOD (LoadFromJsonColorPicker)
{
- PowerToyValues values = PowerToyValues::from_json_string(m_json);
+ PowerToyValues values = PowerToyValues::from_json_string(m_json, m_moduleKey);
auto value = values.get_string_value(L"color_picker");
Assert::IsTrue(value.has_value());
@@ -101,19 +102,19 @@ namespace UnitTestsCommonLib
TEST_METHOD (LoadFromEmptyString)
{
- auto func = [] { PowerToyValues values = PowerToyValues::from_json_string(L""); };
+ auto func = [] { PowerToyValues values = PowerToyValues::from_json_string(L"", L"Module Key"); };
Assert::ExpectException(func);
}
TEST_METHOD (LoadFromInvalidString_NameMissed)
{
- auto func = [] { PowerToyValues values = PowerToyValues::from_json_string(L"{\"properties\" : {\"bool_toggle_true\":{\"value\":true},\"bool_toggle_false\":{\"value\":false},\"color_picker\" : {\"value\":\"#ff8d12\"},\"int_spinner\" : {\"value\":10},\"string_text\" : {\"value\":\"a quick fox\"}},\"version\" : \"1.0\" }"); };
+ auto func = [] { PowerToyValues values = PowerToyValues::from_json_string(L"{\"properties\" : {\"bool_toggle_true\":{\"value\":true},\"bool_toggle_false\":{\"value\":false},\"color_picker\" : {\"value\":\"#ff8d12\"},\"int_spinner\" : {\"value\":10},\"string_text\" : {\"value\":\"a quick fox\"}},\"version\" : \"1.0\" }", L"Module Key"); };
Assert::ExpectException(func);
}
TEST_METHOD (LoadFromInvalidString_VersionMissed)
{
- PowerToyValues values = PowerToyValues::from_json_string(L"{\"name\":\"Module Name\",\"properties\" : {}}");
+ PowerToyValues values = PowerToyValues::from_json_string(L"{\"name\":\"Module Name\",\"properties\" : {}}", L"Module Key");
const std::wstring expectedStr = L"{\"name\" : \"Module Name\", \"properties\" : {},\"version\" : \"1.0\"}";
const auto expected = json::JsonObject::Parse(expectedStr);
const auto actual = json::JsonObject::Parse(values.serialize());
@@ -123,7 +124,7 @@ namespace UnitTestsCommonLib
TEST_METHOD (LoadFromInvalidString_PropertiesMissed)
{
- PowerToyValues values = PowerToyValues::from_json_string(L"{\"name\":\"Module Name\",\"version\" : \"1.0\" }");
+ PowerToyValues values = PowerToyValues::from_json_string(L"{\"name\":\"Module Name\",\"version\" : \"1.0\" }", L"Module Key");
const std::wstring expectedStr = L"{\"name\":\"Module Name\",\"version\" : \"1.0\" }";
const auto expected = json::JsonObject::Parse(expectedStr);
const auto actual = json::JsonObject::Parse(values.serialize());
@@ -133,7 +134,7 @@ namespace UnitTestsCommonLib
TEST_METHOD (LoadFromValidString_EmptyProperties)
{
- PowerToyValues values = PowerToyValues::from_json_string(L"{\"name\":\"Module Name\",\"properties\" : {}, \"version\" : \"1.0\" }");
+ PowerToyValues values = PowerToyValues::from_json_string(L"{\"name\":\"Module Name\",\"properties\" : {}, \"version\" : \"1.0\" }", L"Module Key");
const std::wstring expectedStr = L"{\"name\":\"Module Name\",\"properties\" : {},\"version\" : \"1.0\" }";
const auto expected = json::JsonObject::Parse(expectedStr);
const auto actual = json::JsonObject::Parse(values.serialize());
@@ -143,7 +144,7 @@ namespace UnitTestsCommonLib
TEST_METHOD (LoadFromValidString_ChangedVersion)
{
- PowerToyValues values = PowerToyValues::from_json_string(L"{\"name\":\"Module Name\",\"properties\" : {},\"version\" : \"2.0\"}");
+ PowerToyValues values = PowerToyValues::from_json_string(L"{\"name\":\"Module Name\",\"properties\" : {},\"version\" : \"2.0\"}", L"Module Key");
const std::wstring expectedStr = L"{\"name\" : \"Module Name\", \"properties\" : {},\"version\" : \"1.0\"}"; //version from input json is ignored
const auto expected = json::JsonObject::Parse(expectedStr);
@@ -154,7 +155,7 @@ namespace UnitTestsCommonLib
TEST_METHOD (CreateWithName)
{
- PowerToyValues values(m_moduleName);
+ PowerToyValues values(m_moduleName, m_moduleKey);
const std::wstring expectedStr = L"{\"name\":\"Module Name\",\"properties\" : {},\"version\" : \"1.0\" }";
const auto expected = json::JsonObject::Parse(expectedStr);
@@ -165,7 +166,7 @@ namespace UnitTestsCommonLib
TEST_METHOD (AddPropertyBoolPositive)
{
- PowerToyValues values(m_moduleName);
+ PowerToyValues values(m_moduleName, m_moduleKey);
values.add_property(L"positive_bool_value", true);
auto value = values.get_bool_value(L"positive_bool_value");
@@ -175,7 +176,7 @@ namespace UnitTestsCommonLib
TEST_METHOD (AddPropertyBoolNegative)
{
- PowerToyValues values(m_moduleName);
+ PowerToyValues values(m_moduleName, m_moduleKey);
values.add_property(L"negative_bool_value", false);
auto value = values.get_bool_value(L"negative_bool_value");
@@ -185,7 +186,7 @@ namespace UnitTestsCommonLib
TEST_METHOD (AddPropertyIntPositive)
{
- PowerToyValues values(m_moduleName);
+ PowerToyValues values(m_moduleName, m_moduleKey);
const int intVal = 4392854;
values.add_property(L"integer", intVal);
@@ -196,7 +197,7 @@ namespace UnitTestsCommonLib
TEST_METHOD (AddPropertyIntNegative)
{
- PowerToyValues values(m_moduleName);
+ PowerToyValues values(m_moduleName, m_moduleKey);
const int intVal = -4392854;
values.add_property(L"integer", intVal);
@@ -207,7 +208,7 @@ namespace UnitTestsCommonLib
TEST_METHOD (AddPropertyIntZero)
{
- PowerToyValues values(m_moduleName);
+ PowerToyValues values(m_moduleName, m_moduleKey);
const int intVal = 0;
values.add_property(L"integer", intVal);
@@ -218,7 +219,7 @@ namespace UnitTestsCommonLib
TEST_METHOD (AddPropertyStringEmpty)
{
- PowerToyValues values(m_moduleName);
+ PowerToyValues values(m_moduleName, m_moduleKey);
const std::wstring stringVal = L"";
values.add_property(L"stringval", stringVal);
@@ -229,7 +230,7 @@ namespace UnitTestsCommonLib
TEST_METHOD (AddPropertyString)
{
- PowerToyValues values(m_moduleName);
+ PowerToyValues values(m_moduleName, m_moduleKey);
const std::wstring stringVal = L"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
values.add_property(L"stringval", stringVal);
@@ -240,7 +241,7 @@ namespace UnitTestsCommonLib
TEST_METHOD (AddPropertyJsonEmpty)
{
- PowerToyValues values(m_moduleName);
+ PowerToyValues values(m_moduleName, m_moduleKey);
const auto json = json::JsonObject();
values.add_property(L"jsonval", json);
@@ -251,7 +252,7 @@ namespace UnitTestsCommonLib
TEST_METHOD (AddPropertyJsonObject)
{
- PowerToyValues values(m_moduleName);
+ PowerToyValues values(m_moduleName, m_moduleKey);
const auto json = json::JsonObject::Parse(m_json);
values.add_property(L"jsonval", json);
diff --git a/src/common/interop/KeyboardHook.cpp b/src/common/interop/KeyboardHook.cpp
index 321ed8c88e..c13a60dcb9 100644
--- a/src/common/interop/KeyboardHook.cpp
+++ b/src/common/interop/KeyboardHook.cpp
@@ -60,7 +60,10 @@ LRESULT __clrcall KeyboardHook::HookProc(int nCode, WPARAM wParam, LPARAM lParam
KeyboardEvent ^ ev = gcnew KeyboardEvent();
ev->message = wParam;
ev->key = reinterpret_cast(lParam)->vkCode;
- if (filterKeyboardEvent != nullptr && !filterKeyboardEvent->Invoke(ev))
+ ev->dwExtraInfo = reinterpret_cast(lParam)->dwExtraInfo;
+
+ // Ignore the keyboard hook if the FilterkeyboardEvent returns false.
+ if ((filterKeyboardEvent != nullptr && !filterKeyboardEvent->Invoke(ev)))
{
return CallNextHookEx(hookHandle, nCode, wParam, lParam);
}
diff --git a/src/common/interop/KeyboardHook.h b/src/common/interop/KeyboardHook.h
index 100f05e51d..1b14748e75 100644
--- a/src/common/interop/KeyboardHook.h
+++ b/src/common/interop/KeyboardHook.h
@@ -1,5 +1,7 @@
#pragma once
+#include
+
using namespace System::Threading;
using namespace System::Collections::Generic;
@@ -10,6 +12,7 @@ public
{
WPARAM message;
int key;
+ uint64_t dwExtraInfo;
};
public
diff --git a/src/common/interop/interop.vcxproj b/src/common/interop/interop.vcxproj
index fe2dfc7862..d3b0a96b26 100644
--- a/src/common/interop/interop.vcxproj
+++ b/src/common/interop/interop.vcxproj
@@ -98,6 +98,8 @@
pch.h
true
true
+ true
+ true
diff --git a/src/common/keyboard_layout.cpp b/src/common/keyboard_layout.cpp
index 16b681078d..0bce4a96a7 100644
--- a/src/common/keyboard_layout.cpp
+++ b/src/common/keyboard_layout.cpp
@@ -27,7 +27,7 @@ std::vector LayoutMap::GetKeyCodeList(const bool isShortcut)
return impl->GetKeyCodeList(isShortcut);
}
-std::vector LayoutMap::GetKeyNameList(const bool isShortcut)
+std::vector> LayoutMap::GetKeyNameList(const bool isShortcut)
{
return impl->GetKeyNameList(isShortcut);
}
@@ -215,6 +215,7 @@ void LayoutMap::LayoutMapImpl::UpdateLayout()
keyboardLayoutMap[VK_NONCONVERT] = L"IME Non-Convert";
keyboardLayoutMap[VK_ACCEPT] = L"IME Kana";
keyboardLayoutMap[VK_MODECHANGE] = L"IME Mode Change";
+ keyboardLayoutMap[CommonSharedConstants::VK_DISABLED] = L"Disable";
}
// Function to return the list of key codes in the order for the drop down. It creates it if it doesn't exist
@@ -304,25 +305,25 @@ std::vector LayoutMap::LayoutMapImpl::GetKeyCodeList(const bool isShortcu
return keyCodes;
}
-std::vector LayoutMap::LayoutMapImpl::GetKeyNameList(const bool isShortcut)
+std::vector> LayoutMap::LayoutMapImpl::GetKeyNameList(const bool isShortcut)
{
- std::vector keyNames;
+ std::vector> keyNames;
std::vector keyCodes = GetKeyCodeList(isShortcut);
std::lock_guard lock(keyboardLayoutMap_mutex);
// If it is a key list for the shortcut control then we add a "None" key at the start
if (isShortcut)
{
- keyNames.push_back(L"None");
+ keyNames.push_back({ 0, L"None" });
for (int i = 1; i < keyCodes.size(); i++)
{
- keyNames.push_back(keyboardLayoutMap[keyCodes[i]]);
+ keyNames.push_back({ keyCodes[i], keyboardLayoutMap[keyCodes[i]] });
}
}
else
{
for (int i = 0; i < keyCodes.size(); i++)
{
- keyNames.push_back(keyboardLayoutMap[keyCodes[i]]);
+ keyNames.push_back({ keyCodes[i], keyboardLayoutMap[keyCodes[i]] });
}
}
diff --git a/src/common/keyboard_layout.h b/src/common/keyboard_layout.h
index 58fe9f5280..4b1504c466 100644
--- a/src/common/keyboard_layout.h
+++ b/src/common/keyboard_layout.h
@@ -12,7 +12,7 @@ public:
void UpdateLayout();
std::wstring GetKeyName(DWORD key);
std::vector GetKeyCodeList(const bool isShortcut = false);
- std::vector GetKeyNameList(const bool isShortcut = false);
+ std::vector> GetKeyNameList(const bool isShortcut = false);
private:
class LayoutMapImpl;
diff --git a/src/common/keyboard_layout_impl.h b/src/common/keyboard_layout_impl.h
index 9a1b07f2a8..dc1802e537 100644
--- a/src/common/keyboard_layout_impl.h
+++ b/src/common/keyboard_layout_impl.h
@@ -46,6 +46,6 @@ public:
// Function to return the list of key codes in the order for the drop down. It creates it if it doesn't exist
std::vector GetKeyCodeList(const bool isShortcut);
- // Function to return the list of key name in the order for the drop down based on the key codes
- std::vector GetKeyNameList(const bool isShortcut);
+ // Function to return the list of key name pairs in the order for the drop down based on the key codes
+ std::vector> GetKeyNameList(const bool isShortcut);
};
\ No newline at end of file
diff --git a/src/common/on_thread_executor.cpp b/src/common/on_thread_executor.cpp
index e5a55ffe16..3498e341a8 100644
--- a/src/common/on_thread_executor.cpp
+++ b/src/common/on_thread_executor.cpp
@@ -16,6 +16,14 @@ std::future OnThreadExecutor::submit(task_t task)
return future;
}
+void OnThreadExecutor::cancel()
+{
+ std::lock_guard lock{ _task_mutex };
+ _task_queue = {};
+ _task_cv.notify_one();
+}
+
+
void OnThreadExecutor::worker_thread()
{
while (!_shutdown_request)
diff --git a/src/common/on_thread_executor.h b/src/common/on_thread_executor.h
index 2ea92c9c12..3111602f0f 100644
--- a/src/common/on_thread_executor.h
+++ b/src/common/on_thread_executor.h
@@ -18,6 +18,7 @@ public:
OnThreadExecutor();
~OnThreadExecutor();
std::future submit(task_t task);
+ void cancel();
private:
void worker_thread();
diff --git a/src/common/settings_helpers.cpp b/src/common/settings_helpers.cpp
index 88bf018ff4..35fe5827de 100644
--- a/src/common/settings_helpers.cpp
+++ b/src/common/settings_helpers.cpp
@@ -23,11 +23,11 @@ namespace PTSettingsHelper
return result;
}
- std::wstring get_module_save_folder_location(std::wstring_view powertoy_name)
+ std::wstring get_module_save_folder_location(std::wstring_view powertoy_key)
{
std::wstring result = get_root_save_folder_location();
result += L"\\";
- result += powertoy_name;
+ result += powertoy_key;
std::filesystem::path save_path(result);
if (!std::filesystem::exists(save_path))
{
@@ -36,9 +36,9 @@ namespace PTSettingsHelper
return result;
}
- std::wstring get_module_save_file_location(std::wstring_view powertoy_name)
+ std::wstring get_module_save_file_location(std::wstring_view powertoy_key)
{
- return get_module_save_folder_location(powertoy_name) + settings_filename;
+ return get_module_save_folder_location(powertoy_key) + settings_filename;
}
std::wstring get_powertoys_general_save_file_location()
@@ -46,15 +46,15 @@ namespace PTSettingsHelper
return get_root_save_folder_location() + settings_filename;
}
- void save_module_settings(std::wstring_view powertoy_name, json::JsonObject& settings)
+ void save_module_settings(std::wstring_view powertoy_key, json::JsonObject& settings)
{
- const std::wstring save_file_location = get_module_save_file_location(powertoy_name);
+ const std::wstring save_file_location = get_module_save_file_location(powertoy_key);
json::to_file(save_file_location, settings);
}
- json::JsonObject load_module_settings(std::wstring_view powertoy_name)
+ json::JsonObject load_module_settings(std::wstring_view powertoy_key)
{
- const std::wstring save_file_location = get_module_save_file_location(powertoy_name);
+ const std::wstring save_file_location = get_module_save_file_location(powertoy_key);
auto saved_settings = json::from_file(save_file_location);
return saved_settings.has_value() ? std::move(*saved_settings) : json::JsonObject{};
}
diff --git a/src/common/settings_objects.cpp b/src/common/settings_objects.cpp
index 2bd2e8ca68..b92da8933b 100644
--- a/src/common/settings_objects.cpp
+++ b/src/common/settings_objects.cpp
@@ -1,6 +1,7 @@
#include "pch.h"
#include "settings_objects.h"
#include "settings_helpers.h"
+#include
namespace PowerToysSettings
{
@@ -277,27 +278,33 @@ namespace PowerToysSettings
return L"RESOURCE ID NOT FOUND: " + std::to_wstring(resource_id);
}
- PowerToyValues::PowerToyValues(std::wstring_view powertoy_name)
+ PowerToyValues::PowerToyValues(std::wstring_view powertoy_name, std::wstring_view powertoy_key)
{
- _name = powertoy_name;
+ _key = powertoy_key;
set_version();
m_json.SetNamedValue(L"name", json::value(powertoy_name));
m_json.SetNamedValue(L"properties", json::JsonObject{});
}
- PowerToyValues PowerToyValues::from_json_string(std::wstring_view json)
+ PowerToyValues PowerToyValues::from_json_string(std::wstring_view json, std::wstring_view powertoy_key)
{
PowerToyValues result = PowerToyValues();
+ json::JsonObject jsonObject = json::JsonValue::Parse(json).GetObjectW();
+ if (!jsonObject.HasKey(L"name"))
+ {
+ throw winrt::hresult_error(E_NOT_SET, L"name field not set");
+ }
+
result.m_json = json::JsonValue::Parse(json).GetObjectW();
- result._name = result.m_json.GetNamedString(L"name");
+ result._key = powertoy_key;
return result;
}
- PowerToyValues PowerToyValues::load_from_settings_file(std::wstring_view powertoy_name)
+ PowerToyValues PowerToyValues::load_from_settings_file(std::wstring_view powertoy_key)
{
PowerToyValues result = PowerToyValues();
- result.m_json = PTSettingsHelper::load_module_settings(powertoy_name);
- result._name = powertoy_name;
+ result.m_json = PTSettingsHelper::load_module_settings(powertoy_key);
+ result._key = powertoy_key;
return result;
}
@@ -357,7 +364,7 @@ namespace PowerToysSettings
void PowerToyValues::save_to_settings_file()
{
set_version();
- PTSettingsHelper::save_module_settings(_name, m_json);
+ PTSettingsHelper::save_module_settings(_key, m_json);
}
void PowerToyValues::set_version()
diff --git a/src/common/settings_objects.h b/src/common/settings_objects.h
index 3f2a212eb6..e27e2973ed 100644
--- a/src/common/settings_objects.h
+++ b/src/common/settings_objects.h
@@ -67,9 +67,9 @@ namespace PowerToysSettings
class PowerToyValues
{
public:
- PowerToyValues(std::wstring_view powertoy_name);
- static PowerToyValues from_json_string(std::wstring_view json);
- static PowerToyValues load_from_settings_file(std::wstring_view powertoy_name);
+ PowerToyValues(std::wstring_view powertoy_name, std::wstring_view powertoy_key);
+ static PowerToyValues from_json_string(std::wstring_view json, std::wstring_view powertoy_key);
+ static PowerToyValues load_from_settings_file(std::wstring_view powertoy_key);
template
inline void add_property(std::wstring_view name, T value)
@@ -92,7 +92,7 @@ namespace PowerToysSettings
const std::wstring m_version = L"1.0";
void set_version();
json::JsonObject m_json;
- std::wstring _name;
+ std::wstring _key;
PowerToyValues() {}
};
diff --git a/src/common/shared_constants.h b/src/common/shared_constants.h
index 69f5499454..196901394c 100644
--- a/src/common/shared_constants.h
+++ b/src/common/shared_constants.h
@@ -11,4 +11,7 @@ namespace CommonSharedConstants
// Path to the event used by PowerLauncher
const wchar_t POWER_LAUNCHER_SHARED_EVENT[] = L"Local\\PowerToysRunInvokeEvent-30f26ad7-d36d-4c0e-ab02-68bb5ff3c4ab";
-}
+
+ // Max DWORD for key code to disable keys.
+ const DWORD VK_DISABLED = 0x100;
+}
\ No newline at end of file
diff --git a/src/common/updating/updating.cpp b/src/common/updating/updating.cpp
index 165cf0a69a..de051f30d3 100644
--- a/src/common/updating/updating.cpp
+++ b/src/common/updating/updating.cpp
@@ -102,6 +102,11 @@ namespace updating
std::future> get_new_github_version_info_async()
{
+ // If the current version starts with 0.0.*, it means we're on a local build from a farm and shouldn't check for updates.
+ if (VERSION_MAJOR == 0 && VERSION_MINOR == 0)
+ {
+ co_return std::nullopt;
+ }
try
{
http::HttpClient client;
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/AppSpecificKeysDataModel.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/AppSpecificKeysDataModel.cs
index 6d8d851165..e3bedd2eb3 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/AppSpecificKeysDataModel.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/AppSpecificKeysDataModel.cs
@@ -2,6 +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 System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
@@ -12,19 +13,27 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
[JsonPropertyName("targetApp")]
public string TargetApp { get; set; }
- public new List GetOriginalKeys()
+ public new List GetMappedOriginalKeys()
{
- return base.GetOriginalKeys();
+ return base.GetMappedOriginalKeys();
}
- public new List GetNewRemapKeys()
+ public new List GetMappedNewRemapKeys()
{
- return base.GetNewRemapKeys();
+ return base.GetMappedNewRemapKeys();
}
public bool Compare(AppSpecificKeysDataModel arg)
{
- return OriginalKeys.Equals(arg.OriginalKeys) && NewRemapKeys.Equals(arg.NewRemapKeys) && TargetApp.Equals(arg.TargetApp);
+ if (arg == null)
+ {
+ throw new ArgumentNullException(nameof(arg));
+ }
+
+ // Using Ordinal comparison for internal text
+ return OriginalKeys.Equals(arg.OriginalKeys, StringComparison.Ordinal) &&
+ NewRemapKeys.Equals(arg.NewRemapKeys, StringComparison.Ordinal) &&
+ TargetApp.Equals(arg.TargetApp, StringComparison.Ordinal);
}
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ColorPickerSettings.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ColorPickerSettings.cs
index 4e1a2be279..42de1632d2 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ColorPickerSettings.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ColorPickerSettings.cs
@@ -5,11 +5,11 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
-using Microsoft.PowerToys.Settings.UI.Lib.Utilities;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
namespace Microsoft.PowerToys.Settings.UI.Lib
{
- public class ColorPickerSettings : BasePTModuleSettings
+ public class ColorPickerSettings : BasePTModuleSettings, ISettingsConfig
{
public const string ModuleName = "ColorPicker";
@@ -31,7 +31,23 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
WriteIndented = true,
};
+ if (settingsUtils == null)
+ {
+ throw new ArgumentNullException(nameof(settingsUtils));
+ }
+
settingsUtils.SaveSettings(JsonSerializer.Serialize(this, options), ModuleName);
}
+
+ public string GetModuleName()
+ {
+ return Name;
+ }
+
+ // This can be utilized in the future if the settings.json file is to be modified/deleted.
+ public bool UpgradeSettingsConfiguration()
+ {
+ return false;
+ }
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/CustomAction/SendCustomAction.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/CustomAction/SendCustomAction.cs
index 515be86b6e..81c8b7ef26 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/CustomAction/SendCustomAction.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/CustomAction/SendCustomAction.cs
@@ -25,7 +25,8 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.CustomAction
{
PropertyNamingPolicy = new CustomNamePolicy((propertyName) =>
{
- return propertyName.Equals("ModuleAction") ? moduleName : propertyName;
+ // Using Ordinal as this is an internal property name
+ return propertyName.Equals("ModuleAction", System.StringComparison.Ordinal) ? moduleName : propertyName;
}),
};
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/EnabledModules.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/EnabledModules.cs
index 835b9a7262..eb38781b4e 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/EnabledModules.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/EnabledModules.cs
@@ -148,7 +148,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
return JsonSerializer.Serialize(this);
}
- private void LogTelemetryEvent(bool value, [CallerMemberName] string moduleName = null)
+ private static void LogTelemetryEvent(bool value, [CallerMemberName] string moduleName = null)
{
var dataEvent = new SettingsEnabledEvent()
{
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/FancyZonesSettings.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/FancyZonesSettings.cs
index e3e49aa9f9..570b75cc97 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/FancyZonesSettings.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/FancyZonesSettings.cs
@@ -3,19 +3,33 @@
// See the LICENSE file in the project root for more information.
using System.Text.Json.Serialization;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
namespace Microsoft.PowerToys.Settings.UI.Lib
{
- public class FancyZonesSettings : BasePTModuleSettings
+ public class FancyZonesSettings : BasePTModuleSettings, ISettingsConfig
{
+ public const string ModuleName = "FancyZones";
+
public FancyZonesSettings()
{
- Version = string.Empty;
- Name = string.Empty;
+ Version = "1.0";
+ Name = ModuleName;
Properties = new FZConfigProperties();
}
[JsonPropertyName("properties")]
public FZConfigProperties Properties { get; set; }
+
+ public string GetModuleName()
+ {
+ return Name;
+ }
+
+ // This can be utilized in the future if the settings.json file is to be modified/deleted.
+ public bool UpgradeSettingsConfiguration()
+ {
+ return false;
+ }
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/GeneralSettings.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/GeneralSettings.cs
index 6b8a4932b0..ea0c0107da 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/GeneralSettings.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/GeneralSettings.cs
@@ -2,12 +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;
using System.Text.Json;
using System.Text.Json.Serialization;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
+using Microsoft.PowerToys.Settings.UI.Lib.Utilities;
namespace Microsoft.PowerToys.Settings.UI.Lib
{
- public class GeneralSettings
+ public class GeneralSettings : ISettingsConfig
{
// Gets or sets a value indicating whether packaged.
[JsonPropertyName("packaged")]
@@ -78,9 +81,36 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
return JsonSerializer.Serialize(this);
}
- private string DefaultPowertoysVersion()
+ private static string DefaultPowertoysVersion()
{
return interop.CommonManaged.GetProductVersion();
}
+
+ // This function is to implement the ISettingsConfig interface.
+ // This interface helps in getting the settings configurations.
+ public string GetModuleName()
+ {
+ // The SettingsUtils functions access general settings when the module name is an empty string.
+ return string.Empty;
+ }
+
+ public bool UpgradeSettingsConfiguration()
+ {
+ try
+ {
+ if (Helper.CompareVersions(PowertoysVersion, Helper.GetProductVersion()) != 0)
+ {
+ // Update settings
+ PowertoysVersion = Helper.GetProductVersion();
+ return true;
+ }
+ }
+ catch (FormatException)
+ {
+ // If there is an issue with the version number format, don't migrate settings.
+ }
+
+ return false;
+ }
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/HotkeySettings.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/HotkeySettings.cs
index c4242d2f11..37ee7da743 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/HotkeySettings.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/HotkeySettings.cs
@@ -10,6 +10,8 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
{
public class HotkeySettings
{
+ private const int VKTAB = 0x09;
+
public HotkeySettings()
{
Win = false;
@@ -100,6 +102,11 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
public bool IsValid()
{
+ if (IsAccessibleShortcut())
+ {
+ return false;
+ }
+
return (Alt || Ctrl || Win || Shift) && Code != 0;
}
@@ -107,5 +114,17 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
{
return !Alt && !Ctrl && !Win && !Shift && Code == 0;
}
+
+ public bool IsAccessibleShortcut()
+ {
+ // Shift+Tab and Tab are accessible shortcuts
+ if ((!Alt && !Ctrl && !Win && Shift && Code == VKTAB)
+ || (!Alt && !Ctrl && !Win && !Shift && Code == VKTAB))
+ {
+ return true;
+ }
+
+ return false;
+ }
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/HotkeySettingsControlHook.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/HotkeySettingsControlHook.cs
index 003eadc7d8..26231e7553 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/HotkeySettingsControlHook.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/HotkeySettingsControlHook.cs
@@ -2,6 +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 System;
using interop;
namespace Microsoft.PowerToys.Settings.UI.Lib
@@ -10,7 +11,9 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
public delegate bool IsActive();
- public class HotkeySettingsControlHook
+ public delegate bool FilterAccessibleKeyboardEvents(int key, UIntPtr extraInfo);
+
+ public class HotkeySettingsControlHook : IDisposable
{
private const int WmKeyDown = 0x100;
private const int WmKeyUp = 0x101;
@@ -21,13 +24,17 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
private KeyEvent _keyDown;
private KeyEvent _keyUp;
private IsActive _isActive;
+ private bool disposedValue;
- public HotkeySettingsControlHook(KeyEvent keyDown, KeyEvent keyUp, IsActive isActive)
+ private FilterAccessibleKeyboardEvents _filterKeyboardEvent;
+
+ public HotkeySettingsControlHook(KeyEvent keyDown, KeyEvent keyUp, IsActive isActive, FilterAccessibleKeyboardEvents filterAccessibleKeyboardEvents)
{
_keyDown = keyDown;
_keyUp = keyUp;
_isActive = isActive;
- _hook = new KeyboardHook(HotkeySettingsHookCallback, IsActive, null);
+ _filterKeyboardEvent = filterAccessibleKeyboardEvents;
+ _hook = new KeyboardHook(HotkeySettingsHookCallback, IsActive, FilterKeyboardEvents);
_hook.Start();
}
@@ -51,10 +58,29 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
}
}
+ private bool FilterKeyboardEvents(KeyboardEvent ev)
+ {
+ return _filterKeyboardEvent(ev.key, (UIntPtr)ev.dwExtraInfo);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ // Dispose the KeyboardHook object to terminate the hook threads
+ _hook.Dispose();
+ }
+
+ disposedValue = true;
+ }
+ }
+
public void Dispose()
{
- // Dispose the KeyboardHook object to terminate the hook threads
- _hook.Dispose();
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
}
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ISettingsUtils.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ISettingsUtils.cs
index 3430b0b18c..c5083f3a16 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ISettingsUtils.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ISettingsUtils.cs
@@ -2,11 +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 Microsoft.PowerToys.Settings.UI.Lib.Interface;
+
namespace Microsoft.PowerToys.Settings.UI.Lib
{
public interface ISettingsUtils
{
- T GetSettings(string powertoy = "", string fileName = "settings.json");
+ T GetSettings(string powertoy = "", string fileName = "settings.json")
+ where T : ISettingsConfig, new();
void SaveSettings(string jsonSettings, string powertoy = "", string fileName = "settings.json");
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ImageResizerSettings.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ImageResizerSettings.cs
index fcf512f591..cffb1e0f9e 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ImageResizerSettings.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ImageResizerSettings.cs
@@ -4,12 +4,13 @@
using System.Text.Json;
using System.Text.Json.Serialization;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
namespace Microsoft.PowerToys.Settings.UI.Lib
{
- public class ImageResizerSettings : BasePTModuleSettings
+ public class ImageResizerSettings : BasePTModuleSettings, ISettingsConfig
{
- public const string ModuleName = "Image Resizer";
+ public const string ModuleName = "ImageResizer";
[JsonPropertyName("properties")]
public ImageResizerProperties Properties { get; set; }
@@ -29,5 +30,16 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
};
return JsonSerializer.Serialize(this, options);
}
+
+ public string GetModuleName()
+ {
+ return Name;
+ }
+
+ // This can be utilized in the future if the settings.json file is to be modified/deleted.
+ public bool UpgradeSettingsConfiguration()
+ {
+ return false;
+ }
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ImageSize.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ImageSize.cs
index 24c0910f46..cd268add57 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ImageSize.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ImageSize.cs
@@ -2,6 +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 System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text.Json;
@@ -203,8 +204,8 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
{
_unit = value;
OnPropertyChanged();
- OnPropertyChanged("ExtraBoxOpacity");
- OnPropertyChanged("EnableEtraBoxes");
+ OnPropertyChanged(nameof(ExtraBoxOpacity));
+ OnPropertyChanged(nameof(EnableEtraBoxes));
}
}
}
@@ -222,6 +223,11 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
public void Update(ImageSize modifiedSize)
{
+ if (modifiedSize == null)
+ {
+ throw new ArgumentNullException(nameof(modifiedSize));
+ }
+
Id = modifiedSize.Id;
Name = modifiedSize.Name;
Fit = modifiedSize.Fit;
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ImageresizerSizes.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ImageresizerSizes.cs
index 38ae835af1..36d79b2d03 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ImageresizerSizes.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ImageresizerSizes.cs
@@ -10,8 +10,13 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
{
public class ImageResizerSizes
{
+ // Suppressing this warning because removing the setter breaks
+ // deserialization with System.Text.Json. This affects the UI display.
+ // See: https://github.com/dotnet/runtime/issues/30258
[JsonPropertyName("value")]
+#pragma warning disable CA2227 // Collection properties should be read only
public ObservableCollection Value { get; set; }
+#pragma warning restore CA2227 // Collection properties should be read only
public ImageResizerSizes()
{
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/Interface/ISettingsConfig.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/Interface/ISettingsConfig.cs
new file mode 100644
index 0000000000..fa0291568f
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/Interface/ISettingsConfig.cs
@@ -0,0 +1,16 @@
+// 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.Lib.Interface
+{
+ // Common interface to be implemented by all the objects which get and store settings properties.
+ public interface ISettingsConfig
+ {
+ string ToJsonString();
+
+ string GetModuleName();
+
+ bool UpgradeSettingsConfiguration();
+ }
+}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/Interface/ISettingsRepository`1.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/Interface/ISettingsRepository`1.cs
new file mode 100644
index 0000000000..6caadb85f9
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/Interface/ISettingsRepository`1.cs
@@ -0,0 +1,11 @@
+// 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.Lib.Interface
+{
+ public interface ISettingsRepository
+ {
+ T SettingsConfig { get; set; }
+ }
+}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/KeyboardManagerProfile.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/KeyboardManagerProfile.cs
index 52a0c2d764..66246e2cc4 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/KeyboardManagerProfile.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/KeyboardManagerProfile.cs
@@ -2,11 +2,13 @@
// 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.Serialization;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
namespace Microsoft.PowerToys.Settings.UI.Lib
{
- public class KeyboardManagerProfile
+ public class KeyboardManagerProfile : ISettingsConfig
{
[JsonPropertyName("remapKeys")]
public RemapKeysDataModel RemapKeys { get; set; }
@@ -19,5 +21,21 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
RemapKeys = new RemapKeysDataModel();
RemapShortcuts = new ShortcutsKeyDataModel();
}
+
+ public string ToJsonString()
+ {
+ return JsonSerializer.Serialize(this);
+ }
+
+ public string GetModuleName()
+ {
+ return KeyboardManagerSettings.ModuleName;
+ }
+
+ // This can be utilized in the future if the default.json file is to be modified/deleted.
+ public bool UpgradeSettingsConfiguration()
+ {
+ return false;
+ }
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/KeyboardManagerSettings.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/KeyboardManagerSettings.cs
index 12ff998159..ffe032a47e 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/KeyboardManagerSettings.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/KeyboardManagerSettings.cs
@@ -3,11 +3,14 @@
// See the LICENSE file in the project root for more information.
using System.Text.Json.Serialization;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
namespace Microsoft.PowerToys.Settings.UI.Lib
{
- public class KeyboardManagerSettings : BasePTModuleSettings
+ public class KeyboardManagerSettings : BasePTModuleSettings, ISettingsConfig
{
+ public const string ModuleName = "Keyboard Manager";
+
[JsonPropertyName("properties")]
public KeyboardManagerProperties Properties { get; set; }
@@ -15,14 +18,18 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
{
Properties = new KeyboardManagerProperties();
Version = "1";
- Name = "_unset_";
+ Name = ModuleName;
}
- public KeyboardManagerSettings(string ptName)
+ public string GetModuleName()
{
- Properties = new KeyboardManagerProperties();
- Version = "1";
- Name = ptName;
+ return Name;
+ }
+
+ // This can be utilized in the future if the settings.json file is to be modified/deleted.
+ public bool UpgradeSettingsConfiguration()
+ {
+ return false;
}
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/KeysDataModel.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/KeysDataModel.cs
index 286e5510ae..5085c7fa8a 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/KeysDataModel.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/KeysDataModel.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;
+using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.PowerToys.Settings.UI.Lib.Utilities;
@@ -17,7 +18,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
[JsonPropertyName("newRemapKeys")]
public string NewRemapKeys { get; set; }
- private List MapKeys(string stringOfKeys)
+ private static List MapKeys(string stringOfKeys)
{
return stringOfKeys
.Split(';')
@@ -26,14 +27,19 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
.ToList();
}
- public List GetOriginalKeys()
+ public List GetMappedOriginalKeys()
{
return MapKeys(OriginalKeys);
}
- public List GetNewRemapKeys()
+ public List GetMappedNewRemapKeys()
{
return MapKeys(NewRemapKeys);
}
+
+ public string ToJsonString()
+ {
+ return JsonSerializer.Serialize(this);
+ }
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/PowerLauncherSettings.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/PowerLauncherSettings.cs
index 7eddad108f..72aaedd9e0 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/PowerLauncherSettings.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/PowerLauncherSettings.cs
@@ -5,11 +5,11 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
-using Microsoft.PowerToys.Settings.UI.Lib.Utilities;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
namespace Microsoft.PowerToys.Settings.UI.Lib
{
- public class PowerLauncherSettings : BasePTModuleSettings
+ public class PowerLauncherSettings : BasePTModuleSettings, ISettingsConfig
{
public const string ModuleName = "PowerToys Run";
@@ -31,7 +31,23 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
WriteIndented = true,
};
+ if (settingsUtils == null)
+ {
+ throw new ArgumentNullException(nameof(settingsUtils));
+ }
+
settingsUtils.SaveSettings(JsonSerializer.Serialize(this, options), ModuleName);
}
+
+ public string GetModuleName()
+ {
+ return Name;
+ }
+
+ // This can be utilized in the future if the settings.json file is to be modified/deleted.
+ public bool UpgradeSettingsConfiguration()
+ {
+ return false;
+ }
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/PowerPreviewProperties.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/PowerPreviewProperties.cs
index 1b69a4c75b..5cdc009e79 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/PowerPreviewProperties.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/PowerPreviewProperties.cs
@@ -72,7 +72,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
return JsonSerializer.Serialize(this);
}
- private void LogTelemetryEvent(bool value, [CallerMemberName] string propertyName = null)
+ private static void LogTelemetryEvent(bool value, [CallerMemberName] string propertyName = null)
{
var dataEvent = new SettingsEnabledEvent()
{
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/PowerPreviewSettings.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/PowerPreviewSettings.cs
index afe67754f5..0e41d098cf 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/PowerPreviewSettings.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/PowerPreviewSettings.cs
@@ -3,14 +3,15 @@
// See the LICENSE file in the project root for more information.
using System.Text.Json.Serialization;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
namespace Microsoft.PowerToys.Settings.UI.Lib
{
- public class PowerPreviewSettings : BasePTModuleSettings
+ public class PowerPreviewSettings : BasePTModuleSettings, ISettingsConfig
{
public const string ModuleName = "File Explorer";
- [JsonPropertyName("Properties")]
+ [JsonPropertyName("properties")]
public PowerPreviewProperties Properties { get; set; }
public PowerPreviewSettings()
@@ -20,11 +21,15 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
Name = ModuleName;
}
- public PowerPreviewSettings(string ptName)
+ public string GetModuleName()
{
- Properties = new PowerPreviewProperties();
- Version = "1";
- Name = ptName;
+ return Name;
+ }
+
+ // This can be utilized in the future if the settings.json file is to be modified/deleted.
+ public bool UpgradeSettingsConfiguration()
+ {
+ return false;
}
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/PowerRenameLocalProperties.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/PowerRenameLocalProperties.cs
index a0ba1966fe..3136edd2a6 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/PowerRenameLocalProperties.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/PowerRenameLocalProperties.cs
@@ -3,10 +3,11 @@
// See the LICENSE file in the project root for more information.
using System.Text.Json;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
namespace Microsoft.PowerToys.Settings.UI.Lib
{
- public class PowerRenameLocalProperties
+ public class PowerRenameLocalProperties : ISettingsConfig
{
public PowerRenameLocalProperties()
{
@@ -51,5 +52,18 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
{
return JsonSerializer.Serialize(this);
}
+
+ // This function is required to implement the ISettingsConfig interface and obtain the settings configurations.
+ public string GetModuleName()
+ {
+ string moduleName = PowerRenameSettings.ModuleName;
+ return moduleName;
+ }
+
+ // This can be utilized in the future if the settings.json file is to be modified/deleted.
+ public bool UpgradeSettingsConfiguration()
+ {
+ return false;
+ }
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/PowerRenameSettings.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/PowerRenameSettings.cs
index a3be125bc5..9b89e38f88 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/PowerRenameSettings.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/PowerRenameSettings.cs
@@ -2,11 +2,13 @@
// 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.Text.Json.Serialization;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
namespace Microsoft.PowerToys.Settings.UI.Lib
{
- public class PowerRenameSettings : BasePTModuleSettings
+ public class PowerRenameSettings : BasePTModuleSettings, ISettingsConfig
{
public const string ModuleName = "PowerRename";
@@ -22,6 +24,11 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
public PowerRenameSettings(PowerRenameLocalProperties localProperties)
{
+ if (localProperties == null)
+ {
+ throw new ArgumentNullException(nameof(localProperties));
+ }
+
Properties = new PowerRenameProperties();
Properties.PersistState.Value = localProperties.PersistState;
Properties.MRUEnabled.Value = localProperties.MRUEnabled;
@@ -39,5 +46,16 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
Version = "1";
Name = ptName;
}
+
+ public string GetModuleName()
+ {
+ return Name;
+ }
+
+ // This can be utilized in the future if the power-rename-settings.json file is to be modified/deleted.
+ public bool UpgradeSettingsConfiguration()
+ {
+ return false;
+ }
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/RemapKeysDataModel.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/RemapKeysDataModel.cs
index 4f359197ae..894d97483d 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/RemapKeysDataModel.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/RemapKeysDataModel.cs
@@ -3,18 +3,29 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
+using System.Text.Json;
using System.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Lib
{
public class RemapKeysDataModel
{
+ // Suppressing this warning because removing the setter breaks
+ // deserialization with System.Text.Json. This affects the UI display.
+ // See: https://github.com/dotnet/runtime/issues/30258
[JsonPropertyName("inProcess")]
+#pragma warning disable CA2227 // Collection properties should be read only
public List InProcessRemapKeys { get; set; }
+#pragma warning restore CA2227 // Collection properties should be read only
public RemapKeysDataModel()
{
InProcessRemapKeys = new List();
}
+
+ public string ToJsonString()
+ {
+ return JsonSerializer.Serialize(this);
+ }
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/SettingsRepository`1.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/SettingsRepository`1.cs
new file mode 100644
index 0000000000..8930bf23d4
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/SettingsRepository`1.cs
@@ -0,0 +1,70 @@
+// 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.PowerToys.Settings.UI.Lib.Interface;
+
+namespace Microsoft.PowerToys.Settings.UI.Lib
+{
+ // This Singleton class is a wrapper around the settings configurations that are accessed by viewmodels.
+ // This class can have only one instance and therefore the settings configurations are common to all.
+ public class SettingsRepository : ISettingsRepository
+ where T : class, ISettingsConfig, new()
+ {
+ private static readonly object _SettingsRepoLock = new object();
+
+ private static ISettingsUtils _settingsUtils;
+
+ private static SettingsRepository settingsRepository;
+
+ private T settingsConfig;
+
+ // Suppressing the warning as this is a singleton class and this method is
+ // necessarily static
+#pragma warning disable CA1000 // Do not declare static members on generic types
+ public static SettingsRepository GetInstance(ISettingsUtils settingsUtils)
+#pragma warning restore CA1000 // Do not declare static members on generic types
+ {
+ // To ensure that only one instance of Settings Repository is created in a multi-threaded environment.
+ lock (_SettingsRepoLock)
+ {
+ if (settingsRepository == null)
+ {
+ settingsRepository = new SettingsRepository();
+ _settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
+ }
+
+ return settingsRepository;
+ }
+ }
+
+ // The Singleton class must have a private constructor so that it cannot be instantiated by any other object other than itself.
+ private SettingsRepository()
+ {
+ }
+
+ // Settings configurations shared across all viewmodels
+ public T SettingsConfig
+ {
+ get
+ {
+ if (settingsConfig == null)
+ {
+ T settingsItem = new T();
+ settingsConfig = _settingsUtils.GetSettings(settingsItem.GetModuleName());
+ }
+
+ return settingsConfig;
+ }
+
+ set
+ {
+ if (value != null)
+ {
+ settingsConfig = value;
+ }
+ }
+ }
+ }
+}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/SettingsUtils.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/SettingsUtils.cs
index b0096bbb9c..681727ba22 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/SettingsUtils.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/SettingsUtils.cs
@@ -4,6 +4,7 @@
using System;
using System.Text.Json;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
using Microsoft.PowerToys.Settings.UI.Lib.Utilities;
namespace Microsoft.PowerToys.Settings.UI.Lib
@@ -59,16 +60,42 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
///
/// Get a Deserialized object of the json settings string.
+ /// This function creates a file in the powertoy folder if it does not exist and returns an object with default properties.
///
/// Deserialized json settings object.
public T GetSettings(string powertoy = DefaultModuleName, string fileName = DefaultFileName)
+ where T : ISettingsConfig, new()
+ {
+ if (SettingsExists(powertoy, fileName))
+ {
+ // Given the file already exists, to deserialize the file and read it's content.
+ T deserializedSettings = GetFile(powertoy, fileName);
+
+ // IF the file needs to be modified, to save the new configurations accordingly.
+ if (deserializedSettings.UpgradeSettingsConfiguration())
+ {
+ SaveSettings(deserializedSettings.ToJsonString(), powertoy, fileName);
+ }
+
+ return deserializedSettings;
+ }
+ else
+ {
+ // If the settings file does not exist, to create a new object with default parameters and save it to a newly created settings file.
+ T newSettingsItem = new T();
+ SaveSettings(newSettingsItem.ToJsonString(), powertoy, fileName);
+ return newSettingsItem;
+ }
+ }
+
+ // Given the powerToy folder name and filename to be accessed, this function deserializes and returns the file.
+ private T GetFile(string powertoyFolderName = DefaultModuleName, string fileName = DefaultFileName)
{
// Adding Trim('\0') to overcome possible NTFS file corruption.
// Look at issue https://github.com/microsoft/PowerToys/issues/6413 you'll see the file has a large sum of \0 to fill up a 4096 byte buffer for writing to disk
// This, while not totally ideal, does work around the problem by trimming the end.
// The file itself did write the content correctly but something is off with the actual end of the file, hence the 0x00 bug
- var jsonSettingsString = _ioProvider.ReadAllText(GetSettingsPath(powertoy, fileName)).Trim('\0');
-
+ var jsonSettingsString = _ioProvider.ReadAllText(GetSettingsPath(powertoyFolderName, fileName)).Trim('\0');
return JsonSerializer.Deserialize(jsonSettingsString);
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ShortcutGuideProperties.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ShortcutGuideProperties.cs
index 4d3bbaf3bc..38a46a0cbb 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ShortcutGuideProperties.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ShortcutGuideProperties.cs
@@ -12,7 +12,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
{
OverlayOpacity = new IntProperty(90);
PressTime = new IntProperty(900);
- Theme = new StringProperty("light");
+ Theme = new StringProperty("system");
}
[JsonPropertyName("overlay_opacity")]
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ShortcutGuideSettings.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ShortcutGuideSettings.cs
index 6b5e41e631..343649fd80 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ShortcutGuideSettings.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ShortcutGuideSettings.cs
@@ -3,10 +3,11 @@
// See the LICENSE file in the project root for more information.
using System.Text.Json.Serialization;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
namespace Microsoft.PowerToys.Settings.UI.Lib
{
- public class ShortcutGuideSettings : BasePTModuleSettings
+ public class ShortcutGuideSettings : BasePTModuleSettings, ISettingsConfig
{
public const string ModuleName = "Shortcut Guide";
@@ -19,5 +20,16 @@ namespace Microsoft.PowerToys.Settings.UI.Lib
Properties = new ShortcutGuideProperties();
Version = "1.0";
}
+
+ public string GetModuleName()
+ {
+ return Name;
+ }
+
+ // This can be utilized in the future if the settings.json file is to be modified/deleted.
+ public bool UpgradeSettingsConfiguration()
+ {
+ return false;
+ }
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ShortcutsKeyDataModel.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ShortcutsKeyDataModel.cs
index 0e1e131fe3..91db9695db 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ShortcutsKeyDataModel.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ShortcutsKeyDataModel.cs
@@ -3,22 +3,35 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
+using System.Text.Json;
using System.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Lib
{
public class ShortcutsKeyDataModel
{
+ // Suppressing these warnings because removing the setter breaks
+ // deserialization with System.Text.Json. This affects the UI display.
+ // See: https://github.com/dotnet/runtime/issues/30258
[JsonPropertyName("global")]
+#pragma warning disable CA2227 // Collection properties should be read only
public List GlobalRemapShortcuts { get; set; }
+#pragma warning restore CA2227 // Collection properties should be read only
[JsonPropertyName("appSpecific")]
+#pragma warning disable CA2227 // Collection properties should be read only
public List AppSpecificRemapShortcuts { get; set; }
+#pragma warning restore CA2227 // Collection properties should be read only
public ShortcutsKeyDataModel()
{
GlobalRemapShortcuts = new List();
AppSpecificRemapShortcuts = new List();
}
+
+ public string ToJsonString()
+ {
+ return JsonSerializer.Serialize(this);
+ }
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/Utilities/Helper.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/Utilities/Helper.cs
index d59ca5ccf9..e6b5d723d4 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/Utilities/Helper.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/Utilities/Helper.cs
@@ -11,7 +11,7 @@ using Microsoft.PowerToys.Settings.UI.Lib.CustomAction;
namespace Microsoft.PowerToys.Settings.UI.Lib.Utilities
{
- public class Helper
+ public static class Helper
{
public static bool AllowRunnerToForeground()
{
@@ -20,7 +20,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.Utilities
if (processes.Length > 0)
{
var pid = processes[0].Id;
- result = AllowSetForegroundWindow(pid);
+ result = NativeMethods.AllowSetForegroundWindow(pid);
}
return result;
@@ -74,9 +74,6 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.Utilities
return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
}
- [DllImport("user32.dll")]
- private static extern bool AllowSetForegroundWindow(int dwProcessId);
-
private static readonly interop.LayoutMapManaged LayoutMap = new interop.LayoutMapManaged();
public static string GetKeyName(uint key)
@@ -95,10 +92,19 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.Utilities
{
// Split up the version strings into int[]
// Example: v10.0.2 -> {10, 0, 2};
+ if (version1 == null)
+ {
+ throw new ArgumentNullException(nameof(version1));
+ }
+ else if (version2 == null)
+ {
+ throw new ArgumentNullException(nameof(version2));
+ }
+
var v1 = version1.Substring(1).Split('.').Select(int.Parse).ToArray();
var v2 = version2.Substring(1).Split('.').Select(int.Parse).ToArray();
- if (v1.Count() != 3 || v2.Count() != 3)
+ if (v1.Length != 3 || v2.Length != 3)
{
throw new FormatException();
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/Utilities/NativeMethods.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/Utilities/NativeMethods.cs
new file mode 100644
index 0000000000..efd486abcc
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/Utilities/NativeMethods.cs
@@ -0,0 +1,17 @@
+// 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.Runtime.InteropServices;
+using System.Text;
+
+namespace Microsoft.PowerToys.Settings.UI.Lib.Utilities
+{
+ internal static class NativeMethods
+ {
+ [DllImport("user32.dll")]
+ public static extern bool AllowSetForegroundWindow(int dwProcessId);
+ }
+}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/Utilities/SystemIOProvider.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/Utilities/SystemIOProvider.cs
index be01a3578c..bfcb4d55f7 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/Utilities/SystemIOProvider.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/Utilities/SystemIOProvider.cs
@@ -16,7 +16,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.Utilities
public void DeleteDirectory(string path)
{
- Directory.Delete(path);
+ Directory.Delete(path, recursive: true);
}
public bool DirectoryExists(string path)
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/ColorPickerViewModel.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/ColorPickerViewModel.cs
index 6d49d42355..a56ae1c537 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/ColorPickerViewModel.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/ColorPickerViewModel.cs
@@ -3,22 +3,35 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Globalization;
using System.Text.Json;
using Microsoft.PowerToys.Settings.UI.Lib.Helpers;
-using Microsoft.PowerToys.Settings.UI.Lib.Utilities;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
public class ColorPickerViewModel : Observable
{
+ private GeneralSettings GeneralSettingsConfig { get; set; }
+
private readonly ISettingsUtils _settingsUtils;
+
private ColorPickerSettings _colorPickerSettings;
+
private bool _isEnabled;
private Func SendConfigMSG { get; }
- public ColorPickerViewModel(ISettingsUtils settingsUtils, Func ipcMSGCallBackFunc)
+ public ColorPickerViewModel(ISettingsUtils settingsUtils, ISettingsRepository settingsRepository, Func ipcMSGCallBackFunc)
{
+ // Obtain the general PowerToy settings configurations
+ if (settingsRepository == null)
+ {
+ throw new ArgumentNullException(nameof(settingsRepository));
+ }
+
+ GeneralSettingsConfig = settingsRepository.SettingsConfig;
+
_settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
if (_settingsUtils.SettingsExists(ColorPickerSettings.ModuleName))
{
@@ -29,11 +42,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
_colorPickerSettings = new ColorPickerSettings();
}
- if (_settingsUtils.SettingsExists())
- {
- var generalSettings = _settingsUtils.GetSettings();
- _isEnabled = generalSettings.Enabled.ColorPicker;
- }
+ _isEnabled = GeneralSettingsConfig.Enabled.ColorPicker;
// set the callback functions value to hangle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
@@ -53,10 +62,10 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
_isEnabled = value;
OnPropertyChanged(nameof(IsEnabled));
- // grab the latest version of settings
- var generalSettings = _settingsUtils.GetSettings();
- generalSettings.Enabled.ColorPicker = value;
- OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(generalSettings);
+ // Set the status of ColorPicker in the general settings
+ GeneralSettingsConfig.Enabled.ColorPicker = value;
+ OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
+
SendConfigMSG(outgoing.ToString());
}
}
@@ -118,8 +127,13 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
private void NotifySettingsChanged()
{
+ // Using InvariantCulture as this is an IPC message
SendConfigMSG(
- string.Format("{{ \"powertoys\": {{ \"{0}\": {1} }} }}", ColorPickerSettings.ModuleName, JsonSerializer.Serialize(_colorPickerSettings)));
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
+ ColorPickerSettings.ModuleName,
+ JsonSerializer.Serialize(_colorPickerSettings)));
}
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/FancyZonesViewModel.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/FancyZonesViewModel.cs
index 46929bb2a5..86acc34cc8 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/FancyZonesViewModel.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/FancyZonesViewModel.cs
@@ -4,18 +4,19 @@
using System;
using System.Drawing;
+using System.Globalization;
using System.Runtime.CompilerServices;
using Microsoft.PowerToys.Settings.UI.Lib.Helpers;
-using Microsoft.PowerToys.Settings.UI.Lib.Utilities;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
using Microsoft.PowerToys.Settings.UI.Lib.ViewModels.Commands;
namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
public class FancyZonesViewModel : Observable
{
- private readonly ISettingsUtils _settingsUtils;
+ private GeneralSettings GeneralSettingsConfig { get; set; }
- private const string ModuleName = "FancyZones";
+ private const string ModuleName = FancyZonesSettings.ModuleName;
public ButtonClickCommand LaunchEditorEventHandler { get; set; }
@@ -25,21 +26,25 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
private string settingsConfigFileFolder = string.Empty;
- public FancyZonesViewModel(ISettingsUtils settingsUtils, Func ipcMSGCallBackFunc, string configFileSubfolder = "")
+ public FancyZonesViewModel(ISettingsRepository settingsRepository, ISettingsRepository moduleSettingsRepository, Func ipcMSGCallBackFunc, string configFileSubfolder = "")
{
- settingsConfigFileFolder = configFileSubfolder;
- _settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
+ // To obtain the general settings configurations of PowerToys Settings.
+ if (settingsRepository == null)
+ {
+ throw new ArgumentNullException(nameof(settingsRepository));
+ }
- try
+ GeneralSettingsConfig = settingsRepository.SettingsConfig;
+ settingsConfigFileFolder = configFileSubfolder;
+
+ // To obtain the settings configurations of Fancy zones.
+ if (moduleSettingsRepository == null)
{
- Settings = _settingsUtils.GetSettings(GetSettingsSubPath());
- }
- catch
- {
- Settings = new FancyZonesSettings();
- _settingsUtils.SaveSettings(Settings.ToJsonString(), GetSettingsSubPath());
+ throw new ArgumentNullException(nameof(moduleSettingsRepository));
}
+ Settings = moduleSettingsRepository.SettingsConfig;
+
LaunchEditorEventHandler = new ButtonClickCommand(LaunchEditor);
_shiftDrag = Settings.Properties.FancyzonesShiftDrag.Value;
@@ -64,26 +69,15 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
SendConfigMSG = ipcMSGCallBackFunc;
string inactiveColor = Settings.Properties.FancyzonesInActiveColor.Value;
- _zoneInActiveColor = inactiveColor != string.Empty ? inactiveColor : "#F5FCFF";
+ _zoneInActiveColor = !string.IsNullOrEmpty(inactiveColor) ? inactiveColor : "#F5FCFF";
string borderColor = Settings.Properties.FancyzonesBorderColor.Value;
- _zoneBorderColor = borderColor != string.Empty ? borderColor : "#FFFFFF";
+ _zoneBorderColor = !string.IsNullOrEmpty(borderColor) ? borderColor : "#FFFFFF";
string highlightColor = Settings.Properties.FancyzonesZoneHighlightColor.Value;
- _zoneHighlightColor = highlightColor != string.Empty ? highlightColor : "#0078D7";
+ _zoneHighlightColor = !string.IsNullOrEmpty(highlightColor) ? highlightColor : "#0078D7";
- GeneralSettings generalSettings;
- try
- {
- generalSettings = _settingsUtils.GetSettings(string.Empty);
- }
- catch
- {
- generalSettings = new GeneralSettings();
- _settingsUtils.SaveSettings(generalSettings.ToJsonString(), string.Empty);
- }
-
- _isEnabled = generalSettings.Enabled.FancyZones;
+ _isEnabled = GeneralSettingsConfig.Enabled.FancyZones;
}
private bool _isEnabled;
@@ -121,13 +115,14 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
if (value != _isEnabled)
{
_isEnabled = value;
- GeneralSettings generalSettings = _settingsUtils.GetSettings(string.Empty);
- generalSettings.Enabled.FancyZones = value;
- OutGoingGeneralSettings snd = new OutGoingGeneralSettings(generalSettings);
+
+ // Set the status of FancyZones in the general settings configuration
+ GeneralSettingsConfig.Enabled.FancyZones = value;
+ OutGoingGeneralSettings snd = new OutGoingGeneralSettings(GeneralSettingsConfig);
SendConfigMSG(snd.ToString());
- OnPropertyChanged("IsEnabled");
- OnPropertyChanged("SnapHotkeysCategoryEnabled");
+ OnPropertyChanged(nameof(IsEnabled));
+ OnPropertyChanged(nameof(SnapHotkeysCategoryEnabled));
}
}
}
@@ -153,7 +148,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
_shiftDrag = value;
Settings.Properties.FancyzonesShiftDrag.Value = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -171,7 +166,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
_mouseSwitch = value;
Settings.Properties.FancyzonesMouseSwitch.Value = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -194,8 +189,8 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
_overrideSnapHotkeys = value;
Settings.Properties.FancyzonesOverrideSnapHotkeys.Value = value;
- RaisePropertyChanged();
- OnPropertyChanged("SnapHotkeysCategoryEnabled");
+ NotifyPropertyChanged();
+ OnPropertyChanged(nameof(SnapHotkeysCategoryEnabled));
}
}
}
@@ -213,7 +208,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
_moveWindowsAcrossMonitors = value;
Settings.Properties.FancyzonesMoveWindowsAcrossMonitors.Value = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -231,7 +226,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
_moveWindowsBasedOnPosition = value;
Settings.Properties.FancyzonesMoveWindowsBasedOnPosition.Value = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -249,7 +244,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
_displayChangemoveWindows = value;
Settings.Properties.FancyzonesDisplayChangeMoveWindows.Value = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -267,7 +262,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
_zoneSetChangeMoveWindows = value;
Settings.Properties.FancyzonesZoneSetChangeMoveWindows.Value = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -285,7 +280,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
_appLastZoneMoveWindows = value;
Settings.Properties.FancyzonesAppLastZoneMoveWindows.Value = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -303,7 +298,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
_openWindowOnActiveMonitor = value;
Settings.Properties.FancyzonesOpenWindowOnActiveMonitor.Value = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -321,7 +316,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
_restoreSize = value;
Settings.Properties.FancyzonesRestoreSize.Value = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -339,7 +334,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
_useCursorPosEditorStartupScreen = value;
Settings.Properties.UseCursorposEditorStartupscreen.Value = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -357,7 +352,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
_showOnAllMonitors = value;
Settings.Properties.FancyzonesShowOnAllMonitors.Value = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -375,7 +370,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
_spanZonesAcrossMonitors = value;
Settings.Properties.FancyzonesSpanZonesAcrossMonitors.Value = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -393,11 +388,13 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
_makeDraggedWindowTransparent = value;
Settings.Properties.FancyzonesMakeDraggedWindowTransparent.Value = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
+ // For the following setters we use OrdinalIgnoreCase string comparison since
+ // we expect value to be a hex code.
public string ZoneHighlightColor
{
get
@@ -407,12 +404,15 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
set
{
- value = ToRGBHex(value);
- if (!value.Equals(_zoneHighlightColor))
+ // The fallback value is based on ToRGBHex's behavior, which returns
+ // #FFFFFF if any exceptions are encountered, e.g. from passing in a null value.
+ // This extra handling is added here to deal with FxCop warnings.
+ value = (value != null) ? ToRGBHex(value) : "#FFFFFF";
+ if (!value.Equals(_zoneHighlightColor, StringComparison.OrdinalIgnoreCase))
{
_zoneHighlightColor = value;
Settings.Properties.FancyzonesZoneHighlightColor.Value = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -426,12 +426,15 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
set
{
- value = ToRGBHex(value);
+ // The fallback value is based on ToRGBHex's behavior, which returns
+ // #FFFFFF if any exceptions are encountered, e.g. from passing in a null value.
+ // This extra handling is added here to deal with FxCop warnings.
+ value = (value != null) ? ToRGBHex(value) : "#FFFFFF";
if (!value.Equals(_zoneBorderColor, StringComparison.OrdinalIgnoreCase))
{
_zoneBorderColor = value;
Settings.Properties.FancyzonesBorderColor.Value = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -445,12 +448,15 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
set
{
- value = ToRGBHex(value);
- if (!value.Equals(_zoneInActiveColor))
+ // The fallback value is based on ToRGBHex's behavior, which returns
+ // #FFFFFF if any exceptions are encountered, e.g. from passing in a null value.
+ // This extra handling is added here to deal with FxCop warnings.
+ value = (value != null) ? ToRGBHex(value) : "#FFFFFF";
+ if (!value.Equals(_zoneInActiveColor, StringComparison.OrdinalIgnoreCase))
{
_zoneInActiveColor = value;
Settings.Properties.FancyzonesInActiveColor.Value = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -468,7 +474,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
_highlightOpacity = value;
Settings.Properties.FancyzonesHighlightOpacity.Value = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -484,7 +490,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
if (value != _editorHotkey)
{
- if (value.IsEmpty())
+ if (value == null || value.IsEmpty())
{
_editorHotkey = FZConfigProperties.DefaultHotkeyValue;
}
@@ -494,7 +500,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
}
Settings.Properties.FancyzonesEditorHotkey.Value = _editorHotkey;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -512,7 +518,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
_excludedApps = value;
Settings.Properties.FancyzonesExcludedApps.Value = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -523,7 +529,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
SendConfigMSG("{\"action\":{\"FancyZones\":{\"action_name\":\"ToggledFZEditor\", \"value\":\"\"}}}");
}
- public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
+ public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
OnPropertyChanged(propertyName);
if (SendConfigMSG != null)
@@ -534,13 +540,19 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
}
}
- private string ToRGBHex(string color)
+ private static string ToRGBHex(string color)
{
try
{
- int argb = int.Parse(color.Replace("#", string.Empty), System.Globalization.NumberStyles.HexNumber);
+ // Using InvariantCulture as these are expected to be hex codes.
+ int argb = int.Parse(
+ color.Replace("#", string.Empty),
+ System.Globalization.NumberStyles.HexNumber,
+ CultureInfo.InvariantCulture);
Color clr = Color.FromArgb(argb);
- return "#" + clr.R.ToString("X2") + clr.G.ToString("X2") + clr.B.ToString("X2");
+ return "#" + clr.R.ToString("X2", CultureInfo.InvariantCulture) +
+ clr.G.ToString("X2", CultureInfo.InvariantCulture) +
+ clr.B.ToString("X2", CultureInfo.InvariantCulture);
}
catch (Exception)
{
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/GeneralViewModel.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/GeneralViewModel.cs
index 19864f120c..fe6a83c0fc 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/GeneralViewModel.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/GeneralViewModel.cs
@@ -3,9 +3,10 @@
// See the LICENSE file in the project root for more information.
using System;
-using System.Diagnostics;
+using System.Globalization;
using System.Runtime.CompilerServices;
using Microsoft.PowerToys.Settings.UI.Lib.Helpers;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
using Microsoft.PowerToys.Settings.UI.Lib.Utilities;
using Microsoft.PowerToys.Settings.UI.Lib.ViewModels.Commands;
@@ -13,11 +14,9 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
public class GeneralViewModel : Observable
{
- private readonly ISettingsUtils _settingsUtils;
+ private GeneralSettings GeneralSettingsConfig { get; set; }
- private GeneralSettings GeneralSettingsConfigs { get; set; }
-
- public ButtonClickCommand CheckFoUpdatesEventHandler { get; set; }
+ public ButtonClickCommand CheckForUpdatesEventHandler { get; set; }
public ButtonClickCommand RestartElevatedButtonEventHandler { get; set; }
@@ -35,33 +34,18 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
private string _settingsConfigFileFolder = string.Empty;
- public GeneralViewModel(ISettingsUtils settingsUtils, string runAsAdminText, string runAsUserText, bool isElevated, bool isAdmin, Func updateTheme, Func ipcMSGCallBackFunc, Func ipcMSGRestartAsAdminMSGCallBackFunc, Func ipcMSGCheckForUpdatesCallBackFunc, string configFileSubfolder = "")
+ public GeneralViewModel(ISettingsRepository settingsRepository, string runAsAdminText, string runAsUserText, bool isElevated, bool isAdmin, Func updateTheme, Func ipcMSGCallBackFunc, Func ipcMSGRestartAsAdminMSGCallBackFunc, Func ipcMSGCheckForUpdatesCallBackFunc, string configFileSubfolder = "")
{
- CheckFoUpdatesEventHandler = new ButtonClickCommand(CheckForUpdates_Click);
- RestartElevatedButtonEventHandler = new ButtonClickCommand(Restart_Elevated);
- _settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
+ CheckForUpdatesEventHandler = new ButtonClickCommand(CheckForUpdatesClick);
+ RestartElevatedButtonEventHandler = new ButtonClickCommand(RestartElevated);
- try
+ // To obtain the general settings configuration of PowerToys if it exists, else to create a new file and return the default configurations.
+ if (settingsRepository == null)
{
- GeneralSettingsConfigs = _settingsUtils.GetSettings(string.Empty);
+ throw new ArgumentNullException(nameof(settingsRepository));
+ }
- if (Helper.CompareVersions(GeneralSettingsConfigs.PowertoysVersion, Helper.GetProductVersion()) < 0)
- {
- // Update settings
- GeneralSettingsConfigs.PowertoysVersion = Helper.GetProductVersion();
- _settingsUtils.SaveSettings(GeneralSettingsConfigs.ToJsonString(), string.Empty);
- }
- }
- catch (FormatException e)
- {
- // If there is an issue with the version number format, don't migrate settings.
- Debug.WriteLine(e.Message);
- }
- catch
- {
- GeneralSettingsConfigs = new GeneralSettings();
- _settingsUtils.SaveSettings(GeneralSettingsConfigs.ToJsonString(), string.Empty);
- }
+ GeneralSettingsConfig = settingsRepository.SettingsConfig;
// set the callback functions value to hangle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
@@ -70,28 +54,33 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
// set the callback function value to update the UI theme.
UpdateUIThemeCallBack = updateTheme;
- UpdateUIThemeCallBack(GeneralSettingsConfigs.Theme.ToLower());
+
+ UpdateUIThemeCallBack(GeneralSettingsConfig.Theme);
// Update Settings file folder:
_settingsConfigFileFolder = configFileSubfolder;
- switch (GeneralSettingsConfigs.Theme.ToLower())
+ // Using Invariant here as these are internal strings and fxcop
+ // expects strings to be normalized to uppercase. While the theme names
+ // are represented in lowercase everywhere else, we'll use uppercase
+ // normalization for switch statements
+ switch (GeneralSettingsConfig.Theme.ToUpperInvariant())
{
- case "light":
+ case "LIGHT":
_isLightThemeRadioButtonChecked = true;
break;
- case "dark":
+ case "DARK":
_isDarkThemeRadioButtonChecked = true;
break;
- case "system":
+ case "SYSTEM":
_isSystemThemeRadioButtonChecked = true;
break;
}
- _startup = GeneralSettingsConfigs.Startup;
- _autoDownloadUpdates = GeneralSettingsConfigs.AutoDownloadUpdates;
+ _startup = GeneralSettingsConfig.Startup;
+ _autoDownloadUpdates = GeneralSettingsConfig.AutoDownloadUpdates;
_isElevated = isElevated;
- _runElevated = GeneralSettingsConfigs.RunElevated;
+ _runElevated = GeneralSettingsConfig.RunElevated;
RunningAsUserDefaultText = runAsUserText;
RunningAsAdminDefaultText = runAsAdminText;
@@ -99,15 +88,15 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
_isAdmin = isAdmin;
}
- private bool _packaged = false;
- private bool _startup = false;
- private bool _isElevated = false;
- private bool _runElevated = false;
- private bool _isAdmin = false;
- private bool _isDarkThemeRadioButtonChecked = false;
- private bool _isLightThemeRadioButtonChecked = false;
- private bool _isSystemThemeRadioButtonChecked = false;
- private bool _autoDownloadUpdates = false;
+ private bool _packaged;
+ private bool _startup;
+ private bool _isElevated;
+ private bool _runElevated;
+ private bool _isAdmin;
+ private bool _isDarkThemeRadioButtonChecked;
+ private bool _isLightThemeRadioButtonChecked;
+ private bool _isSystemThemeRadioButtonChecked;
+ private bool _autoDownloadUpdates;
private string _latestAvailableVersion = string.Empty;
@@ -124,7 +113,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
if (_packaged != value)
{
_packaged = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -142,8 +131,8 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
if (_startup != value)
{
_startup = value;
- GeneralSettingsConfigs.Startup = value;
- RaisePropertyChanged();
+ GeneralSettingsConfig.Startup = value;
+ NotifyPropertyChanged();
}
}
}
@@ -181,8 +170,8 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
if (_isElevated != value)
{
_isElevated = value;
- OnPropertyChanged("IsElevated");
- OnPropertyChanged("IsAdminButtonEnabled");
+ OnPropertyChanged(nameof(IsElevated));
+ OnPropertyChanged(nameof(IsAdminButtonEnabled));
OnPropertyChanged("RunningAsAdminText");
}
}
@@ -197,7 +186,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
set
{
- OnPropertyChanged("IsAdminButtonEnabled");
+ OnPropertyChanged(nameof(IsAdminButtonEnabled));
}
}
@@ -214,8 +203,8 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
if (_runElevated != value)
{
_runElevated = value;
- GeneralSettingsConfigs.RunElevated = value;
- RaisePropertyChanged();
+ GeneralSettingsConfig.RunElevated = value;
+ NotifyPropertyChanged();
}
}
}
@@ -241,8 +230,8 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
if (_autoDownloadUpdates != value)
{
_autoDownloadUpdates = value;
- GeneralSettingsConfigs.AutoDownloadUpdates = value;
- RaisePropertyChanged();
+ GeneralSettingsConfig.AutoDownloadUpdates = value;
+ NotifyPropertyChanged();
}
}
}
@@ -258,17 +247,17 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
if (value == true)
{
- GeneralSettingsConfigs.Theme = "dark";
+ GeneralSettingsConfig.Theme = "dark";
_isDarkThemeRadioButtonChecked = value;
try
{
- UpdateUIThemeCallBack(GeneralSettingsConfigs.Theme);
+ UpdateUIThemeCallBack(GeneralSettingsConfig.Theme);
}
catch
{
}
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -284,17 +273,17 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
if (value == true)
{
- GeneralSettingsConfigs.Theme = "light";
+ GeneralSettingsConfig.Theme = "light";
_isLightThemeRadioButtonChecked = value;
try
{
- UpdateUIThemeCallBack(GeneralSettingsConfigs.Theme);
+ UpdateUIThemeCallBack(GeneralSettingsConfig.Theme);
}
catch
{
}
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -310,22 +299,27 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
if (value == true)
{
- GeneralSettingsConfigs.Theme = "system";
+ GeneralSettingsConfig.Theme = "system";
_isSystemThemeRadioButtonChecked = value;
try
{
- UpdateUIThemeCallBack(GeneralSettingsConfigs.Theme);
+ UpdateUIThemeCallBack(GeneralSettingsConfig.Theme);
}
catch
{
}
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
+ // FxCop suggests marking this member static, but it is accessed through
+ // an instance in autogenerated files (GeneralPage.g.cs) and will break
+ // the file if modified
+#pragma warning disable CA1822 // Mark members as static
public string PowerToysVersion
+#pragma warning restore CA1822 // Mark members as static
{
get
{
@@ -346,38 +340,36 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
if (_latestAvailableVersion != value)
{
_latestAvailableVersion = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
- public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
+ public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
// Notify UI of property change
OnPropertyChanged(propertyName);
- OutGoingGeneralSettings outsettings = new OutGoingGeneralSettings(GeneralSettingsConfigs);
+ OutGoingGeneralSettings outsettings = new OutGoingGeneralSettings(GeneralSettingsConfig);
SendConfigMSG(outsettings.ToString());
}
// callback function to launch the URL to check for updates.
- private void CheckForUpdates_Click()
+ private void CheckForUpdatesClick()
{
- GeneralSettings settings = _settingsUtils.GetSettings(_settingsConfigFileFolder);
- settings.CustomActionName = "check_for_updates";
+ GeneralSettingsConfig.CustomActionName = "check_for_updates";
- OutGoingGeneralSettings outsettings = new OutGoingGeneralSettings(settings);
+ OutGoingGeneralSettings outsettings = new OutGoingGeneralSettings(GeneralSettingsConfig);
GeneralSettingsCustomAction customaction = new GeneralSettingsCustomAction(outsettings);
SendCheckForUpdatesConfigMSG(customaction.ToString());
}
- public void Restart_Elevated()
+ public void RestartElevated()
{
- GeneralSettings settings = _settingsUtils.GetSettings(_settingsConfigFileFolder);
- settings.CustomActionName = "restart_elevation";
+ GeneralSettingsConfig.CustomActionName = "restart_elevation";
- OutGoingGeneralSettings outsettings = new OutGoingGeneralSettings(settings);
+ OutGoingGeneralSettings outsettings = new OutGoingGeneralSettings(GeneralSettingsConfig);
GeneralSettingsCustomAction customaction = new GeneralSettingsCustomAction(outsettings);
SendRestartAsAdminConfigMSG(customaction.ToString());
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/ImageResizerViewModel.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/ImageResizerViewModel.cs
index 5110092799..e86b59c9ad 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/ImageResizerViewModel.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/ImageResizerViewModel.cs
@@ -7,24 +7,34 @@ using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using Microsoft.PowerToys.Settings.UI.Lib.Helpers;
-using Microsoft.PowerToys.Settings.UI.Lib.Utilities;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
public class ImageResizerViewModel : Observable
{
+ private GeneralSettings GeneralSettingsConfig { get; set; }
+
private readonly ISettingsUtils _settingsUtils;
private ImageResizerSettings Settings { get; set; }
- private const string ModuleName = "ImageResizer";
+ private const string ModuleName = ImageResizerSettings.ModuleName;
private Func SendConfigMSG { get; }
- public ImageResizerViewModel(ISettingsUtils settingsUtils, Func ipcMSGCallBackFunc)
+ public ImageResizerViewModel(ISettingsUtils settingsUtils, ISettingsRepository settingsRepository, Func ipcMSGCallBackFunc)
{
_settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
+ // To obtain the general settings configurations of PowerToys.
+ if (settingsRepository == null)
+ {
+ throw new ArgumentNullException(nameof(settingsRepository));
+ }
+
+ GeneralSettingsConfig = settingsRepository.SettingsConfig;
+
try
{
Settings = _settingsUtils.GetSettings(ModuleName);
@@ -35,22 +45,10 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
_settingsUtils.SaveSettings(Settings.ToJsonString(), ModuleName);
}
- GeneralSettings generalSettings;
-
- try
- {
- generalSettings = _settingsUtils.GetSettings(string.Empty);
- }
- catch
- {
- generalSettings = new GeneralSettings();
- _settingsUtils.SaveSettings(generalSettings.ToJsonString(), string.Empty);
- }
-
// set the callback functions value to hangle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
- _isEnabled = generalSettings.Enabled.ImageResizer;
+ _isEnabled = GeneralSettingsConfig.Enabled.ImageResizer;
_advancedSizes = Settings.Properties.ImageresizerSizes.Value;
_jpegQualityLevel = Settings.Properties.ImageresizerJpegQualityLevel.Value;
_pngInterlaceOption = Settings.Properties.ImageresizerPngInterlaceOption.Value;
@@ -64,18 +62,18 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
size.Id = i;
i++;
- size.PropertyChanged += Size_PropertyChanged;
+ size.PropertyChanged += SizePropertyChanged;
}
}
- private bool _isEnabled = false;
+ private bool _isEnabled;
private ObservableCollection _advancedSizes = new ObservableCollection();
- private int _jpegQualityLevel = 0;
+ private int _jpegQualityLevel;
private int _pngInterlaceOption;
private int _tiffCompressOption;
private string _fileName;
private bool _keepDateModified;
- private int _encoderGuidId = 0;
+ private int _encoderGuidId;
public bool IsEnabled
{
@@ -88,28 +86,34 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
if (value != _isEnabled)
{
+ // To set the status of ImageResizer in the General PowerToys settings.
_isEnabled = value;
- GeneralSettings generalSettings = _settingsUtils.GetSettings(string.Empty);
- generalSettings.Enabled.ImageResizer = value;
- OutGoingGeneralSettings snd = new OutGoingGeneralSettings(generalSettings);
+ GeneralSettingsConfig.Enabled.ImageResizer = value;
+ OutGoingGeneralSettings snd = new OutGoingGeneralSettings(GeneralSettingsConfig);
+
SendConfigMSG(snd.ToString());
- OnPropertyChanged("IsEnabled");
+ OnPropertyChanged(nameof(IsEnabled));
}
}
}
+#pragma warning disable CA2227 // Collection properties should be read only
public ObservableCollection Sizes
+#pragma warning restore CA2227 // Collection properties should be read only
{
get
{
return _advancedSizes;
}
+ // FxCop demands collection properties to be read-only, but this
+ // setter is used in autogenerated files (ImageResizerPage.g.cs)
+ // and replacing the setter with its own method will break the file
set
{
SavesImageSizes(value);
_advancedSizes = value;
- OnPropertyChanged("Sizes");
+ OnPropertyChanged(nameof(Sizes));
}
}
@@ -127,7 +131,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
_jpegQualityLevel = value;
Settings.Properties.ImageresizerJpegQualityLevel.Value = value;
_settingsUtils.SaveSettings(Settings.ToJsonString(), ModuleName);
- OnPropertyChanged("JPEGQualityLevel");
+ OnPropertyChanged(nameof(JPEGQualityLevel));
}
}
}
@@ -146,7 +150,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
_pngInterlaceOption = value;
Settings.Properties.ImageresizerPngInterlaceOption.Value = value;
_settingsUtils.SaveSettings(Settings.ToJsonString(), ModuleName);
- OnPropertyChanged("PngInterlaceOption");
+ OnPropertyChanged(nameof(PngInterlaceOption));
}
}
}
@@ -165,7 +169,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
_tiffCompressOption = value;
Settings.Properties.ImageresizerTiffCompressOption.Value = value;
_settingsUtils.SaveSettings(Settings.ToJsonString(), ModuleName);
- OnPropertyChanged("TiffCompressOption");
+ OnPropertyChanged(nameof(TiffCompressOption));
}
}
}
@@ -184,7 +188,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
_fileName = value;
Settings.Properties.ImageresizerFileName.Value = value;
_settingsUtils.SaveSettings(Settings.ToJsonString(), ModuleName);
- OnPropertyChanged("FileName");
+ OnPropertyChanged(nameof(FileName));
}
}
}
@@ -201,7 +205,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
_keepDateModified = value;
Settings.Properties.ImageresizerKeepDateModified.Value = value;
_settingsUtils.SaveSettings(Settings.ToJsonString(), ModuleName);
- OnPropertyChanged("KeepDateModified");
+ OnPropertyChanged(nameof(KeepDateModified));
}
}
@@ -220,17 +224,25 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
_settingsUtils.SaveSettings(Settings.Properties.ImageresizerSizes.ToJsonString(), ModuleName, "sizes.json");
Settings.Properties.ImageresizerFallbackEncoder.Value = GetEncoderGuid(value);
_settingsUtils.SaveSettings(Settings.ToJsonString(), ModuleName);
- OnPropertyChanged("Encoder");
+ OnPropertyChanged(nameof(Encoder));
}
}
}
+ public string EncoderGuid
+ {
+ get
+ {
+ return ImageResizerViewModel.GetEncoderGuid(_encoderGuidId);
+ }
+ }
+
public void AddRow()
{
ObservableCollection imageSizes = Sizes;
int maxId = imageSizes.Count > 0 ? imageSizes.OrderBy(x => x.Id).Last().Id : -1;
ImageSize newSize = new ImageSize(maxId + 1);
- newSize.PropertyChanged += Size_PropertyChanged;
+ newSize.PropertyChanged += SizePropertyChanged;
imageSizes.Add(newSize);
_advancedSizes = imageSizes;
SavesImageSizes(imageSizes);
@@ -253,7 +265,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
_settingsUtils.SaveSettings(Settings.ToJsonString(), ModuleName);
}
- public string GetEncoderGuid(int value)
+ public static string GetEncoderGuid(int value)
{
// PNG Encoder guid
if (value == 0)
@@ -294,40 +306,40 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
return null;
}
- public int GetEncoderIndex(string guid)
+ public static int GetEncoderIndex(string value)
{
// PNG Encoder guid
- if (guid == "1b7cfaf4-713f-473c-bbcd-6137425faeaf")
+ if (value == "1b7cfaf4-713f-473c-bbcd-6137425faeaf")
{
return 0;
}
// Bitmap Encoder guid
- else if (guid == "0af1d87e-fcfe-4188-bdeb-a7906471cbe3")
+ else if (value == "0af1d87e-fcfe-4188-bdeb-a7906471cbe3")
{
return 1;
}
// JPEG Encoder guid
- else if (guid == "19e4a5aa-5662-4fc5-a0c0-1758028e1057")
+ else if (value == "19e4a5aa-5662-4fc5-a0c0-1758028e1057")
{
return 2;
}
// Tiff encoder guid.
- else if (guid == "163bcc30-e2e9-4f0b-961d-a3e9fdb788a3")
+ else if (value == "163bcc30-e2e9-4f0b-961d-a3e9fdb788a3")
{
return 3;
}
// Tiff encoder guid.
- else if (guid == "57a37caa-367a-4540-916b-f183c5093a4b")
+ else if (value == "57a37caa-367a-4540-916b-f183c5093a4b")
{
return 4;
}
// Gif encoder guid.
- else if (guid == "1f8a5601-7d4d-4cbd-9c82-1bc8d4eeb9a5")
+ else if (value == "1f8a5601-7d4d-4cbd-9c82-1bc8d4eeb9a5")
{
return 5;
}
@@ -335,7 +347,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
return -1;
}
- public void Size_PropertyChanged(object sender, PropertyChangedEventArgs e)
+ public void SizePropertyChanged(object sender, PropertyChangedEventArgs e)
{
ImageSize modifiedSize = (ImageSize)sender;
ObservableCollection imageSizes = Sizes;
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/KeyboardManagerViewModel.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/KeyboardManagerViewModel.cs
index 7f2df05c49..ba50ea8d05 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/KeyboardManagerViewModel.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/KeyboardManagerViewModel.cs
@@ -4,11 +4,13 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using Microsoft.PowerToys.Settings.UI.Lib.Helpers;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
using Microsoft.PowerToys.Settings.UI.Lib.Utilities;
using Microsoft.PowerToys.Settings.UI.Lib.ViewModels.Commands;
@@ -16,9 +18,11 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
public class KeyboardManagerViewModel : Observable
{
+ private GeneralSettings GeneralSettingsConfig { get; set; }
+
private readonly ISettingsUtils _settingsUtils;
- private const string PowerToyName = "Keyboard Manager";
+ private const string PowerToyName = KeyboardManagerSettings.ModuleName;
private const string RemapKeyboardActionName = "RemapKeyboard";
private const string RemapKeyboardActionValue = "Open Remap Keyboard Window";
private const string EditShortcutActionName = "EditShortcut";
@@ -32,14 +36,20 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
private ICommand _remapKeyboardCommand;
private ICommand _editShortcutCommand;
private KeyboardManagerProfile _profile;
- private GeneralSettings _generalSettings;
private Func SendConfigMSG { get; }
private Func, int> FilterRemapKeysList { get; }
- public KeyboardManagerViewModel(ISettingsUtils settingsUtils, Func ipcMSGCallBackFunc, Func, int> filterRemapKeysList)
+ public KeyboardManagerViewModel(ISettingsUtils settingsUtils, ISettingsRepository settingsRepository, Func ipcMSGCallBackFunc, Func, int> filterRemapKeysList)
{
+ if (settingsRepository == null)
+ {
+ throw new ArgumentNullException(nameof(settingsRepository));
+ }
+
+ GeneralSettingsConfig = settingsRepository.SettingsConfig;
+
// set the callback functions value to hangle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
FilterRemapKeysList = filterRemapKeysList;
@@ -59,35 +69,25 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
}
else
{
- Settings = new KeyboardManagerSettings(PowerToyName);
+ Settings = new KeyboardManagerSettings();
_settingsUtils.SaveSettings(Settings.ToJsonString(), PowerToyName);
}
-
- if (_settingsUtils.SettingsExists())
- {
- _generalSettings = _settingsUtils.GetSettings(string.Empty);
- }
- else
- {
- _generalSettings = new GeneralSettings();
- _settingsUtils.SaveSettings(_generalSettings.ToJsonString(), string.Empty);
- }
}
public bool Enabled
{
get
{
- return _generalSettings.Enabled.KeyboardManager;
+ return GeneralSettingsConfig.Enabled.KeyboardManager;
}
set
{
- if (_generalSettings.Enabled.KeyboardManager != value)
+ if (GeneralSettingsConfig.Enabled.KeyboardManager != value)
{
- _generalSettings.Enabled.KeyboardManager = value;
+ GeneralSettingsConfig.Enabled.KeyboardManager = value;
OnPropertyChanged(nameof(Enabled));
- OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(_generalSettings);
+ OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
SendConfigMSG(outgoing.ToString());
}
@@ -112,7 +112,22 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
public static List CombineShortcutLists(List globalShortcutList, List appSpecificShortcutList)
{
- return globalShortcutList.ConvertAll(x => new AppSpecificKeysDataModel { OriginalKeys = x.OriginalKeys, NewRemapKeys = x.NewRemapKeys, TargetApp = "All Apps" }).Concat(appSpecificShortcutList).ToList();
+ if (globalShortcutList == null && appSpecificShortcutList == null)
+ {
+ return new List();
+ }
+ else if (globalShortcutList == null)
+ {
+ return appSpecificShortcutList;
+ }
+ else if (appSpecificShortcutList == null)
+ {
+ return globalShortcutList.ConvertAll(x => new AppSpecificKeysDataModel { OriginalKeys = x.OriginalKeys, NewRemapKeys = x.NewRemapKeys, TargetApp = "All Apps" }).ToList();
+ }
+ else
+ {
+ return globalShortcutList.ConvertAll(x => new AppSpecificKeysDataModel { OriginalKeys = x.OriginalKeys, NewRemapKeys = x.NewRemapKeys, TargetApp = "All Apps" }).Concat(appSpecificShortcutList).ToList();
+ }
}
public List RemapShortcuts
@@ -134,28 +149,31 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
public ICommand EditShortcutCommand => _editShortcutCommand ?? (_editShortcutCommand = new RelayCommand(OnEditShortcut));
+ // Note: FxCop suggests calling ConfigureAwait() for the following methods,
+ // and calling ConfigureAwait(true) has the same behavior as not explicitly
+ // calling it (continuations are scheduled on the task-creating thread)
private async void OnRemapKeyboard()
{
- await Task.Run(() => OnRemapKeyboardBackground());
+ await Task.Run(() => OnRemapKeyboardBackground()).ConfigureAwait(true);
}
private async void OnEditShortcut()
{
- await Task.Run(() => OnEditShortcutBackground());
+ await Task.Run(() => OnEditShortcutBackground()).ConfigureAwait(true);
}
private async Task OnRemapKeyboardBackground()
{
Helper.AllowRunnerToForeground();
SendConfigMSG(Helper.GetSerializedCustomAction(PowerToyName, RemapKeyboardActionName, RemapKeyboardActionValue));
- await Task.CompletedTask;
+ await Task.CompletedTask.ConfigureAwait(true);
}
private async Task OnEditShortcutBackground()
{
Helper.AllowRunnerToForeground();
SendConfigMSG(Helper.GetSerializedCustomAction(PowerToyName, EditShortcutActionName, EditShortcutActionValue));
- await Task.CompletedTask;
+ await Task.CompletedTask.ConfigureAwait(true);
}
public void NotifyFileChanged()
@@ -177,8 +195,19 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
// update the UI element here.
try
{
- _profile = _settingsUtils.GetSettings(PowerToyName, Settings.Properties.ActiveConfiguration.Value + JsonFileType);
- FilterRemapKeysList(_profile.RemapKeys.InProcessRemapKeys);
+ string fileName = Settings.Properties.ActiveConfiguration.Value + JsonFileType;
+
+ if (_settingsUtils.SettingsExists(PowerToyName, fileName))
+ {
+ _profile = _settingsUtils.GetSettings(PowerToyName, fileName);
+ }
+ else
+ {
+ // The KBM process out of runner creates the default.json file if it does not exist.
+ success = false;
+ }
+
+ FilterRemapKeysList(_profile?.RemapKeys?.InProcessRemapKeys);
}
finally
{
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/PowerLauncherViewModel.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/PowerLauncherViewModel.cs
index 73e15ae558..2fac0e69ec 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/PowerLauncherViewModel.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/PowerLauncherViewModel.cs
@@ -3,19 +3,21 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text.Json;
using Microsoft.PowerToys.Settings.UI.Lib.Helpers;
-using Microsoft.PowerToys.Settings.UI.Lib.Utilities;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
public class PowerLauncherViewModel : Observable
{
+ private GeneralSettings GeneralSettingsConfig { get; set; }
+
private readonly ISettingsUtils _settingsUtils;
private PowerLauncherSettings settings;
- private GeneralSettings generalSettings;
public delegate void SendCallback(PowerLauncherSettings settings);
@@ -23,18 +25,32 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
private Func SendConfigMSG { get; }
- public PowerLauncherViewModel(ISettingsUtils settingsUtils, Func ipcMSGCallBackFunc, int defaultKeyCode)
+ public PowerLauncherViewModel(ISettingsUtils settingsUtils, ISettingsRepository settingsRepository, Func ipcMSGCallBackFunc, int defaultKeyCode)
{
_settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
+ // To obtain the general Settings configurations of PowerToys
+ if (settingsRepository == null)
+ {
+ throw new ArgumentNullException(nameof(settingsRepository));
+ }
+
+ GeneralSettingsConfig = settingsRepository.SettingsConfig;
+
// set the callback functions value to hangle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
callback = (PowerLauncherSettings settings) =>
{
// Propagate changes to Power Launcher through IPC
+ // Using InvariantCulture as this is an IPC message
SendConfigMSG(
- string.Format("{{ \"powertoys\": {{ \"{0}\": {1} }} }}", PowerLauncherSettings.ModuleName, JsonSerializer.Serialize(settings)));
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
+ PowerLauncherSettings.ModuleName,
+ JsonSerializer.Serialize(settings)));
};
+
if (_settingsUtils.SettingsExists(PowerLauncherSettings.ModuleName))
{
settings = _settingsUtils.GetSettings(PowerLauncherSettings.ModuleName);
@@ -47,15 +63,6 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
settings.Properties.MaximumNumberOfResults = 4;
callback(settings);
}
-
- if (_settingsUtils.SettingsExists())
- {
- generalSettings = _settingsUtils.GetSettings();
- }
- else
- {
- generalSettings = new GeneralSettings();
- }
}
public PowerLauncherViewModel(PowerLauncherSettings settings, SendCallback callback)
@@ -76,16 +83,16 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
get
{
- return generalSettings.Enabled.PowerLauncher;
+ return GeneralSettingsConfig.Enabled.PowerLauncher;
}
set
{
- if (generalSettings.Enabled.PowerLauncher != value)
+ if (GeneralSettingsConfig.Enabled.PowerLauncher != value)
{
- generalSettings.Enabled.PowerLauncher = value;
+ GeneralSettingsConfig.Enabled.PowerLauncher = value;
OnPropertyChanged(nameof(EnablePowerLauncher));
- OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(generalSettings);
+ OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
SendConfigMSG(outgoing.ToString());
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/PowerPreviewViewModel.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/PowerPreviewViewModel.cs
index 7415c8eb65..5282a0e48c 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/PowerPreviewViewModel.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/PowerPreviewViewModel.cs
@@ -5,15 +5,13 @@
using System;
using System.Runtime.CompilerServices;
using Microsoft.PowerToys.Settings.UI.Lib.Helpers;
-using Microsoft.PowerToys.Settings.UI.Lib.Utilities;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
public class PowerPreviewViewModel : Observable
{
- private readonly ISettingsUtils _settingsUtils;
-
- private const string ModuleName = "File Explorer";
+ private const string ModuleName = PowerPreviewSettings.ModuleName;
private PowerPreviewSettings Settings { get; set; }
@@ -21,22 +19,30 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
private string _settingsConfigFileFolder = string.Empty;
- public PowerPreviewViewModel(ISettingsUtils settingsUtils, Func ipcMSGCallBackFunc, string configFileSubfolder = "")
+ private GeneralSettings GeneralSettingsConfig { get; set; }
+
+ public PowerPreviewViewModel(ISettingsRepository moduleSettingsRepository, ISettingsRepository generalSettingsRepository, Func ipcMSGCallBackFunc, string configFileSubfolder = "")
{
// Update Settings file folder:
_settingsConfigFileFolder = configFileSubfolder;
- _settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
- try
+ // To obtain the general Settings configurations of PowerToys
+ if (generalSettingsRepository == null)
{
- Settings = _settingsUtils.GetSettings(GetSettingsSubPath());
+ throw new ArgumentNullException(nameof(generalSettingsRepository));
}
- catch
+
+ GeneralSettingsConfig = generalSettingsRepository.SettingsConfig;
+
+ // To obtain the PowerPreview settings if it exists.
+ // If the file does not exist, to create a new one and return the default settings configurations.
+ if (moduleSettingsRepository == null)
{
- Settings = new PowerPreviewSettings();
- _settingsUtils.SaveSettings(Settings.ToJsonString(), GetSettingsSubPath());
+ throw new ArgumentNullException(nameof(moduleSettingsRepository));
}
+ Settings = moduleSettingsRepository.SettingsConfig;
+
// set the callback functions value to hangle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
@@ -45,9 +51,9 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
_mdRenderIsEnabled = Settings.Properties.EnableMdPreview;
}
- private bool _svgRenderIsEnabled = false;
- private bool _mdRenderIsEnabled = false;
- private bool _svgThumbnailIsEnabled = false;
+ private bool _svgRenderIsEnabled;
+ private bool _mdRenderIsEnabled;
+ private bool _svgThumbnailIsEnabled;
public bool SVGRenderIsEnabled
{
@@ -108,6 +114,14 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
return _settingsConfigFileFolder + "\\" + ModuleName;
}
+ public bool IsElevated
+ {
+ get
+ {
+ return GeneralSettingsConfig.IsElevated;
+ }
+ }
+
private void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
// Notify UI of property change
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/PowerRenameViewModel.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/PowerRenameViewModel.cs
index 727dfd676b..fae8014c6a 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/PowerRenameViewModel.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/PowerRenameViewModel.cs
@@ -5,15 +5,17 @@
using System;
using System.Runtime.CompilerServices;
using Microsoft.PowerToys.Settings.UI.Lib.Helpers;
-using Microsoft.PowerToys.Settings.UI.Lib.Utilities;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
public class PowerRenameViewModel : Observable
{
+ private GeneralSettings GeneralSettingsConfig { get; set; }
+
private readonly ISettingsUtils _settingsUtils;
- private const string ModuleName = "PowerRename";
+ private const string ModuleName = PowerRenameSettings.ModuleName;
private string _settingsConfigFileFolder = string.Empty;
@@ -21,12 +23,19 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
private Func SendConfigMSG { get; }
- public PowerRenameViewModel(ISettingsUtils settingsUtils, Func ipcMSGCallBackFunc, string configFileSubfolder = "")
+ public PowerRenameViewModel(ISettingsUtils settingsUtils, ISettingsRepository settingsRepository, Func ipcMSGCallBackFunc, string configFileSubfolder = "")
{
// Update Settings file folder:
_settingsConfigFileFolder = configFileSubfolder;
_settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
+ if (settingsRepository == null)
+ {
+ throw new ArgumentNullException(nameof(settingsRepository));
+ }
+
+ GeneralSettingsConfig = settingsRepository.SettingsConfig;
+
try
{
PowerRenameLocalProperties localSettings = _settingsUtils.GetSettings(GetSettingsSubPath(), "power-rename-settings.json");
@@ -47,27 +56,15 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
_powerRenameRestoreFlagsOnLaunch = Settings.Properties.PersistState.Value;
_powerRenameMaxDispListNumValue = Settings.Properties.MaxMRUSize.Value;
_autoComplete = Settings.Properties.MRUEnabled.Value;
-
- GeneralSettings generalSettings;
- try
- {
- generalSettings = _settingsUtils.GetSettings(string.Empty);
- }
- catch
- {
- generalSettings = new GeneralSettings();
- _settingsUtils.SaveSettings(generalSettings.ToJsonString(), string.Empty);
- }
-
- _powerRenameEnabled = generalSettings.Enabled.PowerRename;
+ _powerRenameEnabled = GeneralSettingsConfig.Enabled.PowerRename;
}
- private bool _powerRenameEnabled = false;
- private bool _powerRenameEnabledOnContextMenu = false;
- private bool _powerRenameEnabledOnContextExtendedMenu = false;
- private bool _powerRenameRestoreFlagsOnLaunch = false;
- private int _powerRenameMaxDispListNumValue = 0;
- private bool _autoComplete = false;
+ private bool _powerRenameEnabled;
+ private bool _powerRenameEnabledOnContextMenu;
+ private bool _powerRenameEnabledOnContextExtendedMenu;
+ private bool _powerRenameRestoreFlagsOnLaunch;
+ private int _powerRenameMaxDispListNumValue;
+ private bool _autoComplete;
public bool IsEnabled
{
@@ -80,14 +77,14 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
if (value != _powerRenameEnabled)
{
- GeneralSettings generalSettings = _settingsUtils.GetSettings(string.Empty);
- generalSettings.Enabled.PowerRename = value;
- OutGoingGeneralSettings snd = new OutGoingGeneralSettings(generalSettings);
+ GeneralSettingsConfig.Enabled.PowerRename = value;
+ OutGoingGeneralSettings snd = new OutGoingGeneralSettings(GeneralSettingsConfig);
+
SendConfigMSG(snd.ToString());
_powerRenameEnabled = value;
- OnPropertyChanged("IsEnabled");
- RaisePropertyChanged("GlobalAndMruEnabled");
+ OnPropertyChanged(nameof(IsEnabled));
+ RaisePropertyChanged(nameof(GlobalAndMruEnabled));
}
}
}
@@ -106,7 +103,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
_autoComplete = value;
Settings.Properties.MRUEnabled.Value = value;
RaisePropertyChanged();
- RaisePropertyChanged("GlobalAndMruEnabled");
+ RaisePropertyChanged(nameof(GlobalAndMruEnabled));
}
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/ShortcutGuideViewModel.cs b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/ShortcutGuideViewModel.cs
index 6c4f5d645e..bcde5576b7 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/ShortcutGuideViewModel.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Lib/ViewModels/ShortcutGuideViewModel.cs
@@ -5,54 +5,48 @@
using System;
using System.Runtime.CompilerServices;
using Microsoft.PowerToys.Settings.UI.Lib.Helpers;
-using Microsoft.PowerToys.Settings.UI.Lib.Utilities;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
public class ShortcutGuideViewModel : Observable
{
- private readonly ISettingsUtils _settingsUtils;
+ private GeneralSettings GeneralSettingsConfig { get; set; }
private ShortcutGuideSettings Settings { get; set; }
- private const string ModuleName = "Shortcut Guide";
+ private const string ModuleName = ShortcutGuideSettings.ModuleName;
private Func SendConfigMSG { get; }
private string _settingsConfigFileFolder = string.Empty;
- public ShortcutGuideViewModel(ISettingsUtils settingsUtils, Func ipcMSGCallBackFunc, string configFileSubfolder = "")
+ public ShortcutGuideViewModel(ISettingsRepository settingsRepository, ISettingsRepository moduleSettingsRepository, Func ipcMSGCallBackFunc, string configFileSubfolder = "")
{
// Update Settings file folder:
_settingsConfigFileFolder = configFileSubfolder;
- _settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
- try
+ // To obtain the general PowerToys settings.
+ if (settingsRepository == null)
{
- Settings = _settingsUtils.GetSettings(GetSettingsSubPath());
- }
- catch
- {
- Settings = new ShortcutGuideSettings();
- _settingsUtils.SaveSettings(Settings.ToJsonString(), GetSettingsSubPath());
+ throw new ArgumentNullException(nameof(settingsRepository));
}
- GeneralSettings generalSettings;
+ GeneralSettingsConfig = settingsRepository.SettingsConfig;
- try
+ // To obtain the shortcut guide settings, if the file exists.
+ // If not, to create a file with the default settings and to return the default configurations.
+ if (moduleSettingsRepository == null)
{
- generalSettings = _settingsUtils.GetSettings(string.Empty);
- }
- catch
- {
- generalSettings = new GeneralSettings();
- _settingsUtils.SaveSettings(generalSettings.ToJsonString(), string.Empty);
+ throw new ArgumentNullException(nameof(moduleSettingsRepository));
}
+ Settings = moduleSettingsRepository.SettingsConfig;
+
// set the callback functions value to hangle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
- _isEnabled = generalSettings.Enabled.ShortcutGuide;
+ _isEnabled = GeneralSettingsConfig.Enabled.ShortcutGuide;
_pressTime = Settings.Properties.PressTime.Value;
_opacity = Settings.Properties.OverlayOpacity.Value;
@@ -74,10 +68,10 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
}
}
- private bool _isEnabled = false;
- private int _themeIndex = 0;
- private int _pressTime = 0;
- private int _opacity = 0;
+ private bool _isEnabled;
+ private int _themeIndex;
+ private int _pressTime;
+ private int _opacity;
public bool IsEnabled
{
@@ -91,11 +85,13 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
if (value != _isEnabled)
{
_isEnabled = value;
- GeneralSettings generalSettings = _settingsUtils.GetSettings(string.Empty);
- generalSettings.Enabled.ShortcutGuide = value;
- OutGoingGeneralSettings snd = new OutGoingGeneralSettings(generalSettings);
+
+ // To update the status of shortcut guide in General PowerToy settings.
+ GeneralSettingsConfig.Enabled.ShortcutGuide = value;
+ OutGoingGeneralSettings snd = new OutGoingGeneralSettings(GeneralSettingsConfig);
+
SendConfigMSG(snd.ToString());
- OnPropertyChanged("IsEnabled");
+ OnPropertyChanged(nameof(IsEnabled));
}
}
}
@@ -116,7 +112,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
// set theme to dark.
Settings.Properties.Theme.Value = "dark";
_themeIndex = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
if (value == 1)
@@ -124,7 +120,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
// set theme to light.
Settings.Properties.Theme.Value = "light";
_themeIndex = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
if (value == 2)
@@ -132,7 +128,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
// set theme to system default.
Settings.Properties.Theme.Value = "system";
_themeIndex = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -151,7 +147,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
_pressTime = value;
Settings.Properties.PressTime.Value = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -169,7 +165,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
{
_opacity = value;
Settings.Properties.OverlayOpacity.Value = value;
- RaisePropertyChanged();
+ NotifyPropertyChanged();
}
}
}
@@ -179,7 +175,7 @@ namespace Microsoft.PowerToys.Settings.UI.Lib.ViewModels
return _settingsConfigFileFolder + "\\" + ModuleName;
}
- public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
+ public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
OnPropertyChanged(propertyName);
SndShortcutGuideSettings outsettings = new SndShortcutGuideSettings(Settings);
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.Runner/MainWindow.xaml.cs b/src/core/Microsoft.PowerToys.Settings.UI.Runner/MainWindow.xaml.cs
index ff477163a5..38c02e694a 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.Runner/MainWindow.xaml.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.Runner/MainWindow.xaml.cs
@@ -30,6 +30,12 @@ namespace Microsoft.PowerToys.Settings.UI.Runner
private void WindowsXamlHost_ChildChanged(object sender, EventArgs e)
{
+ // If sender is null, it could lead to a NullReferenceException. This might occur on restarting as admin (check https://github.com/microsoft/PowerToys/issues/7393 for details)
+ if (sender == null)
+ {
+ return;
+ }
+
// Hook up x:Bind source.
WindowsXamlHost windowsXamlHost = sender as WindowsXamlHost;
ShellPage shellPage = windowsXamlHost.GetUwpInternalObject() as ShellPage;
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/BackCompatTestProperties.cs b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/BackCompatTestProperties.cs
new file mode 100644
index 0000000000..15d954881b
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/BackCompatTestProperties.cs
@@ -0,0 +1,87 @@
+using Microsoft.PowerToys.Settings.UI.Lib;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
+using Microsoft.PowerToys.Settings.UI.Lib.Utilities;
+using Microsoft.PowerToys.Settings.UI.UnitTests.Mocks;
+using Moq;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq.Expressions;
+using System.Text;
+
+namespace Microsoft.PowerToys.Settings.UI.UnitTests.BackwardsCompatibility
+{
+ public static class BackCompatTestProperties
+ {
+ public const string RootPathStubFiles = "..\\..\\..\\..\\src\\core\\Microsoft.PowerToys.Settings.UI.UnitTests\\BackwardsCompatibility\\TestFiles\\{0}\\Microsoft\\PowerToys\\{1}\\{2}";
+
+ internal class MockSettingsRepository : ISettingsRepository where T : ISettingsConfig, new()
+ {
+ T _settingsConfig;
+ readonly ISettingsUtils _settingsUtils;
+ public MockSettingsRepository( ISettingsUtils settingsUtils)
+ {
+ _settingsUtils = settingsUtils;
+ }
+ public T SettingsConfig
+ {
+ get
+ {
+ T settingsItem = new T();
+ _settingsConfig = _settingsUtils.GetSettings(settingsItem.GetModuleName());
+ return _settingsConfig;
+ }
+
+ set
+ {
+ if (value != null)
+ {
+ _settingsConfig = value;
+ }
+ }
+ }
+ }
+
+
+ public static MockGetModuleIOProvider(string version, string module, string fileName)
+ {
+
+ var stubSettingsPath = string.Format(CultureInfo.InvariantCulture, BackCompatTestProperties.RootPathStubFiles, version, module, fileName);
+ Expression> filterExpression = (string s) => s.Contains(module, StringComparison.Ordinal);
+ var mockIOProvider = IIOProviderMocks.GetMockIOReadWithStubFile(stubSettingsPath, filterExpression);
+ return mockIOProvider;
+ }
+
+ public static void VerifyModuleIOProviderWasRead(Mock provider, string module, int expectedCallCount)
+ {
+ if(provider == null)
+ {
+ throw new ArgumentNullException(nameof(provider));
+ }
+
+ Expression> filterExpression = (string s) => s.Contains(module, StringComparison.Ordinal);
+
+ IIOProviderMocks.VerifyIOReadWithStubFile(provider, filterExpression, expectedCallCount);
+ }
+
+ public static Mock GetGeneralSettingsIOProvider(string version)
+ {
+ var stubGeneralSettingsPath = string.Format(CultureInfo.InvariantCulture, BackCompatTestProperties.RootPathStubFiles, version, string.Empty, "settings.json");
+ Expression> filterExpression = (string s) => s.Contains("Microsoft\\PowerToys\\settings.json", StringComparison.Ordinal);
+ var mockGeneralIOProvider = IIOProviderMocks.GetMockIOReadWithStubFile(stubGeneralSettingsPath, filterExpression);
+ return mockGeneralIOProvider;
+ }
+
+ public static void VerifyGeneralSettingsIOProviderWasRead(Mock provider, int expectedCallCount)
+ {
+ if (provider == null)
+ {
+ throw new ArgumentNullException(nameof(provider));
+ }
+
+ Expression> filterExpression = (string s) => s.Contains("Microsoft\\PowerToys\\settings.json", StringComparison.Ordinal);
+ IIOProviderMocks.VerifyIOReadWithStubFile(provider, filterExpression, expectedCallCount);
+ }
+
+ }
+}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/ColorPicker/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/ColorPicker/settings.json
new file mode 100644
index 0000000000..5281265fff
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/ColorPicker/settings.json
@@ -0,0 +1 @@
+{"properties":{"ActivationShortcut":{"win":false,"ctrl":false,"alt":false,"shift":true,"code":222,"key":""},"changecursor":{"value":false},"copiedcolorrepresentation":1},"name":"ColorPicker","version":"1.0"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/FancyZones/app-zone-history.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/FancyZones/app-zone-history.json
new file mode 100644
index 0000000000..fb6e7d71c1
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/FancyZones/app-zone-history.json
@@ -0,0 +1 @@
+{"app-zone-history":[]}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/FancyZones/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/FancyZones/settings.json
new file mode 100644
index 0000000000..8f7a87b9de
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/FancyZones/settings.json
@@ -0,0 +1 @@
+{"version":"1.0","name":"FancyZones","properties":{"fancyzones_shiftDrag":{"value":false},"fancyzones_mouseSwitch":{"value":true},"fancyzones_overrideSnapHotkeys":{"value":true},"fancyzones_moveWindowAcrossMonitors":{"value":true},"fancyzones_moveWindowsBasedOnPosition":{"value":true},"fancyzones_displayChange_moveWindows":{"value":true},"fancyzones_zoneSetChange_moveWindows":{"value":true},"fancyzones_appLastZone_moveWindows":{"value":true},"fancyzones_openWindowOnActiveMonitor":{"value":true},"fancyzones_restoreSize":{"value":true},"use_cursorpos_editor_startupscreen":{"value":false},"fancyzones_show_on_all_monitors":{"value":true},"fancyzones_span_zones_across_monitors":{"value":true},"fancyzones_makeDraggedWindowTransparent":{"value":true},"fancyzones_zoneColor":{"value":"#B53AFF"},"fancyzones_zoneBorderColor":{"value":"#FF6508"},"fancyzones_zoneHighlightColor":{"value":"#9BD7D6"},"fancyzones_highlight_opacity":{"value":26},"fancyzones_editor_hotkey":{"value":{"win":false,"ctrl":false,"alt":false,"shift":true,"code":80,"key":""}},"fancyzones_excluded_apps":{"value":""}}}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/FancyZones/zones-settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/FancyZones/zones-settings.json
new file mode 100644
index 0000000000..5845d2e689
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/FancyZones/zones-settings.json
@@ -0,0 +1 @@
+{"devices":[],"custom-zone-sets":[]}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/File Explorer/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/File Explorer/settings.json
new file mode 100644
index 0000000000..eec19159ea
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/File Explorer/settings.json
@@ -0,0 +1 @@
+{"Properties":{"svg-previewer-toggle-setting":{"value":false},"svg-thumbnail-toggle-setting":{"value":false},"md-previewer-toggle-setting":{"value":false}},"name":"File Explorer","version":"1.0"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/ImageResizer/image-resizer-settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/ImageResizer/image-resizer-settings.json
new file mode 100644
index 0000000000..bb8ba61ca5
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/ImageResizer/image-resizer-settings.json
@@ -0,0 +1 @@
+{"Enabled":false}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/ImageResizer/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/ImageResizer/settings.json
new file mode 100644
index 0000000000..5c699cfc7c
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/ImageResizer/settings.json
@@ -0,0 +1,92 @@
+{
+ "properties": {
+ "imageresizer_selectedSizeIndex": {
+ "value": 0
+ },
+ "imageresizer_shrinkOnly": {
+ "value": false
+ },
+ "imageresizer_replace": {
+ "value": false
+ },
+ "imageresizer_ignoreOrientation": {
+ "value": true
+ },
+ "imageresizer_jpegQualityLevel": {
+ "value": 92
+ },
+ "imageresizer_pngInterlaceOption": {
+ "value": 1
+ },
+ "imageresizer_tiffCompressOption": {
+ "value": 4
+ },
+ "imageresizer_fileName": {
+ "value": "%1 (%2)"
+ },
+ "imageresizer_sizes": {
+ "value": [
+ {
+ "Id": 0,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Small",
+ "fit": 1,
+ "width": 853,
+ "height": 479,
+ "unit": 3
+ },
+ {
+ "Id": 1,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Medium",
+ "fit": 1,
+ "width": 1365,
+ "height": 767,
+ "unit": 3
+ },
+ {
+ "Id": 2,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Large",
+ "fit": 1,
+ "width": 1919,
+ "height": 1079,
+ "unit": 3
+ },
+ {
+ "Id": 3,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Phone",
+ "fit": 1,
+ "width": 319,
+ "height": 567,
+ "unit": 3
+ }
+ ]
+ },
+ "imageresizer_keepDateModified": {
+ "value": true
+ },
+ "imageresizer_fallbackEncoder": {
+ "value": "163bcc30-e2e9-4f0b-961d-a3e9fdb788a3"
+ },
+ "imageresizer_customSize": {
+ "value": {
+ "Id": 4,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "custom",
+ "fit": 1,
+ "width": 1024,
+ "height": 640,
+ "unit": 3
+ }
+ }
+ },
+ "name": "Image Resizer",
+ "version": "1"
+}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/ImageResizer/sizes.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/ImageResizer/sizes.json
new file mode 100644
index 0000000000..58206ccae1
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/ImageResizer/sizes.json
@@ -0,0 +1,44 @@
+{
+ "value": [
+ {
+ "Id": 0,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Small",
+ "fit": 1,
+ "width": 853,
+ "height": 479,
+ "unit": 3
+ },
+ {
+ "Id": 1,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Medium",
+ "fit": 1,
+ "width": 1365,
+ "height": 767,
+ "unit": 3
+ },
+ {
+ "Id": 2,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Large",
+ "fit": 1,
+ "width": 1919,
+ "height": 1079,
+ "unit": 3
+ },
+ {
+ "Id": 3,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Phone",
+ "fit": 1,
+ "width": 319,
+ "height": 567,
+ "unit": 3
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/Keyboard Manager/default.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/Keyboard Manager/default.json
new file mode 100644
index 0000000000..a398788dfd
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/Keyboard Manager/default.json
@@ -0,0 +1 @@
+{"remapKeys":{"inProcess":[{"originalKeys":"83","newRemapKeys":"65"}]},"remapShortcuts":{"global":[{"originalKeys":"160;70","newRemapKeys":"160;20"}],"appSpecific":[]}}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/Keyboard Manager/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/Keyboard Manager/settings.json
new file mode 100644
index 0000000000..19056456bf
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/Keyboard Manager/settings.json
@@ -0,0 +1 @@
+{"properties":{"activeConfiguration":{"value":"default"},"keyboardConfigurations":{"value":["default"]}},"name":"Keyboard Manager","version":"1"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/PowerRename/power-rename-settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/PowerRename/power-rename-settings.json
new file mode 100644
index 0000000000..b032467504
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/PowerRename/power-rename-settings.json
@@ -0,0 +1 @@
+{"Enabled":false,"ShowIcon":false,"ExtendedContextMenuOnly":true,"PersistState":false,"MRUEnabled":true,"MaxMRUSize":13,"SearchText":"","ReplaceText":""}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/PowerRename/power-rename-ui-flags b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/PowerRename/power-rename-ui-flags
new file mode 100644
index 0000000000..c227083464
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/PowerRename/power-rename-ui-flags
@@ -0,0 +1 @@
+0
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/PowerToys Run/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/PowerToys Run/settings.json
new file mode 100644
index 0000000000..022b584f0f
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/PowerToys Run/settings.json
@@ -0,0 +1 @@
+{"properties":{"search_result_preference":"most_recently_used","search_type_preference":"application_name","maximum_number_of_results":8,"open_powerlauncher":{"win":false,"ctrl":false,"alt":false,"shift":true,"code":186,"key":""},"open_file_location":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"copy_path_location":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"open_console":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"override_win_r_key":false,"override_win_s_key":false,"ignore_hotkeys_in_fullscreen":false,"disable_drive_detection_warning":true,"clear_input_on_launch":true},"name":"PowerToys Run","version":"1.0"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/Shortcut Guide/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/Shortcut Guide/settings.json
new file mode 100644
index 0000000000..bbd6c87542
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/Shortcut Guide/settings.json
@@ -0,0 +1 @@
+{"properties":{"overlay_opacity":{"value":36},"press_time":{"value":1050},"theme":{"value":"dark"}},"name":"Shortcut Guide","version":"1.0"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/settings.json
new file mode 100644
index 0000000000..361e1e80d3
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/settings.json
@@ -0,0 +1 @@
+{"packaged":false,"startup":false,"enabled":{"ColorPicker":false,"FancyZones":false,"File Explorer":true,"Image Resizer":false,"Keyboard Manager":false,"PowerRename":false,"PowerToys Run":false,"Shortcut Guide":false},"is_elevated":false,"run_elevated":false,"download_updates_automatically":false,"is_admin":true,"theme":"light","system_theme":"dark","powertoys_version":"v0.21.1"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/update_state.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/update_state.json
new file mode 100644
index 0000000000..9f6d7485f8
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/update_state.json
@@ -0,0 +1 @@
+{"github_update_last_checked_date":"1601059000","pending_update":false,"pending_installer_filename":""}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/ColorPicker/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/ColorPicker/settings.json
new file mode 100644
index 0000000000..0a42e76f86
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/ColorPicker/settings.json
@@ -0,0 +1 @@
+{"properties":{"ActivationShortcut":{"win":true,"ctrl":false,"alt":false,"shift":true,"code":67,"key":""},"changecursor":{"value":false},"copiedcolorrepresentation":0},"name":"ColorPicker","version":"1"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/FancyZones/app-zone-history.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/FancyZones/app-zone-history.json
new file mode 100644
index 0000000000..fb6e7d71c1
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/FancyZones/app-zone-history.json
@@ -0,0 +1 @@
+{"app-zone-history":[]}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/FancyZones/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/FancyZones/settings.json
new file mode 100644
index 0000000000..e26c5fe0e3
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/FancyZones/settings.json
@@ -0,0 +1 @@
+{"version":"1.0","name":"FancyZones","properties":{"fancyzones_shiftDrag":{"value":false},"fancyzones_mouseSwitch":{"value":true},"fancyzones_overrideSnapHotkeys":{"value":true},"fancyzones_moveWindowAcrossMonitors":{"value":true},"fancyzones_displayChange_moveWindows":{"value":true},"fancyzones_zoneSetChange_moveWindows":{"value":true},"fancyzones_virtualDesktopChange_moveWindows":{"value":true},"fancyzones_appLastZone_moveWindows":{"value":true},"use_cursorpos_editor_startupscreen":{"value":false},"fancyzones_show_on_all_monitors":{"value":true},"fancyzones_makeDraggedWindowTransparent":{"value":true},"fancyzones_zoneColor":{"value":"#F5FCFF"},"fancyzones_zoneBorderColor":{"value":"#365E5C"},"fancyzones_zoneHighlightColor":{"value":"#D77F70"},"fancyzones_highlight_opacity":{"value":93},"fancyzones_editor_hotkey":{"value":{"win":false,"ctrl":false,"alt":false,"shift":true,"key":"D","code":68}},"fancyzones_excluded_apps":{"value":""}}}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/FancyZones/zones-settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/FancyZones/zones-settings.json
new file mode 100644
index 0000000000..f2e80ad8a5
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/FancyZones/zones-settings.json
@@ -0,0 +1 @@
+{"app-zone-history":[],"devices":[{"device-id":"LEN4140#4&2cf01444&0&UID265988_1920_1200_{FAD9BC94-3335-41ED-A550-A14A1B4E2FB7}","active-zoneset":{"uuid":"{0C77400D-EBC4-40EB-A820-262706297DD0}","type":"priority-grid"},"editor-show-spacing":true,"editor-spacing":16,"editor-zone-count":3}],"custom-zone-sets":[]}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/File Explorer/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/File Explorer/settings.json
new file mode 100644
index 0000000000..f47095eb8f
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/File Explorer/settings.json
@@ -0,0 +1 @@
+{"properties":{"svg-previewer-toggle-setting":{"value":false},"md-previewer-toggle-setting":{"value":false}},"name":"File Explorer","version":"1.0"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/ImageResizer/image-resizer-settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/ImageResizer/image-resizer-settings.json
new file mode 100644
index 0000000000..bb8ba61ca5
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/ImageResizer/image-resizer-settings.json
@@ -0,0 +1 @@
+{"Enabled":false}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/ImageResizer/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/ImageResizer/settings.json
new file mode 100644
index 0000000000..f14f254ae8
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/ImageResizer/settings.json
@@ -0,0 +1,82 @@
+{
+ "version": "1",
+ "name": "Image Resizer",
+ "properties": {
+ "imageresizer_selectedSizeIndex": {
+ "value": 0
+ },
+ "imageresizer_shrinkOnly": {
+ "value": false
+ },
+ "imageresizer_replace": {
+ "value": false
+ },
+ "imageresizer_ignoreOrientation": {
+ "value": true
+ },
+ "imageresizer_jpegQualityLevel": {
+ "value": 89
+ },
+ "imageresizer_pngInterlaceOption": {
+ "value": 2
+ },
+ "imageresizer_tiffCompressOption": {
+ "value": 2
+ },
+ "imageresizer_fileName": {
+ "value": "%1 (%2)"
+ },
+ "imageresizer_sizes": {
+ "value": [
+ {
+ "Id": 0,
+ "name": "Small",
+ "fit": 1,
+ "width": 1,
+ "height": 1,
+ "unit": 3
+ },
+ {
+ "Id": 1,
+ "name": "Medium",
+ "fit": 1,
+ "width": 1,
+ "height": 1,
+ "unit": 3
+ },
+ {
+ "Id": 2,
+ "name": "Large",
+ "fit": 1,
+ "width": 1,
+ "height": 1,
+ "unit": 3
+ },
+ {
+ "Id": 3,
+ "name": "Phone",
+ "fit": 1,
+ "width": 1,
+ "height": 1,
+ "unit": 3
+ }
+ ]
+ },
+ "imageresizer_keepDateModified": {
+ "value": true
+ },
+ "imageresizer_fallbackEncoder": {
+ "value": "163bcc30-e2e9-4f0b-961d-a3e9fdb788a3"
+ },
+ "imageresizer_customSize": {
+ "value": {
+ "Id": 4,
+ "name": "custom",
+ "fit": 1,
+ "width": 1024,
+ "height": 640,
+ "unit": 3
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/ImageResizer/sizes.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/ImageResizer/sizes.json
new file mode 100644
index 0000000000..faf2ed7fbd
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/ImageResizer/sizes.json
@@ -0,0 +1,36 @@
+{
+ "value": [
+ {
+ "Id": 0,
+ "name": "Small",
+ "fit": 1,
+ "width": 1,
+ "height": 1,
+ "unit": 3
+ },
+ {
+ "Id": 1,
+ "name": "Medium",
+ "fit": 1,
+ "width": 1,
+ "height": 1,
+ "unit": 3
+ },
+ {
+ "Id": 2,
+ "name": "Large",
+ "fit": 1,
+ "width": 1,
+ "height": 1,
+ "unit": 3
+ },
+ {
+ "Id": 3,
+ "name": "Phone",
+ "fit": 1,
+ "width": 1,
+ "height": 1,
+ "unit": 3
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/Keyboard Manager/default.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/Keyboard Manager/default.json
new file mode 100644
index 0000000000..f053aeee4c
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/Keyboard Manager/default.json
@@ -0,0 +1 @@
+{"remapKeys":{"inProcess":[{"originalKeys":"65","newRemapKeys":"105"}]},"remapShortcuts":{"global":[{"originalKeys":"160;70","newRemapKeys":"160;82"}]}}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/Keyboard Manager/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/Keyboard Manager/settings.json
new file mode 100644
index 0000000000..19056456bf
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/Keyboard Manager/settings.json
@@ -0,0 +1 @@
+{"properties":{"activeConfiguration":{"value":"default"},"keyboardConfigurations":{"value":["default"]}},"name":"Keyboard Manager","version":"1"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/PowerRename/power-rename-settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/PowerRename/power-rename-settings.json
new file mode 100644
index 0000000000..4512288d09
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/PowerRename/power-rename-settings.json
@@ -0,0 +1 @@
+{"Enabled":false,"ShowIcon":false,"ExtendedContextMenuOnly":true,"PersistState":false,"MRUEnabled":true,"MaxMRUSize":8,"SearchText":"","ReplaceText":""}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/PowerRename/power-rename-ui-flags b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/PowerRename/power-rename-ui-flags
new file mode 100644
index 0000000000..c227083464
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/PowerRename/power-rename-ui-flags
@@ -0,0 +1 @@
+0
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/PowerToys Run/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/PowerToys Run/settings.json
new file mode 100644
index 0000000000..54a860a6d0
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/PowerToys Run/settings.json
@@ -0,0 +1 @@
+{"properties":{"search_result_preference":"most_recently_used","search_type_preference":"application_name","maximum_number_of_results":10,"open_powerlauncher":{"win":false,"ctrl":false,"alt":false,"shift":true,"key":"P","code":80},"open_file_location":{"win":false,"ctrl":false,"alt":false,"shift":false,"key":"","code":0},"copy_path_location":{"win":false,"ctrl":false,"alt":false,"shift":false,"key":"","code":0},"open_console":{"win":false,"ctrl":false,"alt":false,"shift":false,"key":"","code":0},"override_win_r_key":false,"override_win_s_key":false},"name":"PowerToys Run","version":"1.0"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/Shortcut Guide/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/Shortcut Guide/settings.json
new file mode 100644
index 0000000000..050b5d71d6
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/Shortcut Guide/settings.json
@@ -0,0 +1 @@
+{"name":"Shortcut Guide","properties":{"overlay_opacity":{"value":51},"press_time":{"value":100},"theme":{"value":"dark"}},"version":"1.0"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/settings.json
new file mode 100644
index 0000000000..e0cd68e182
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/settings.json
@@ -0,0 +1 @@
+{"packaged":false,"startup":false,"enabled":{"FancyZones":false,"File Explorer":true,"Image Resizer":false,"Keyboard Manager":false,"PowerRename":false,"PowerToys Run":false,"Shortcut Guide":false},"is_elevated":false,"run_elevated":false,"download_updates_automatically":false,"is_admin":true,"theme":"dark","system_theme":"dark","powertoys_version":"v0.18.2"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/update_state.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/update_state.json
new file mode 100644
index 0000000000..79c1d88a22
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/update_state.json
@@ -0,0 +1 @@
+{"github_update_last_checked_date":"1601047811","pending_update":false}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/FancyZones/app-zone-history.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/FancyZones/app-zone-history.json
new file mode 100644
index 0000000000..fb6e7d71c1
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/FancyZones/app-zone-history.json
@@ -0,0 +1 @@
+{"app-zone-history":[]}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/FancyZones/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/FancyZones/settings.json
new file mode 100644
index 0000000000..807e2fe467
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/FancyZones/settings.json
@@ -0,0 +1 @@
+{"version":"1.0","name":"FancyZones","properties":{"fancyzones_shiftDrag":{"value":true},"fancyzones_mouseSwitch":{"value":false},"fancyzones_overrideSnapHotkeys":{"value":false},"fancyzones_moveWindowAcrossMonitors":{"value":false},"fancyzones_displayChange_moveWindows":{"value":false},"fancyzones_zoneSetChange_moveWindows":{"value":false},"fancyzones_appLastZone_moveWindows":{"value":false},"use_cursorpos_editor_startupscreen":{"value":true},"fancyzones_show_on_all_monitors":{"value":false},"fancyzones_makeDraggedWindowTransparent":{"value":false},"fancyzones_zoneColor":{"value":"#F5FCFF"},"fancyzones_zoneBorderColor":{"value":"#FFFFFF"},"fancyzones_zoneHighlightColor":{"value":"#0078D7"},"fancyzones_highlight_opacity":{"value":50},"fancyzones_editor_hotkey":{"value":{"win":true,"ctrl":false,"alt":false,"shift":false,"key":"`","code":192}},"fancyzones_excluded_apps":{"value":""}}}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/FancyZones/zones-settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/FancyZones/zones-settings.json
new file mode 100644
index 0000000000..5845d2e689
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/FancyZones/zones-settings.json
@@ -0,0 +1 @@
+{"devices":[],"custom-zone-sets":[]}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/File Explorer/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/File Explorer/settings.json
new file mode 100644
index 0000000000..f47095eb8f
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/File Explorer/settings.json
@@ -0,0 +1 @@
+{"properties":{"svg-previewer-toggle-setting":{"value":false},"md-previewer-toggle-setting":{"value":false}},"name":"File Explorer","version":"1.0"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/ImageResizer/image-resizer-settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/ImageResizer/image-resizer-settings.json
new file mode 100644
index 0000000000..0c24d47891
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/ImageResizer/image-resizer-settings.json
@@ -0,0 +1 @@
+{"Enabled":true}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/ImageResizer/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/ImageResizer/settings.json
new file mode 100644
index 0000000000..93866782f6
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/ImageResizer/settings.json
@@ -0,0 +1,92 @@
+{
+ "version": "1",
+ "name": "Image Resizer",
+ "properties": {
+ "imageresizer_selectedSizeIndex": {
+ "value": 0
+ },
+ "imageresizer_shrinkOnly": {
+ "value": false
+ },
+ "imageresizer_replace": {
+ "value": false
+ },
+ "imageresizer_ignoreOrientation": {
+ "value": true
+ },
+ "imageresizer_jpegQualityLevel": {
+ "value": 89
+ },
+ "imageresizer_pngInterlaceOption": {
+ "value": 1
+ },
+ "imageresizer_tiffCompressOption": {
+ "value": 2
+ },
+ "imageresizer_fileName": {
+ "value": "%1 (%2)"
+ },
+ "imageresizer_sizes": {
+ "value": [
+ {
+ "Id": 0,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Small",
+ "fit": 1,
+ "width": 850,
+ "height": 480,
+ "unit": 3
+ },
+ {
+ "Id": 1,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Medium",
+ "fit": 1,
+ "width": 1363,
+ "height": 768,
+ "unit": 3
+ },
+ {
+ "Id": 2,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Large",
+ "fit": 1,
+ "width": 1917,
+ "height": 1080,
+ "unit": 3
+ },
+ {
+ "Id": 3,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Phone",
+ "fit": 1,
+ "width": 316,
+ "height": 568,
+ "unit": 3
+ }
+ ]
+ },
+ "imageresizer_keepDateModified": {
+ "value": true
+ },
+ "imageresizer_fallbackEncoder": {
+ "value": "57a37caa-367a-4540-916b-f183c5093a4b"
+ },
+ "imageresizer_customSize": {
+ "value": {
+ "Id": 4,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "custom",
+ "fit": 1,
+ "width": 1024,
+ "height": 640,
+ "unit": 3
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/ImageResizer/sizes.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/ImageResizer/sizes.json
new file mode 100644
index 0000000000..ff43902861
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/ImageResizer/sizes.json
@@ -0,0 +1,44 @@
+{
+ "value": [
+ {
+ "Id": 0,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Small",
+ "fit": 1,
+ "width": 850,
+ "height": 480,
+ "unit": 3
+ },
+ {
+ "Id": 1,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Medium",
+ "fit": 1,
+ "width": 1363,
+ "height": 768,
+ "unit": 3
+ },
+ {
+ "Id": 2,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Large",
+ "fit": 1,
+ "width": 1917,
+ "height": 1080,
+ "unit": 3
+ },
+ {
+ "Id": 3,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Phone",
+ "fit": 1,
+ "width": 316,
+ "height": 568,
+ "unit": 3
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/Keyboard Manager/default.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/Keyboard Manager/default.json
new file mode 100644
index 0000000000..d8aaf5c98e
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/Keyboard Manager/default.json
@@ -0,0 +1 @@
+{"remapKeys":{"inProcess":[{"originalKeys":"68","newRemapKeys":"65"}]},"remapShortcuts":{"global":[{"originalKeys":"160;20","newRemapKeys":"160;9"}]}}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/Keyboard Manager/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/Keyboard Manager/settings.json
new file mode 100644
index 0000000000..19056456bf
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/Keyboard Manager/settings.json
@@ -0,0 +1 @@
+{"properties":{"activeConfiguration":{"value":"default"},"keyboardConfigurations":{"value":["default"]}},"name":"Keyboard Manager","version":"1"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/PowerRename/power-rename-settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/PowerRename/power-rename-settings.json
new file mode 100644
index 0000000000..238899d712
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/PowerRename/power-rename-settings.json
@@ -0,0 +1 @@
+{"Enabled":false,"ShowIcon":false,"ExtendedContextMenuOnly":true,"PersistState":false,"MRUEnabled":false,"MaxMRUSize":10,"SearchText":"","ReplaceText":""}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/PowerRename/power-rename-ui-flags b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/PowerRename/power-rename-ui-flags
new file mode 100644
index 0000000000..c227083464
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/PowerRename/power-rename-ui-flags
@@ -0,0 +1 @@
+0
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/PowerToys Run/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/PowerToys Run/settings.json
new file mode 100644
index 0000000000..fb8d39d043
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/PowerToys Run/settings.json
@@ -0,0 +1 @@
+{"properties":{"search_result_preference":"most_recently_used","search_type_preference":"application_name","maximum_number_of_results":8,"open_powerlauncher":{"win":false,"ctrl":false,"alt":false,"shift":true,"key":"P","code":80},"open_file_location":{"win":false,"ctrl":false,"alt":false,"shift":false,"key":"","code":0},"copy_path_location":{"win":false,"ctrl":false,"alt":false,"shift":false,"key":"","code":0},"open_console":{"win":false,"ctrl":false,"alt":false,"shift":false,"key":"","code":0},"override_win_r_key":false,"override_win_s_key":false,"ignore_hotkeys_in_fullscreen":true},"name":"PowerToys Run","version":"1.0"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/Shortcut Guide/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/Shortcut Guide/settings.json
new file mode 100644
index 0000000000..31a80ba53d
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/Shortcut Guide/settings.json
@@ -0,0 +1 @@
+{"name":"Shortcut Guide","properties":{"overlay_opacity":{"value":32},"press_time":{"value":1150},"theme":{"value":"system"}},"version":"1.0"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/settings.json
new file mode 100644
index 0000000000..b67493d5fa
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/settings.json
@@ -0,0 +1 @@
+{"packaged":false,"startup":true,"enabled":{"FancyZones":true,"File Explorer":true,"Image Resizer":true,"Keyboard Manager":false,"PowerRename":false,"PowerToys Run":true,"Shortcut Guide":false},"is_elevated":false,"run_elevated":false,"download_updates_automatically":false,"is_admin":true,"theme":"light","system_theme":"dark","powertoys_version":"v0.19.2"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/update_state.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/update_state.json
new file mode 100644
index 0000000000..fd7b24bb2f
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/update_state.json
@@ -0,0 +1 @@
+{"github_update_last_checked_date":"1601050757","pending_update":false,"pending_installer_filename":""}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/ColorPicker/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/ColorPicker/settings.json
new file mode 100644
index 0000000000..d2f68d583d
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/ColorPicker/settings.json
@@ -0,0 +1 @@
+{"properties":{"ActivationShortcut":{"win":false,"ctrl":false,"alt":false,"shift":true,"code":80,"key":""},"changecursor":{"value":false},"copiedcolorrepresentation":1},"name":"ColorPicker","version":"1.0"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/FancyZones/app-zone-history.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/FancyZones/app-zone-history.json
new file mode 100644
index 0000000000..fb6e7d71c1
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/FancyZones/app-zone-history.json
@@ -0,0 +1 @@
+{"app-zone-history":[]}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/FancyZones/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/FancyZones/settings.json
new file mode 100644
index 0000000000..fdb33ff391
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/FancyZones/settings.json
@@ -0,0 +1 @@
+{"version":"1.0","name":"FancyZones","properties":{"fancyzones_shiftDrag":{"value":false},"fancyzones_mouseSwitch":{"value":true},"fancyzones_overrideSnapHotkeys":{"value":true},"fancyzones_moveWindowAcrossMonitors":{"value":true},"fancyzones_displayChange_moveWindows":{"value":true},"fancyzones_zoneSetChange_moveWindows":{"value":true},"fancyzones_appLastZone_moveWindows":{"value":true},"fancyzones_openWindowOnActiveMonitor":{"value":true},"fancyzones_restoreSize":{"value":true},"use_cursorpos_editor_startupscreen":{"value":false},"fancyzones_show_on_all_monitors":{"value":true},"fancyzones_makeDraggedWindowTransparent":{"value":true},"fancyzones_zoneColor":{"value":"#18FCFF"},"fancyzones_zoneBorderColor":{"value":"#C0FF43"},"fancyzones_zoneHighlightColor":{"value":"#0016D7"},"fancyzones_highlight_opacity":{"value":25},"fancyzones_editor_hotkey":{"value":{"win":false,"ctrl":false,"alt":false,"shift":true,"code":87,"key":""}},"fancyzones_excluded_apps":{"value":""}}}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/FancyZones/zones-settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/FancyZones/zones-settings.json
new file mode 100644
index 0000000000..5845d2e689
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/FancyZones/zones-settings.json
@@ -0,0 +1 @@
+{"devices":[],"custom-zone-sets":[]}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/File Explorer/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/File Explorer/settings.json
new file mode 100644
index 0000000000..eec19159ea
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/File Explorer/settings.json
@@ -0,0 +1 @@
+{"Properties":{"svg-previewer-toggle-setting":{"value":false},"svg-thumbnail-toggle-setting":{"value":false},"md-previewer-toggle-setting":{"value":false}},"name":"File Explorer","version":"1.0"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/ImageResizer/image-resizer-settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/ImageResizer/image-resizer-settings.json
new file mode 100644
index 0000000000..0c24d47891
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/ImageResizer/image-resizer-settings.json
@@ -0,0 +1 @@
+{"Enabled":true}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/ImageResizer/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/ImageResizer/settings.json
new file mode 100644
index 0000000000..e2e1a598ef
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/ImageResizer/settings.json
@@ -0,0 +1,92 @@
+{
+ "properties": {
+ "imageresizer_selectedSizeIndex": {
+ "value": 0
+ },
+ "imageresizer_shrinkOnly": {
+ "value": false
+ },
+ "imageresizer_replace": {
+ "value": false
+ },
+ "imageresizer_ignoreOrientation": {
+ "value": true
+ },
+ "imageresizer_jpegQualityLevel": {
+ "value": 90
+ },
+ "imageresizer_pngInterlaceOption": {
+ "value": 0
+ },
+ "imageresizer_tiffCompressOption": {
+ "value": 0
+ },
+ "imageresizer_fileName": {
+ "value": "%1 (%2)"
+ },
+ "imageresizer_sizes": {
+ "value": [
+ {
+ "Id": 0,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Small",
+ "fit": 1,
+ "width": 854,
+ "height": 480,
+ "unit": 3
+ },
+ {
+ "Id": 1,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Medium",
+ "fit": 1,
+ "width": 1366,
+ "height": 768,
+ "unit": 3
+ },
+ {
+ "Id": 2,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Large",
+ "fit": 1,
+ "width": 1920,
+ "height": 1080,
+ "unit": 3
+ },
+ {
+ "Id": 3,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Phone",
+ "fit": 1,
+ "width": 320,
+ "height": 568,
+ "unit": 3
+ }
+ ]
+ },
+ "imageresizer_keepDateModified": {
+ "value": false
+ },
+ "imageresizer_fallbackEncoder": {
+ "value": "19e4a5aa-5662-4fc5-a0c0-1758028e1057"
+ },
+ "imageresizer_customSize": {
+ "value": {
+ "Id": 4,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "custom",
+ "fit": 1,
+ "width": 1024,
+ "height": 640,
+ "unit": 3
+ }
+ }
+ },
+ "name": "Image Resizer",
+ "version": "1"
+}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/ImageResizer/sizes.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/ImageResizer/sizes.json
new file mode 100644
index 0000000000..ca57d156cc
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/ImageResizer/sizes.json
@@ -0,0 +1,44 @@
+{
+ "value": [
+ {
+ "Id": 0,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Small",
+ "fit": 1,
+ "width": 854,
+ "height": 480,
+ "unit": 3
+ },
+ {
+ "Id": 1,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Medium",
+ "fit": 1,
+ "width": 1366,
+ "height": 768,
+ "unit": 3
+ },
+ {
+ "Id": 2,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Large",
+ "fit": 1,
+ "width": 1920,
+ "height": 1080,
+ "unit": 3
+ },
+ {
+ "Id": 3,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Phone",
+ "fit": 1,
+ "width": 320,
+ "height": 568,
+ "unit": 3
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/Keyboard Manager/default.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/Keyboard Manager/default.json
new file mode 100644
index 0000000000..ae6ad1c806
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/Keyboard Manager/default.json
@@ -0,0 +1 @@
+{"remapKeys":{"inProcess":[{"originalKeys":"97","newRemapKeys":"65"},{"originalKeys":"83","newRemapKeys":"96"}]},"remapShortcuts":{"global":[{"originalKeys":"162;164;79","newRemapKeys":"164;160;72"}],"appSpecific":[]}}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/Keyboard Manager/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/Keyboard Manager/settings.json
new file mode 100644
index 0000000000..19056456bf
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/Keyboard Manager/settings.json
@@ -0,0 +1 @@
+{"properties":{"activeConfiguration":{"value":"default"},"keyboardConfigurations":{"value":["default"]}},"name":"Keyboard Manager","version":"1"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/PowerRename/power-rename-settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/PowerRename/power-rename-settings.json
new file mode 100644
index 0000000000..4f8b4be978
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/PowerRename/power-rename-settings.json
@@ -0,0 +1 @@
+{"Enabled":false,"ShowIcon":false,"ExtendedContextMenuOnly":true,"PersistState":false,"MRUEnabled":false,"MaxMRUSize":6,"SearchText":"","ReplaceText":""}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/PowerRename/power-rename-ui-flags b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/PowerRename/power-rename-ui-flags
new file mode 100644
index 0000000000..c227083464
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/PowerRename/power-rename-ui-flags
@@ -0,0 +1 @@
+0
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/PowerToys Run/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/PowerToys Run/settings.json
new file mode 100644
index 0000000000..8d7203e825
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/PowerToys Run/settings.json
@@ -0,0 +1 @@
+{"properties":{"search_result_preference":"most_recently_used","search_type_preference":"application_name","maximum_number_of_results":8,"open_powerlauncher":{"win":true,"ctrl":false,"alt":false,"shift":false,"code":80,"key":""},"open_file_location":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"copy_path_location":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"open_console":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"override_win_r_key":false,"override_win_s_key":false,"ignore_hotkeys_in_fullscreen":true,"disable_drive_detection_warning":true,"clear_input_on_launch":true},"name":"PowerToys Run","version":"1.0"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/Shortcut Guide/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/Shortcut Guide/settings.json
new file mode 100644
index 0000000000..8a0d5e12c2
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/Shortcut Guide/settings.json
@@ -0,0 +1 @@
+{"properties":{"overlay_opacity":{"value":21},"press_time":{"value":650},"theme":{"value":"dark"}},"name":"Shortcut Guide","version":"1.0"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/settings.json
new file mode 100644
index 0000000000..4bcbb8f854
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/settings.json
@@ -0,0 +1 @@
+{"packaged":false,"startup":false,"enabled":{"ColorPicker":true,"FancyZones":false,"File Explorer":true,"Image Resizer":true,"Keyboard Manager":false,"PowerRename":false,"PowerToys Run":false,"Shortcut Guide":false},"is_elevated":false,"run_elevated":false,"download_updates_automatically":false,"is_admin":true,"theme":"light","system_theme":"dark","powertoys_version":"v0.20.1"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/update_state.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/update_state.json
new file mode 100644
index 0000000000..b7526e5e37
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/update_state.json
@@ -0,0 +1 @@
+{"github_update_last_checked_date":"1601057084","pending_update":false,"pending_installer_filename":""}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/ColorPicker/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/ColorPicker/settings.json
new file mode 100644
index 0000000000..98dd53de90
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/ColorPicker/settings.json
@@ -0,0 +1 @@
+{"properties":{"ActivationShortcut":{"win":false,"ctrl":false,"alt":false,"shift":true,"code":83,"key":""},"changecursor":{"value":false},"copiedcolorrepresentation":1},"name":"ColorPicker","version":"1.0"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/FancyZones/app-zone-history.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/FancyZones/app-zone-history.json
new file mode 100644
index 0000000000..fb6e7d71c1
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/FancyZones/app-zone-history.json
@@ -0,0 +1 @@
+{"app-zone-history":[]}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/FancyZones/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/FancyZones/settings.json
new file mode 100644
index 0000000000..a48300be19
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/FancyZones/settings.json
@@ -0,0 +1 @@
+{"version":"1.0","name":"FancyZones","properties":{"fancyzones_shiftDrag":{"value":false},"fancyzones_mouseSwitch":{"value":true},"fancyzones_overrideSnapHotkeys":{"value":true},"fancyzones_moveWindowAcrossMonitors":{"value":true},"fancyzones_moveWindowsBasedOnPosition":{"value":true},"fancyzones_displayChange_moveWindows":{"value":true},"fancyzones_zoneSetChange_moveWindows":{"value":true},"fancyzones_appLastZone_moveWindows":{"value":true},"fancyzones_openWindowOnActiveMonitor":{"value":true},"fancyzones_restoreSize":{"value":true},"use_cursorpos_editor_startupscreen":{"value":true},"fancyzones_show_on_all_monitors":{"value":true},"fancyzones_span_zones_across_monitors":{"value":true},"fancyzones_makeDraggedWindowTransparent":{"value":true},"fancyzones_zoneColor":{"value":"#FF6B1A"},"fancyzones_zoneBorderColor":{"value":"#C4B9FF"},"fancyzones_zoneHighlightColor":{"value":"#D7115F"},"fancyzones_highlight_opacity":{"value":15},"fancyzones_editor_hotkey":{"value":{"win":false,"ctrl":false,"alt":false,"shift":true,"code":83,"key":""}},"fancyzones_excluded_apps":{"value":""}}}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/FancyZones/zones-settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/FancyZones/zones-settings.json
new file mode 100644
index 0000000000..5845d2e689
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/FancyZones/zones-settings.json
@@ -0,0 +1 @@
+{"devices":[],"custom-zone-sets":[]}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/File Explorer/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/File Explorer/settings.json
new file mode 100644
index 0000000000..eec19159ea
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/File Explorer/settings.json
@@ -0,0 +1 @@
+{"Properties":{"svg-previewer-toggle-setting":{"value":false},"svg-thumbnail-toggle-setting":{"value":false},"md-previewer-toggle-setting":{"value":false}},"name":"File Explorer","version":"1.0"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/ImageResizer/image-resizer-settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/ImageResizer/image-resizer-settings.json
new file mode 100644
index 0000000000..bb8ba61ca5
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/ImageResizer/image-resizer-settings.json
@@ -0,0 +1 @@
+{"Enabled":false}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/ImageResizer/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/ImageResizer/settings.json
new file mode 100644
index 0000000000..e22a529bf1
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/ImageResizer/settings.json
@@ -0,0 +1,92 @@
+{
+ "properties": {
+ "imageresizer_selectedSizeIndex": {
+ "value": 0
+ },
+ "imageresizer_shrinkOnly": {
+ "value": false
+ },
+ "imageresizer_replace": {
+ "value": false
+ },
+ "imageresizer_ignoreOrientation": {
+ "value": true
+ },
+ "imageresizer_jpegQualityLevel": {
+ "value": 89
+ },
+ "imageresizer_pngInterlaceOption": {
+ "value": 1
+ },
+ "imageresizer_tiffCompressOption": {
+ "value": 1
+ },
+ "imageresizer_fileName": {
+ "value": "%1 (%2)"
+ },
+ "imageresizer_sizes": {
+ "value": [
+ {
+ "Id": 0,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Small",
+ "fit": 1,
+ "width": 853,
+ "height": 479,
+ "unit": 3
+ },
+ {
+ "Id": 1,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Medium",
+ "fit": 1,
+ "width": 1365,
+ "height": 767,
+ "unit": 3
+ },
+ {
+ "Id": 2,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Large",
+ "fit": 1,
+ "width": 1919,
+ "height": 1079,
+ "unit": 3
+ },
+ {
+ "Id": 3,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Phone",
+ "fit": 1,
+ "width": 319,
+ "height": 567,
+ "unit": 3
+ }
+ ]
+ },
+ "imageresizer_keepDateModified": {
+ "value": true
+ },
+ "imageresizer_fallbackEncoder": {
+ "value": "163bcc30-e2e9-4f0b-961d-a3e9fdb788a3"
+ },
+ "imageresizer_customSize": {
+ "value": {
+ "Id": 4,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "custom",
+ "fit": 1,
+ "width": 1024,
+ "height": 640,
+ "unit": 3
+ }
+ }
+ },
+ "name": "Image Resizer",
+ "version": "1"
+}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/ImageResizer/sizes.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/ImageResizer/sizes.json
new file mode 100644
index 0000000000..58206ccae1
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/ImageResizer/sizes.json
@@ -0,0 +1,44 @@
+{
+ "value": [
+ {
+ "Id": 0,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Small",
+ "fit": 1,
+ "width": 853,
+ "height": 479,
+ "unit": 3
+ },
+ {
+ "Id": 1,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Medium",
+ "fit": 1,
+ "width": 1365,
+ "height": 767,
+ "unit": 3
+ },
+ {
+ "Id": 2,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Large",
+ "fit": 1,
+ "width": 1919,
+ "height": 1079,
+ "unit": 3
+ },
+ {
+ "Id": 3,
+ "ExtraBoxOpacity": 100,
+ "EnableEtraBoxes": true,
+ "name": "Phone",
+ "fit": 1,
+ "width": 319,
+ "height": 567,
+ "unit": 3
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/Keyboard Manager/default.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/Keyboard Manager/default.json
new file mode 100644
index 0000000000..2dc8725416
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/Keyboard Manager/default.json
@@ -0,0 +1 @@
+{"remapKeys":{"inProcess":[{"originalKeys":"65","newRemapKeys":"69"}]},"remapShortcuts":{"global":[{"originalKeys":"160;68","newRemapKeys":"9"}],"appSpecific":[]}}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/Keyboard Manager/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/Keyboard Manager/settings.json
new file mode 100644
index 0000000000..19056456bf
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/Keyboard Manager/settings.json
@@ -0,0 +1 @@
+{"properties":{"activeConfiguration":{"value":"default"},"keyboardConfigurations":{"value":["default"]}},"name":"Keyboard Manager","version":"1"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/PowerRename/power-rename-settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/PowerRename/power-rename-settings.json
new file mode 100644
index 0000000000..769c340fef
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/PowerRename/power-rename-settings.json
@@ -0,0 +1 @@
+{"Enabled":false,"ShowIcon":false,"ExtendedContextMenuOnly":true,"PersistState":false,"MRUEnabled":true,"MaxMRUSize":6,"SearchText":"","ReplaceText":""}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/PowerRename/power-rename-ui-flags b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/PowerRename/power-rename-ui-flags
new file mode 100644
index 0000000000..c227083464
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/PowerRename/power-rename-ui-flags
@@ -0,0 +1 @@
+0
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/PowerToys Run/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/PowerToys Run/settings.json
new file mode 100644
index 0000000000..3cfe7919eb
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/PowerToys Run/settings.json
@@ -0,0 +1 @@
+{"properties":{"search_result_preference":"most_recently_used","search_type_preference":"application_name","maximum_number_of_results":1,"open_powerlauncher":{"win":false,"ctrl":false,"alt":false,"shift":true,"code":68,"key":""},"open_file_location":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"copy_path_location":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"open_console":{"win":false,"ctrl":false,"alt":false,"shift":false,"code":0,"key":""},"override_win_r_key":false,"override_win_s_key":false,"ignore_hotkeys_in_fullscreen":true,"disable_drive_detection_warning":true,"clear_input_on_launch":true},"name":"PowerToys Run","version":"1.0"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/Shortcut Guide/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/Shortcut Guide/settings.json
new file mode 100644
index 0000000000..1ca0af7016
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/Shortcut Guide/settings.json
@@ -0,0 +1 @@
+{"properties":{"overlay_opacity":{"value":92},"press_time":{"value":850},"theme":{"value":"dark"}},"name":"Shortcut Guide","version":"1.0"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/Video Conference/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/Video Conference/settings.json
new file mode 100644
index 0000000000..8b750c760e
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/Video Conference/settings.json
@@ -0,0 +1 @@
+{"version":"1.0","name":"Video Conference","properties":{"mute_camera_and_microphone_hotkey":{"value":{"win":false,"ctrl":false,"alt":false,"shift":true,"code":73,"key":""}},"mute_microphone_hotkey":{"value":{"win":false,"ctrl":false,"alt":false,"shift":true,"code":68,"key":""}},"mute_camera_hotkey":{"value":{"win":false,"ctrl":false,"alt":false,"shift":true,"code":86,"key":""}},"selected_camera":{"value":"USB Video Device"},"toolbar_position":{"value":"Bottom center"},"toolbar_monitor":{"value":"All monitors"},"camera_overlay_image_path":{"value":""},"theme":{"value":"light"},"hide_toolbar_when_unmuted":{"value":false}}}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/settings.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/settings.json
new file mode 100644
index 0000000000..75ff2a920f
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/settings.json
@@ -0,0 +1 @@
+{"packaged":false,"startup":false,"enabled":{"ColorPicker":false,"FancyZones":false,"File Explorer":true,"Image Resizer":false,"Keyboard Manager":false,"PowerRename":false,"PowerToys Run":false,"Shortcut Guide":true,"Video Conference":false},"is_elevated":false,"run_elevated":false,"download_updates_automatically":false,"is_admin":true,"theme":"light","system_theme":"dark","powertoys_version":"v0.22.0"}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/update_state.json b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/update_state.json
new file mode 100644
index 0000000000..f11745b028
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/update_state.json
@@ -0,0 +1 @@
+{"github_update_last_checked_date":"1601060698","pending_update":false,"pending_installer_filename":""}
\ No newline at end of file
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/Microsoft.PowerToys.Settings.UI.UnitTests.csproj b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/Microsoft.PowerToys.Settings.UI.UnitTests.csproj
index 42214bf52e..c3cde6c756 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/Microsoft.PowerToys.Settings.UI.UnitTests.csproj
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/Microsoft.PowerToys.Settings.UI.UnitTests.csproj
@@ -24,6 +24,11 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
+ 3.3.0
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/Mocks/IIOProviderMocks.cs b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/Mocks/IIOProviderMocks.cs
index 08a6b02977..a7c6a4eaf4 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/Mocks/IIOProviderMocks.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/Mocks/IIOProviderMocks.cs
@@ -2,6 +2,8 @@
using Moq;
using System;
using System.Collections.Generic;
+using System.IO;
+using System.Linq.Expressions;
using System.Text;
namespace Microsoft.PowerToys.Settings.UI.UnitTests.Mocks
@@ -34,5 +36,35 @@ namespace Microsoft.PowerToys.Settings.UI.UnitTests.Mocks
return mockIOProvider;
}
+
+
+
+ ///
+ /// This method mocks an IO provider so that it will always return data at the savePath location.
+ /// This mock is specific to a given module, and is verifiable that the stub file was read.
+ ///
+ /// The path to the stub settings file
+ /// The substring in the path that identifies the module eg. Microsoft\\PowerToys\\ColorPicker
+ ///
+ internal static Mock GetMockIOReadWithStubFile(string savePath, Expression> filterExpression)
+ {
+ string saveContent = File.ReadAllText(savePath);
+ var mockIOProvider = new Mock();
+
+
+ mockIOProvider.Setup(x => x.ReadAllText(It.Is(filterExpression)))
+ .Returns(() => saveContent).Verifiable();
+
+
+ mockIOProvider.Setup(x => x.FileExists(It.Is(filterExpression)))
+ .Returns(true);
+
+ return mockIOProvider;
+ }
+
+ internal static void VerifyIOReadWithStubFile(Mock mockIOProvider, Expression> filterExpression, int expectedCallCount)
+ {
+ mockIOProvider.Verify(x => x.ReadAllText(It.Is(filterExpression)), Times.Exactly(expectedCallCount));
+ }
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/Mocks/ISettingsUtilsMocks.cs b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/Mocks/ISettingsUtilsMocks.cs
index e1e200ce96..b1c47f15b1 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/Mocks/ISettingsUtilsMocks.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/Mocks/ISettingsUtilsMocks.cs
@@ -1,24 +1,19 @@
using Microsoft.PowerToys.Settings.UI.Lib;
-using Microsoft.PowerToys.Settings.UI.Lib.Utilities;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
using Moq;
using System;
-using System.Collections.Generic;
-using System.Text;
namespace Microsoft.PowerToys.Settings.UI.UnitTests.Mocks
{
internal static class ISettingsUtilsMocks
{
//Stubs out empty values for imageresizersettings and general settings as needed by the imageresizer viewmodel
- internal static Mock GetStubSettingsUtils()
+ internal static Mock GetStubSettingsUtils()
+ where T : ISettingsConfig, new()
{
var settingsUtils = new Mock();
- settingsUtils.Setup(x => x.GetSettings(It.IsAny(), It.IsAny()))
- .Returns(new InvocationFunc(invocation =>
- {
- var typeArgument = invocation.Method.GetGenericArguments()[0];
- return Activator.CreateInstance(typeArgument);
- }));
+ settingsUtils.Setup(x => x.GetSettings(It.IsAny(), It.IsAny()))
+ .Returns(new T());
return settingsUtils;
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ModelsTests/BasePTModuleSettingsTest.cs b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ModelsTests/BasePTModuleSettingsTest.cs
index cc4a2d158c..0ea3fe34fa 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ModelsTests/BasePTModuleSettingsTest.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ModelsTests/BasePTModuleSettingsTest.cs
@@ -21,8 +21,8 @@ namespace CommonLibTest
// https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to.
// Test also fails when the attributes are not initialized i.e they have null values.
[TestMethod]
- [Obsolete]
- public void ToJsonString_ShouldReturnValidJSONOfModel_WhenSuccessful()
+ [ObsoleteAttribute("This test method is obsolete.", true)]
+ public void ToJsonStringShouldReturnValidJSONOfModelWhenSuccessful()
{
//Mock Disk access
string saveContent = string.Empty;
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ModelsTests/BasePTSettingsTest.cs b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ModelsTests/BasePTSettingsTest.cs
index 2bec4be724..023ee7e851 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ModelsTests/BasePTSettingsTest.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ModelsTests/BasePTSettingsTest.cs
@@ -3,15 +3,26 @@
// See the LICENSE file in the project root for more information.
using Microsoft.PowerToys.Settings.UI.Lib;
+using Microsoft.PowerToys.Settings.UI.Lib.Interface;
namespace Microsoft.PowerToys.Settings.UnitTest
{
- public class BasePTSettingsTest : BasePTModuleSettings
+ public class BasePTSettingsTest : BasePTModuleSettings, ISettingsConfig
{
public BasePTSettingsTest()
{
Name = string.Empty;
Version = string.Empty;
}
+
+ public string GetModuleName()
+ {
+ return Name;
+ }
+
+ public bool UpgradeSettingsConfiguration()
+ {
+ return false;
+ }
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ModelsTests/HelperTest.cs b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ModelsTests/HelperTest.cs
index 421d044e4a..f951bfb43b 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ModelsTests/HelperTest.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ModelsTests/HelperTest.cs
@@ -24,7 +24,7 @@ namespace CommonLibTest
}
[TestMethod]
- public void Helper_CompareVersions_ShouldBeEqual_WhenSuccessful()
+ public void HelperCompareVersionsShouldBeEqualWhenSuccessful()
{
TestStringsAreEqual("v0.0.0", "v0.0.0");
TestStringsAreEqual("v0.1.1", "v0.1.1");
@@ -33,7 +33,7 @@ namespace CommonLibTest
}
[TestMethod]
- public void Helper_CompareVersions_ShouldBeSmaller_WhenSuccessful()
+ public void HelperCompareVersionsShouldBeSmallerWhenSuccessful()
{
TestStringIsSmaller("v0.0.0", "v0.0.1");
TestStringIsSmaller("v0.0.0", "v0.1.0");
@@ -47,28 +47,28 @@ namespace CommonLibTest
[TestMethod]
[ExpectedException(typeof(FormatException))]
- public void Helper_CompareVersions_ShouldThrowBadFormat_WhenNoVersionString()
+ public void HelperCompareVersionsShouldThrowBadFormatWhenNoVersionString()
{
Helper.CompareVersions("v0.0.1", string.Empty);
}
[TestMethod]
[ExpectedException(typeof(FormatException))]
- public void Helper_CompareVersions_ShouldThrowBadFormat_WhenShortVersionString()
+ public void HelperCompareVersionsShouldThrowBadFormatWhenShortVersionString()
{
Helper.CompareVersions("v0.0.1", "v0.1");
}
[TestMethod]
[ExpectedException(typeof(FormatException))]
- public void Helper_CompareVersions_ShouldThrowBadFormat_WhenLongVersionString()
+ public void HelperCompareVersionsShouldThrowBadFormatWhenLongVersionString()
{
Helper.CompareVersions("v0.0.1", "v0.0.0.1");
}
[TestMethod]
[ExpectedException(typeof(FormatException))]
- public void Helper_CompareVersions_ShouldThrowBadFormat_WhenItIsNotAVersionString()
+ public void HelperCompareVersionsShouldThrowBadFormatWhenItIsNotAVersionString()
{
Helper.CompareVersions("v0.0.1", "HelloWorld");
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ModelsTests/SettingsRepositoryTest.cs b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ModelsTests/SettingsRepositoryTest.cs
new file mode 100644
index 0000000000..5bb2825755
--- /dev/null
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ModelsTests/SettingsRepositoryTest.cs
@@ -0,0 +1,68 @@
+// 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.IO;
+using System.Threading.Tasks;
+using Microsoft.PowerToys.Settings.UI.Lib;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Microsoft.PowerToys.Settings.UI.UnitTests.Mocks;
+
+namespace CommonLibTest
+{
+ [TestClass]
+ public class SettingsRepositoryTest
+ {
+ private static Task> GetSettingsRepository(ISettingsUtils settingsUtils)
+ {
+
+ return Task.Run(() =>
+ {
+ return SettingsRepository.GetInstance(settingsUtils);
+ });
+ }
+
+
+ [TestMethod]
+ public void SettingsRepositoryInstanceWhenCalledMustReturnSameObject()
+ {
+ // The singleton class Settings Repository must always have a single instance
+ var mockSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils();
+
+ // Arrange and Act
+ SettingsRepository firstInstance = SettingsRepository.GetInstance(mockSettingsUtils.Object);
+ SettingsRepository secondInstance = SettingsRepository.GetInstance(mockSettingsUtils.Object);
+
+ // Assert
+ Assert.IsTrue(object.ReferenceEquals(firstInstance, secondInstance));
+ }
+
+ [TestMethod]
+ public void SettingsRepositoryInstanceMustBeTheSameAcrossThreads()
+ {
+ // Multiple tasks try to access and initialize the settings repository class, however they must all access the same settings Repository object.
+
+ // Arrange
+ var mockSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils();
+ List>> settingsRepoTasks = new List>>();
+ int numberOfTasks = 100;
+
+ for(int i = 0; i < numberOfTasks; i++)
+ {
+ settingsRepoTasks.Add(GetSettingsRepository(mockSettingsUtils.Object));
+ }
+
+ // Act
+ Task.WaitAll(settingsRepoTasks.ToArray());
+
+ // Assert
+ for(int i=0; i< numberOfTasks-1; i++)
+ {
+ Assert.IsTrue(object.ReferenceEquals(settingsRepoTasks[i].Result, settingsRepoTasks[i + 1].Result));
+ }
+
+ }
+ }
+}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ModelsTests/SettingsUtilsTests.cs b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ModelsTests/SettingsUtilsTests.cs
index 36a4165407..51606154d1 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ModelsTests/SettingsUtilsTests.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ModelsTests/SettingsUtilsTests.cs
@@ -21,7 +21,7 @@ namespace CommonLibTest
[TestMethod]
- public void SaveSettings_SaveSettingsToFile_WhenFilePathExists()
+ public void SaveSettingsSaveSettingsToFileWhenFilePathExists()
{
// Arrange
var mockIOProvider = IIOProviderMocks.GetMockIOProviderForSaveLoadExists();
@@ -41,7 +41,7 @@ namespace CommonLibTest
}
[TestMethod]
- public void SaveSettings_ShouldCreateFile_WhenFilePathIsNotFound()
+ public void SaveSettingsShouldCreateFileWhenFilePathIsNotFound()
{
// Arrange
var mockIOProvider = IIOProviderMocks.GetMockIOProviderForSaveLoadExists();
@@ -59,7 +59,7 @@ namespace CommonLibTest
}
[TestMethod]
- public void SettingsFolderExists_ShouldReturnFalse_WhenFilePathIsNotFound()
+ public void SettingsFolderExistsShouldReturnFalseWhenFilePathIsNotFound()
{
// Arrange
var mockIOProvider = IIOProviderMocks.GetMockIOProviderForSaveLoadExists();
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/ColorPicker.cs b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/ColorPicker.cs
index 0d31233992..4cadefcba3 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/ColorPicker.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/ColorPicker.cs
@@ -2,19 +2,70 @@
// 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.Globalization;
using System.IO;
using System.Text.Json;
using Microsoft.PowerToys.Settings.UI.Lib;
using Microsoft.PowerToys.Settings.UI.Lib.ViewModels;
+using Microsoft.PowerToys.Settings.UI.UnitTests.BackwardsCompatibility;
using Microsoft.PowerToys.Settings.UI.UnitTests.Mocks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
-using Moq;
namespace ViewModelTests
{
[TestClass]
public class ColorPicker
{
-
+ ///
+ /// Test if the original settings files were modified.
+ ///
+ [TestMethod]
+ [DataRow("v0.20.1", "settings.json")] //Color picker was introduced in .20
+ [DataRow("v0.21.1", "settings.json")]
+ [DataRow("v0.22.0", "settings.json")]
+ public void OriginalFilesModificationTest(string version, string fileName)
+ {
+ //Arrange
+ var mockIOProvider = BackCompatTestProperties.GetModuleIOProvider(version, ColorPickerSettings.ModuleName, fileName);
+ var mockSettingsUtils = new SettingsUtils(mockIOProvider.Object);
+ ColorPickerSettings originalSettings = mockSettingsUtils.GetSettings(ColorPickerSettings.ModuleName);
+
+ var mockGeneralIOProvider = BackCompatTestProperties.GetGeneralSettingsIOProvider(version);
+ var mockGeneralSettingsUtils = new SettingsUtils(mockGeneralIOProvider.Object);
+ GeneralSettings originalGeneralSettings = mockGeneralSettingsUtils.GetSettings();
+ var generalSettingsRepository = new BackCompatTestProperties.MockSettingsRepository(mockGeneralSettingsUtils);
+
+ //Act
+ // Initialise View Model with test Config files
+ ColorPickerViewModel viewModel = new ColorPickerViewModel(mockSettingsUtils, generalSettingsRepository, ColorPickerIsEnabledByDefaultIPC);
+
+ //Assert
+ // Verifiy that the old settings persisted
+ Assert.AreEqual(originalGeneralSettings.Enabled.ColorPicker, viewModel.IsEnabled);
+ Assert.AreEqual(originalSettings.Properties.ActivationShortcut.ToString(), viewModel.ActivationShortcut.ToString());
+ Assert.AreEqual(originalSettings.Properties.ChangeCursor, viewModel.ChangeCursor);
+
+ //Verify that the stub file was used
+ var expectedCallCount = 2; //once via the view model, and once by the test (GetSettings)
+ BackCompatTestProperties.VerifyModuleIOProviderWasRead(mockIOProvider, ColorPickerSettings.ModuleName, expectedCallCount);
+ BackCompatTestProperties.VerifyGeneralSettingsIOProviderWasRead(mockGeneralIOProvider, expectedCallCount);
+ }
+
+ [TestMethod]
+ public void ColorPickerIsEnabledByDefault()
+ {
+ var mockSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils();
+ var viewModel = new ColorPickerViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SettingsRepository.GetInstance(ISettingsUtilsMocks.GetStubSettingsUtils().Object), ColorPickerIsEnabledByDefaultIPC);
+
+ Assert.IsTrue(viewModel.IsEnabled);
+ }
+
+ private static int ColorPickerIsEnabledByDefaultIPC(string msg)
+ {
+ OutGoingGeneralSettings snd = JsonSerializer.Deserialize(msg);
+ Assert.IsTrue(snd.GeneralSettings.Enabled.ColorPicker);
+ return 0;
+ }
+
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/FancyZones.cs b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/FancyZones.cs
index a4e1a4984a..923fcc7013 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/FancyZones.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/FancyZones.cs
@@ -4,11 +4,13 @@
using System;
using System.Drawing;
+using System.Globalization;
using System.IO;
using System.Text.Json;
using CommonLibTest;
using Microsoft.PowerToys.Settings.UI.Lib;
using Microsoft.PowerToys.Settings.UI.Lib.ViewModels;
+using Microsoft.PowerToys.Settings.UI.UnitTests.BackwardsCompatibility;
using Microsoft.PowerToys.Settings.UI.UnitTests.Mocks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
@@ -20,8 +22,79 @@ namespace ViewModelTests
{
public const string FancyZonesTestFolderName = "Test\\FancyZones";
+ ///
+ /// Test if the original settings files were modified.
+ ///
[TestMethod]
- public void IsEnabled_ShouldDisableModule_WhenSuccessful()
+ [DataRow("v0.18.2", "settings.json")]
+ [DataRow("v0.19.2", "settings.json")]
+ [DataRow("v0.20.1", "settings.json")]
+ [DataRow("v0.21.1", "settings.json")]
+ [DataRow("v0.22.0", "settings.json")]
+ public void OriginalFilesModificationTest(string version, string fileName)
+ {
+ var mockIOProvider = BackCompatTestProperties.GetModuleIOProvider(version, FancyZonesSettings.ModuleName, fileName);
+ var mockSettingsUtils = new SettingsUtils(mockIOProvider.Object);
+ FancyZonesSettings originalSettings = mockSettingsUtils.GetSettings(FancyZonesSettings.ModuleName);
+
+ var mockGeneralIOProvider = BackCompatTestProperties.GetGeneralSettingsIOProvider(version);
+ var mockGeneralSettingsUtils = new SettingsUtils(mockGeneralIOProvider.Object);
+ GeneralSettings originalGeneralSettings = mockGeneralSettingsUtils.GetSettings();
+ var generalSettingsRepository = new BackCompatTestProperties.MockSettingsRepository(mockGeneralSettingsUtils);
+ var fancyZonesRepository = new BackCompatTestProperties.MockSettingsRepository(mockSettingsUtils);
+
+ // Initialise View Model with test Config files
+ FancyZonesViewModel viewModel = new FancyZonesViewModel(generalSettingsRepository, fancyZonesRepository, ColorPickerIsEnabledByDefault_IPC);
+
+ // Verifiy that the old settings persisted
+ Assert.AreEqual(originalGeneralSettings.Enabled.FancyZones, viewModel.IsEnabled);
+ Assert.AreEqual(originalSettings.Properties.FancyzonesAppLastZoneMoveWindows.Value, viewModel.AppLastZoneMoveWindows);
+ Assert.AreEqual(originalSettings.Properties.FancyzonesBorderColor.Value, viewModel.ZoneBorderColor);
+ Assert.AreEqual(originalSettings.Properties.FancyzonesDisplayChangeMoveWindows.Value, viewModel.DisplayChangeMoveWindows);
+ Assert.AreEqual(originalSettings.Properties.FancyzonesEditorHotkey.Value.ToString(), viewModel.EditorHotkey.ToString());
+ Assert.AreEqual(originalSettings.Properties.FancyzonesExcludedApps.Value, viewModel.ExcludedApps);
+ Assert.AreEqual(originalSettings.Properties.FancyzonesHighlightOpacity.Value, viewModel.HighlightOpacity);
+ Assert.AreEqual(originalSettings.Properties.FancyzonesInActiveColor.Value, viewModel.ZoneInActiveColor);
+ Assert.AreEqual(originalSettings.Properties.FancyzonesMakeDraggedWindowTransparent.Value, viewModel.MakeDraggedWindowsTransparent);
+ Assert.AreEqual(originalSettings.Properties.FancyzonesMouseSwitch.Value, viewModel.MouseSwitch);
+ Assert.AreEqual(originalSettings.Properties.FancyzonesMoveWindowsAcrossMonitors.Value, viewModel.MoveWindowsAcrossMonitors);
+ Assert.AreEqual(originalSettings.Properties.FancyzonesMoveWindowsBasedOnPosition.Value, viewModel.MoveWindowsBasedOnPosition);
+ Assert.AreEqual(originalSettings.Properties.FancyzonesOpenWindowOnActiveMonitor.Value, viewModel.OpenWindowOnActiveMonitor);
+ Assert.AreEqual(originalSettings.Properties.FancyzonesOverrideSnapHotkeys.Value, viewModel.OverrideSnapHotkeys);
+ Assert.AreEqual(originalSettings.Properties.FancyzonesRestoreSize.Value, viewModel.RestoreSize);
+ Assert.AreEqual(originalSettings.Properties.FancyzonesShiftDrag.Value, viewModel.ShiftDrag);
+ Assert.AreEqual(originalSettings.Properties.FancyzonesShowOnAllMonitors.Value, viewModel.ShowOnAllMonitors);
+ Assert.AreEqual(originalSettings.Properties.FancyzonesSpanZonesAcrossMonitors.Value, viewModel.SpanZonesAcrossMonitors);
+ Assert.AreEqual(originalSettings.Properties.FancyzonesZoneHighlightColor.Value, viewModel.ZoneHighlightColor);
+ Assert.AreEqual(originalSettings.Properties.FancyzonesZoneSetChangeMoveWindows.Value, viewModel.ZoneSetChangeMoveWindows);
+ Assert.AreEqual(originalSettings.Properties.UseCursorposEditorStartupscreen.Value, viewModel.UseCursorPosEditorStartupScreen);
+
+ //Verify that the stub file was used
+ var expectedCallCount = 2; //once via the view model, and once by the test (GetSettings)
+ BackCompatTestProperties.VerifyModuleIOProviderWasRead(mockIOProvider, FancyZonesSettings.ModuleName, expectedCallCount);
+ BackCompatTestProperties.VerifyGeneralSettingsIOProviderWasRead(mockGeneralIOProvider, expectedCallCount);
+ }
+
+ private int ColorPickerIsEnabledByDefault_IPC(string msg)
+ {
+ OutGoingGeneralSettings snd = JsonSerializer.Deserialize(msg);
+ Assert.IsTrue(snd.GeneralSettings.Enabled.ColorPicker);
+ return 0;
+ }
+
+ private Mock mockGeneralSettingsUtils;
+
+ private Mock mockFancyZonesSettingsUtils;
+
+ [TestInitialize]
+ public void SetUpStubSettingUtils()
+ {
+ mockGeneralSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils();
+ mockFancyZonesSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils();
+ }
+
+ [TestMethod]
+ public void IsEnabledShouldDisableModuleWhenSuccessful()
{
Func SendMockIPCConfigMSG = msg =>
{
@@ -31,7 +104,7 @@ namespace ViewModelTests
};
// arrange
- FancyZonesViewModel viewModel = new FancyZonesViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, FancyZonesTestFolderName);
+ FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), SendMockIPCConfigMSG, FancyZonesTestFolderName);
Assert.IsTrue(viewModel.IsEnabled); // check if the module is enabled.
// act
@@ -39,7 +112,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void ShiftDrag_ShouldSetValue2False_WhenSuccessful()
+ public void ShiftDragShouldSetValue2FalseWhenSuccessful()
{
// Assert
Func SendMockIPCConfigMSG = msg =>
@@ -50,7 +123,7 @@ namespace ViewModelTests
};
// arrange
- FancyZonesViewModel viewModel = new FancyZonesViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, FancyZonesTestFolderName);
+ FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), SendMockIPCConfigMSG, FancyZonesTestFolderName);
Assert.IsTrue(viewModel.ShiftDrag); // check if value was initialized to false.
// act
@@ -58,7 +131,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void OverrideSnapHotkeys_ShouldSetValue2True_WhenSuccessful()
+ public void OverrideSnapHotkeysShouldSetValue2TrueWhenSuccessful()
{
// Assert
Func SendMockIPCConfigMSG = msg =>
@@ -69,7 +142,7 @@ namespace ViewModelTests
};
// arrange
- FancyZonesViewModel viewModel = new FancyZonesViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, FancyZonesTestFolderName);
+ FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), SendMockIPCConfigMSG, FancyZonesTestFolderName);
Assert.IsFalse(viewModel.OverrideSnapHotkeys); // check if value was initialized to false.
// act
@@ -77,7 +150,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void MoveWindowsBasedOnPosition_ShouldSetValue2True_WhenSuccessful()
+ public void MoveWindowsBasedOnPositionShouldSetValue2TrueWhenSuccessful()
{
// Assert
Func SendMockIPCConfigMSG = msg =>
@@ -88,7 +161,7 @@ namespace ViewModelTests
};
// arrange
- FancyZonesViewModel viewModel = new FancyZonesViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, FancyZonesTestFolderName);
+ FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), SendMockIPCConfigMSG, FancyZonesTestFolderName);
Assert.IsFalse(viewModel.MoveWindowsBasedOnPosition); // check if value was initialized to false.
// act
@@ -96,7 +169,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void ZoneSetChangeFlashZones_ShouldSetValue2False_WhenSuccessful()
+ public void ZoneSetChangeFlashZonesShouldSetValue2FalseWhenSuccessful()
{
// Assert
Func SendMockIPCConfigMSG = msg =>
@@ -107,7 +180,7 @@ namespace ViewModelTests
};
// arrange
- FancyZonesViewModel viewModel = new FancyZonesViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, FancyZonesTestFolderName);
+ FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), SendMockIPCConfigMSG, FancyZonesTestFolderName);
Assert.IsFalse(viewModel.MakeDraggedWindowsTransparent); // check if value was initialized to false.
// act
@@ -115,7 +188,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void MouseSwitch_ShouldSetValue2True_WhenSuccessful()
+ public void MouseSwitchShouldSetValue2TrueWhenSuccessful()
{
// Assert
Func SendMockIPCConfigMSG = msg =>
@@ -126,7 +199,7 @@ namespace ViewModelTests
};
// arrange
- FancyZonesViewModel viewModel = new FancyZonesViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, FancyZonesTestFolderName);
+ FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), SendMockIPCConfigMSG, FancyZonesTestFolderName);
Assert.IsFalse(viewModel.MouseSwitch); // check if value was initialized to false.
// act
@@ -134,7 +207,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void DisplayChangeMoveWindows_ShouldSetValue2True_WhenSuccessful()
+ public void DisplayChangeMoveWindowsShouldSetValue2TrueWhenSuccessful()
{
// Assert
Func SendMockIPCConfigMSG = msg =>
@@ -145,7 +218,7 @@ namespace ViewModelTests
};
// arrange
- FancyZonesViewModel viewModel = new FancyZonesViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, FancyZonesTestFolderName);
+ FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), SendMockIPCConfigMSG, FancyZonesTestFolderName);
Assert.IsFalse(viewModel.DisplayChangeMoveWindows); // check if value was initialized to false.
// act
@@ -153,7 +226,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void ZoneSetChangeMoveWindows_ShouldSetValue2True_WhenSuccessful()
+ public void ZoneSetChangeMoveWindowsShouldSetValue2TrueWhenSuccessful()
{
// Assert
Func SendMockIPCConfigMSG = msg =>
@@ -164,7 +237,7 @@ namespace ViewModelTests
};
// arrange
- FancyZonesViewModel viewModel = new FancyZonesViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, FancyZonesTestFolderName);
+ FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), SendMockIPCConfigMSG, FancyZonesTestFolderName);
Assert.IsFalse(viewModel.ZoneSetChangeMoveWindows); // check if value was initialized to false.
// act
@@ -172,7 +245,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void AppLastZoneMoveWindows_ShouldSetValue2True_WhenSuccessful()
+ public void AppLastZoneMoveWindowsShouldSetValue2TrueWhenSuccessful()
{
// Assert
Func SendMockIPCConfigMSG = msg =>
@@ -183,14 +256,14 @@ namespace ViewModelTests
};
// arrange
- FancyZonesViewModel viewModel = new FancyZonesViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, FancyZonesTestFolderName);
+ FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), SendMockIPCConfigMSG, FancyZonesTestFolderName);
Assert.IsFalse(viewModel.AppLastZoneMoveWindows); // check if value was initialized to false.
// act
viewModel.AppLastZoneMoveWindows = true;
}
- public void OpenWindowOnActiveMonitor_ShouldSetValue2True_WhenSuccessful()
+ public void OpenWindowOnActiveMonitorShouldSetValue2TrueWhenSuccessful()
{
// Assert
Func SendMockIPCConfigMSG = msg =>
@@ -201,7 +274,7 @@ namespace ViewModelTests
};
// arrange
- FancyZonesViewModel viewModel = new FancyZonesViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, FancyZonesTestFolderName);
+ FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), SendMockIPCConfigMSG, FancyZonesTestFolderName);
Assert.IsFalse(viewModel.OpenWindowOnActiveMonitor); // check if value was initialized to false.
// act
@@ -209,7 +282,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void RestoreSize_ShouldSetValue2True_WhenSuccessful()
+ public void RestoreSizeShouldSetValue2TrueWhenSuccessful()
{
// Assert
Func SendMockIPCConfigMSG = msg =>
@@ -220,7 +293,7 @@ namespace ViewModelTests
};
// arrange
- FancyZonesViewModel viewModel = new FancyZonesViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, FancyZonesTestFolderName);
+ FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), SendMockIPCConfigMSG, FancyZonesTestFolderName);
Assert.IsFalse(viewModel.RestoreSize); // check if value was initialized to false.
// act
@@ -228,7 +301,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void UseCursorPosEditorStartupScreen_ShouldSetValue2False_WhenSuccessful()
+ public void UseCursorPosEditorStartupScreenShouldSetValue2FalseWhenSuccessful()
{
// Assert
Func SendMockIPCConfigMSG = msg =>
@@ -239,7 +312,7 @@ namespace ViewModelTests
};
// arrange
- FancyZonesViewModel viewModel = new FancyZonesViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, FancyZonesTestFolderName);
+ FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), SendMockIPCConfigMSG, FancyZonesTestFolderName);
Assert.IsTrue(viewModel.UseCursorPosEditorStartupScreen); // check if value was initialized to false.
// act
@@ -247,7 +320,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void ShowOnAllMonitors_ShouldSetValue2True_WhenSuccessful()
+ public void ShowOnAllMonitorsShouldSetValue2TrueWhenSuccessful()
{
// Assert
Func SendMockIPCConfigMSG = msg =>
@@ -258,7 +331,7 @@ namespace ViewModelTests
};
// arrange
- FancyZonesViewModel viewModel = new FancyZonesViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, FancyZonesTestFolderName);
+ FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), SendMockIPCConfigMSG, FancyZonesTestFolderName);
Assert.IsFalse(viewModel.ShowOnAllMonitors); // check if value was initialized to false.
// act
@@ -266,7 +339,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void ZoneHighlightColor_ShouldSetColorValue2White_WhenSuccessful()
+ public void ZoneHighlightColorShouldSetColorValue2WhiteWhenSuccessful()
{
// Assert
Func SendMockIPCConfigMSG = msg =>
@@ -277,7 +350,7 @@ namespace ViewModelTests
};
// arrange
- FancyZonesViewModel viewModel = new FancyZonesViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, FancyZonesTestFolderName);
+ FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), SendMockIPCConfigMSG, FancyZonesTestFolderName);
Assert.AreEqual(ConfigDefaults.DefaultFancyZonesZoneHighlightColor, viewModel.ZoneHighlightColor);
// act
@@ -285,7 +358,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void ZoneBorderColor_ShouldSetColorValue2White_WhenSuccessful()
+ public void ZoneBorderColorShouldSetColorValue2WhiteWhenSuccessful()
{
// Assert
Func SendMockIPCConfigMSG = msg =>
@@ -296,7 +369,7 @@ namespace ViewModelTests
};
// arrange
- FancyZonesViewModel viewModel = new FancyZonesViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, FancyZonesTestFolderName);
+ FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), SendMockIPCConfigMSG, FancyZonesTestFolderName);
Assert.AreEqual(ConfigDefaults.DefaultFancyzonesBorderColor, viewModel.ZoneBorderColor);
// act
@@ -304,7 +377,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void ZoneInActiveColor_ShouldSetColorValue2White_WhenSuccessful()
+ public void ZoneInActiveColorShouldSetColorValue2WhiteWhenSuccessful()
{
// Assert
Func SendMockIPCConfigMSG = msg =>
@@ -315,7 +388,7 @@ namespace ViewModelTests
};
// arrange
- FancyZonesViewModel viewModel = new FancyZonesViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, FancyZonesTestFolderName);
+ FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), SendMockIPCConfigMSG, FancyZonesTestFolderName);
Assert.AreEqual(ConfigDefaults.DefaultFancyZonesInActiveColor, viewModel.ZoneInActiveColor);
// act
@@ -323,7 +396,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void ExcludedApps_ShouldSetColorValue2White_WhenSuccessful()
+ public void ExcludedAppsShouldSetColorValue2WhiteWhenSuccessful()
{
// Assert
Func SendMockIPCConfigMSG = msg =>
@@ -334,7 +407,7 @@ namespace ViewModelTests
};
// arrange
- FancyZonesViewModel viewModel = new FancyZonesViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, FancyZonesTestFolderName);
+ FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), SendMockIPCConfigMSG, FancyZonesTestFolderName);
Assert.AreEqual(string.Empty, viewModel.ExcludedApps);
// act
@@ -342,7 +415,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void HighlightOpacity_ShouldSetOpacityValueTo60_WhenSuccessful()
+ public void HighlightOpacityShouldSetOpacityValueTo60WhenSuccessful()
{
// Assert
Func SendMockIPCConfigMSG = msg =>
@@ -353,16 +426,11 @@ namespace ViewModelTests
};
// arrange
- FancyZonesViewModel viewModel = new FancyZonesViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, FancyZonesTestFolderName);
+ FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), SendMockIPCConfigMSG, FancyZonesTestFolderName);
Assert.AreEqual(50, viewModel.HighlightOpacity);
// act
viewModel.HighlightOpacity = 60;
}
-
- private string ToRGBHex(Color color)
- {
- return "#" + color.R.ToString("X2") + color.G.ToString("X2") + color.B.ToString("X2");
- }
}
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/General.cs b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/General.cs
index f696f24f97..85ff65bdde 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/General.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/General.cs
@@ -3,10 +3,13 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Globalization;
using System.IO;
using System.Text.Json;
using Microsoft.PowerToys.Settings.UI.Lib;
using Microsoft.PowerToys.Settings.UI.Lib.ViewModels;
+using Microsoft.PowerToys.Settings.UI.UnitTests.BackwardsCompatibility;
+using Microsoft.PowerToys.Settings.UI.UnitTests.Mocks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using NuGet.Frameworks;
@@ -16,17 +19,70 @@ namespace ViewModelTests
[TestClass]
public class General
{
- public const string generalSettings_file_name = "Test\\GenealSettings";
+ public const string generalSettingsFileName = "Test\\GenealSettings";
+
+ private Mock mockGeneralSettingsUtils;
+
+
+
+ [TestInitialize]
+ public void SetUpStubSettingUtils()
+ {
+ mockGeneralSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils();
+ }
+
+ ///
+ [TestMethod]
+ [DataRow("v0.18.2")]
+ [DataRow("v0.19.2")]
+ [DataRow("v0.20.1")]
+ [DataRow("v0.21.1")]
+ [DataRow("v0.22.0")]
+ public void OriginalFilesModificationTest(string version)
+ {
+ var mockGeneralIOProvider = BackCompatTestProperties.GetGeneralSettingsIOProvider(version);
+ var mockGeneralSettingsUtils = new SettingsUtils(mockGeneralIOProvider.Object);
+ GeneralSettings originalGeneralSettings = mockGeneralSettingsUtils.GetSettings();
+ var generalSettingsRepository = new BackCompatTestProperties.MockSettingsRepository(mockGeneralSettingsUtils);
+
+ // Initialise View Model with test Config files
+ // Arrange
+ Func SendMockIPCConfigMSG = msg => { return 0; };
+ Func SendRestartAdminIPCMessage = msg => { return 0; };
+ Func SendCheckForUpdatesIPCMessage = msg => { return 0; };
+ var viewModel = new GeneralViewModel(
+ settingsRepository: generalSettingsRepository,
+ runAsAdminText: "GeneralSettings_RunningAsAdminText",
+ runAsUserText: "GeneralSettings_RunningAsUserText",
+ isElevated: false,
+ isAdmin: false,
+ updateTheme: UpdateUIThemeMethod,
+ ipcMSGCallBackFunc: SendMockIPCConfigMSG,
+ ipcMSGRestartAsAdminMSGCallBackFunc: SendRestartAdminIPCMessage,
+ ipcMSGCheckForUpdatesCallBackFunc: SendCheckForUpdatesIPCMessage,
+ configFileSubfolder: string.Empty);
+
+ // Verifiy that the old settings persisted
+ Assert.AreEqual(originalGeneralSettings.AutoDownloadUpdates, viewModel.AutoDownloadUpdates);
+ Assert.AreEqual(originalGeneralSettings.Packaged, viewModel.Packaged);
+ Assert.AreEqual(originalGeneralSettings.PowertoysVersion, viewModel.PowerToysVersion);
+ Assert.AreEqual(originalGeneralSettings.RunElevated, viewModel.RunElevated);
+ Assert.AreEqual(originalGeneralSettings.Startup, viewModel.Startup);
+
+ //Verify that the stub file was used
+ var expectedCallCount = 2; //once via the view model, and once by the test (GetSettings)
+ BackCompatTestProperties.VerifyGeneralSettingsIOProviderWasRead(mockGeneralIOProvider, expectedCallCount);
+ }
[TestMethod]
- public void IsElevated_ShouldUpdateRunasAdminStatusAttrs_WhenSuccessful()
+ public void IsElevatedShouldUpdateRunasAdminStatusAttrsWhenSuccessful()
{
// Arrange
Func SendMockIPCConfigMSG = msg => { return 0; };
Func SendRestartAdminIPCMessage = msg => { return 0; };
Func SendCheckForUpdatesIPCMessage = msg => { return 0; };
GeneralViewModel viewModel = new GeneralViewModel(
- new Mock().Object,
+ SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object),
"GeneralSettings_RunningAsAdminText",
"GeneralSettings_RunningAsUserText",
false,
@@ -35,7 +91,7 @@ namespace ViewModelTests
SendMockIPCConfigMSG,
SendRestartAdminIPCMessage,
SendCheckForUpdatesIPCMessage,
- generalSettings_file_name);
+ generalSettingsFileName);
Assert.AreEqual(viewModel.RunningAsUserDefaultText, viewModel.RunningAsText);
Assert.IsFalse(viewModel.IsElevated);
@@ -49,7 +105,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void Startup_ShouldEnableRunOnStartUp_WhenSuccessful()
+ public void StartupShouldEnableRunOnStartUpWhenSuccessful()
{
// Assert
Func SendMockIPCConfigMSG = msg =>
@@ -63,7 +119,7 @@ namespace ViewModelTests
Func SendRestartAdminIPCMessage = msg => { return 0; };
Func SendCheckForUpdatesIPCMessage = msg => { return 0; };
GeneralViewModel viewModel = new GeneralViewModel(
- new Mock().Object,
+ SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object),
"GeneralSettings_RunningAsAdminText",
"GeneralSettings_RunningAsUserText",
false,
@@ -72,7 +128,7 @@ namespace ViewModelTests
SendMockIPCConfigMSG,
SendRestartAdminIPCMessage,
SendCheckForUpdatesIPCMessage,
- generalSettings_file_name);
+ generalSettingsFileName);
Assert.IsFalse(viewModel.Startup);
// act
@@ -80,7 +136,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void RunElevated_ShouldEnableAlwaysRunElevated_WhenSuccessful()
+ public void RunElevatedShouldEnableAlwaysRunElevatedWhenSuccessful()
{
// Assert
Func SendMockIPCConfigMSG = msg =>
@@ -95,7 +151,7 @@ namespace ViewModelTests
// Arrange
GeneralViewModel viewModel = new GeneralViewModel(
- new Mock().Object,
+ SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object),
"GeneralSettings_RunningAsAdminText",
"GeneralSettings_RunningAsUserText",
false,
@@ -104,7 +160,7 @@ namespace ViewModelTests
SendMockIPCConfigMSG,
SendRestartAdminIPCMessage,
SendCheckForUpdatesIPCMessage,
- generalSettings_file_name);
+ generalSettingsFileName);
Assert.IsFalse(viewModel.RunElevated);
@@ -113,7 +169,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void IsLightThemeRadioButtonChecked_ShouldThemeToLight_WhenSuccessful()
+ public void IsLightThemeRadioButtonCheckedShouldThemeToLightWhenSuccessful()
{
// Arrange
GeneralViewModel viewModel = null;
@@ -128,7 +184,7 @@ namespace ViewModelTests
Func SendRestartAdminIPCMessage = msg => { return 0; };
Func SendCheckForUpdatesIPCMessage = msg => { return 0; };
viewModel = new GeneralViewModel(
- new Mock().Object,
+ SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object),
"GeneralSettings_RunningAsAdminText",
"GeneralSettings_RunningAsUserText",
false,
@@ -137,7 +193,7 @@ namespace ViewModelTests
SendMockIPCConfigMSG,
SendRestartAdminIPCMessage,
SendCheckForUpdatesIPCMessage,
- generalSettings_file_name);
+ generalSettingsFileName);
Assert.IsFalse(viewModel.IsLightThemeRadioButtonChecked);
// act
@@ -145,7 +201,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void IsDarkThemeRadioButtonChecked_ShouldThemeToDark_WhenSuccessful()
+ public void IsDarkThemeRadioButtonCheckedShouldThemeToDarkWhenSuccessful()
{
// Arrange
// Assert
@@ -159,7 +215,7 @@ namespace ViewModelTests
Func SendRestartAdminIPCMessage = msg => { return 0; };
Func SendCheckForUpdatesIPCMessage = msg => { return 0; };
GeneralViewModel viewModel = new GeneralViewModel(
- new Mock().Object,
+ SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object),
"GeneralSettings_RunningAsAdminText",
"GeneralSettings_RunningAsUserText",
false,
@@ -168,7 +224,7 @@ namespace ViewModelTests
SendMockIPCConfigMSG,
SendRestartAdminIPCMessage,
SendCheckForUpdatesIPCMessage,
- generalSettings_file_name);
+ generalSettingsFileName);
Assert.IsFalse(viewModel.IsDarkThemeRadioButtonChecked);
@@ -195,7 +251,7 @@ namespace ViewModelTests
Assert.IsTrue(modules.ColorPicker);
}
- public int UpdateUIThemeMethod(string themeName)
+ public static int UpdateUIThemeMethod(string themeName)
{
return 0;
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/ImageResizer.cs b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/ImageResizer.cs
index 0e4ef3e16f..74b61e89ea 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/ImageResizer.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/ImageResizer.cs
@@ -3,12 +3,14 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Json;
using Microsoft.PowerToys.Settings.UI.Lib;
using Microsoft.PowerToys.Settings.UI.Lib.Utilities;
using Microsoft.PowerToys.Settings.UI.Lib.ViewModels;
+using Microsoft.PowerToys.Settings.UI.UnitTests.BackwardsCompatibility;
using Microsoft.PowerToys.Settings.UI.UnitTests.Mocks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
@@ -18,13 +20,62 @@ namespace ViewModelTests
[TestClass]
public class ImageResizer
{
- public const string Module = "ImageResizer";
+
+ private Mock mockGeneralSettingsUtils;
+
+ private Mock mockImgResizerSettingsUtils;
+
+ [TestInitialize]
+ public void SetUpStubSettingUtils()
+ {
+ mockGeneralSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils();
+ mockImgResizerSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils();
+ }
+
+
+ ///
+ /// Test if the original settings files were modified.
+ ///
+ [TestMethod]
+ [DataRow("v0.18.2", "settings.json")]
+ [DataRow("v0.19.2", "settings.json")]
+ [DataRow("v0.20.1", "settings.json")]
+ [DataRow("v0.21.1", "settings.json")]
+ [DataRow("v0.22.0", "settings.json")]
+ public void OriginalFilesModificationTest(string version, string fileName)
+ {
+ var mockIOProvider = BackCompatTestProperties.GetModuleIOProvider(version, ImageResizerSettings.ModuleName, fileName);
+ var mockSettingsUtils = new SettingsUtils(mockIOProvider.Object);
+ ImageResizerSettings originalSettings = mockSettingsUtils.GetSettings(ImageResizerSettings.ModuleName);
+
+ var mockGeneralIOProvider = BackCompatTestProperties.GetGeneralSettingsIOProvider(version);
+ var mockGeneralSettingsUtils = new SettingsUtils(mockGeneralIOProvider.Object);
+ GeneralSettings originalGeneralSettings = mockGeneralSettingsUtils.GetSettings();
+ var generalSettingsRepository = new BackCompatTestProperties.MockSettingsRepository(mockGeneralSettingsUtils);
+
+ // Initialise View Model with test Config files
+ Func SendMockIPCConfigMSG = msg => { return 0; };
+ ImageResizerViewModel viewModel = new ImageResizerViewModel(mockSettingsUtils, generalSettingsRepository, SendMockIPCConfigMSG);
+
+ // Verifiy that the old settings persisted
+ Assert.AreEqual(originalGeneralSettings.Enabled.ImageResizer, viewModel.IsEnabled);
+ Assert.AreEqual(ImageResizerViewModel.GetEncoderIndex(originalSettings.Properties.ImageresizerFallbackEncoder.Value), viewModel.Encoder);
+ Assert.AreEqual(originalSettings.Properties.ImageresizerFileName.Value, viewModel.FileName);
+ Assert.AreEqual(originalSettings.Properties.ImageresizerJpegQualityLevel.Value, viewModel.JPEGQualityLevel);
+ Assert.AreEqual(originalSettings.Properties.ImageresizerKeepDateModified.Value, viewModel.KeepDateModified);
+ Assert.AreEqual(originalSettings.Properties.ImageresizerPngInterlaceOption.Value, viewModel.PngInterlaceOption);
+ Assert.AreEqual(originalSettings.Properties.ImageresizerSizes.Value.Count, viewModel.Sizes.Count);
+ Assert.AreEqual(originalSettings.Properties.ImageresizerTiffCompressOption.Value, viewModel.TiffCompressOption);
+
+ //Verify that the stub file was used
+ var expectedCallCount = 2; //once via the view model, and once by the test (GetSettings)
+ BackCompatTestProperties.VerifyModuleIOProviderWasRead(mockIOProvider, ImageResizerSettings.ModuleName, expectedCallCount);
+ BackCompatTestProperties.VerifyGeneralSettingsIOProviderWasRead(mockGeneralIOProvider, expectedCallCount);
+ }
[TestMethod]
- public void IsEnabled_ShouldEnableModule_WhenSuccessful()
+ public void IsEnabledShouldEnableModuleWhenSuccessful()
{
- var mockSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils();
-
// Assert
Func SendMockIPCConfigMSG = msg =>
{
@@ -34,96 +85,96 @@ namespace ViewModelTests
};
// arrange
- ImageResizerViewModel viewModel = new ImageResizerViewModel(mockSettingsUtils.Object, SendMockIPCConfigMSG);
+ ImageResizerViewModel viewModel = new ImageResizerViewModel(mockImgResizerSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG);
// act
viewModel.IsEnabled = true;
}
[TestMethod]
- public void JPEGQualityLevel_ShouldSetValueToTen_WhenSuccessful()
+ public void JPEGQualityLevelShouldSetValueToTenWhenSuccessful()
{
// arrange
var mockIOProvider = IIOProviderMocks.GetMockIOProviderForSaveLoadExists();
var mockSettingsUtils = new SettingsUtils(mockIOProvider.Object);
Func SendMockIPCConfigMSG = msg => { return 0; };
- ImageResizerViewModel viewModel = new ImageResizerViewModel(mockSettingsUtils, SendMockIPCConfigMSG);
+ ImageResizerViewModel viewModel = new ImageResizerViewModel(mockSettingsUtils, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG);
// act
viewModel.JPEGQualityLevel = 10;
// Assert
- viewModel = new ImageResizerViewModel(mockSettingsUtils, SendMockIPCConfigMSG);
+ viewModel = new ImageResizerViewModel(mockSettingsUtils, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG);
Assert.AreEqual(10, viewModel.JPEGQualityLevel);
}
[TestMethod]
- public void PngInterlaceOption_ShouldSetValueToTen_WhenSuccessful()
+ public void PngInterlaceOptionShouldSetValueToTenWhenSuccessful()
{
// arrange
var mockIOProvider = IIOProviderMocks.GetMockIOProviderForSaveLoadExists();
var mockSettingsUtils = new SettingsUtils(mockIOProvider.Object);
Func SendMockIPCConfigMSG = msg => { return 0; };
- ImageResizerViewModel viewModel = new ImageResizerViewModel(mockSettingsUtils, SendMockIPCConfigMSG);
+ ImageResizerViewModel viewModel = new ImageResizerViewModel(mockSettingsUtils, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG);
// act
viewModel.PngInterlaceOption = 10;
// Assert
- viewModel = new ImageResizerViewModel(mockSettingsUtils, SendMockIPCConfigMSG);
+ viewModel = new ImageResizerViewModel(mockSettingsUtils, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG);
Assert.AreEqual(10, viewModel.PngInterlaceOption);
}
[TestMethod]
- public void TiffCompressOption_ShouldSetValueToTen_WhenSuccessful()
+ public void TiffCompressOptionShouldSetValueToTenWhenSuccessful()
{
// arrange
var mockIOProvider = IIOProviderMocks.GetMockIOProviderForSaveLoadExists();
var mockSettingsUtils = new SettingsUtils(mockIOProvider.Object);
Func SendMockIPCConfigMSG = msg => { return 0; };
- ImageResizerViewModel viewModel = new ImageResizerViewModel(mockSettingsUtils, SendMockIPCConfigMSG);
+ ImageResizerViewModel viewModel = new ImageResizerViewModel(mockSettingsUtils, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG);
// act
viewModel.TiffCompressOption = 10;
// Assert
- viewModel = new ImageResizerViewModel(mockSettingsUtils, SendMockIPCConfigMSG);
+ viewModel = new ImageResizerViewModel(mockSettingsUtils, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG);
Assert.AreEqual(10, viewModel.TiffCompressOption);
}
[TestMethod]
- public void FileName_ShouldUpdateValue_WhenSuccessful()
+ public void FileNameShouldUpdateValueWhenSuccessful()
{
// arrange
var mockIOProvider = IIOProviderMocks.GetMockIOProviderForSaveLoadExists();
var mockSettingsUtils = new SettingsUtils(mockIOProvider.Object);
Func SendMockIPCConfigMSG = msg => { return 0; };
- ImageResizerViewModel viewModel = new ImageResizerViewModel(mockSettingsUtils, SendMockIPCConfigMSG);
+ ImageResizerViewModel viewModel = new ImageResizerViewModel(mockSettingsUtils, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG);
string expectedValue = "%1 (%3)";
// act
viewModel.FileName = expectedValue;
// Assert
- viewModel = new ImageResizerViewModel(mockSettingsUtils, SendMockIPCConfigMSG);
+ viewModel = new ImageResizerViewModel(mockSettingsUtils, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG);
Assert.AreEqual(expectedValue, viewModel.FileName);
}
[TestMethod]
- public void KeepDateModified_ShouldUpdateValue_WhenSuccessful()
+ public void KeepDateModifiedShouldUpdateValueWhenSuccessful()
{
// arrange
- var settingUtils = ISettingsUtilsMocks.GetStubSettingsUtils();
+ var settingUtils = ISettingsUtilsMocks.GetStubSettingsUtils();
var expectedSettingsString = new ImageResizerSettings() { Properties = new ImageResizerProperties() { ImageresizerKeepDateModified = new BoolProperty() { Value = true } } }.ToJsonString();
settingUtils.Setup(x => x.SaveSettings(
It.Is(content => content.Equals(expectedSettingsString, StringComparison.Ordinal)),
- It.Is(module => module.Equals(Module, StringComparison.Ordinal)),
+ It.Is(module => module.Equals(ImageResizerSettings.ModuleName, StringComparison.Ordinal)),
It.IsAny()))
.Verifiable();
Func SendMockIPCConfigMSG = msg => { return 0; };
- ImageResizerViewModel viewModel = new ImageResizerViewModel(settingUtils.Object, SendMockIPCConfigMSG);
+ ImageResizerViewModel viewModel = new ImageResizerViewModel(settingUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG);
// act
viewModel.KeepDateModified = true;
@@ -133,46 +184,46 @@ namespace ViewModelTests
}
[TestMethod]
- public void Encoder_ShouldUpdateValue_WhenSuccessful()
+ public void EncoderShouldUpdateValueWhenSuccessful()
{
// arrange
var mockIOProvider = IIOProviderMocks.GetMockIOProviderForSaveLoadExists();
var mockSettingsUtils = new SettingsUtils(mockIOProvider.Object);
Func SendMockIPCConfigMSG = msg => { return 0; };
- ImageResizerViewModel viewModel = new ImageResizerViewModel(mockSettingsUtils, SendMockIPCConfigMSG);
+ ImageResizerViewModel viewModel = new ImageResizerViewModel(mockSettingsUtils, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG);
// act
viewModel.Encoder = 3;
// Assert
- viewModel = new ImageResizerViewModel(mockSettingsUtils, SendMockIPCConfigMSG);
- Assert.AreEqual("163bcc30-e2e9-4f0b-961d-a3e9fdb788a3", viewModel.GetEncoderGuid(viewModel.Encoder));
+ viewModel = new ImageResizerViewModel(mockSettingsUtils, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG);
+ Assert.AreEqual("163bcc30-e2e9-4f0b-961d-a3e9fdb788a3", viewModel.EncoderGuid);
Assert.AreEqual(3, viewModel.Encoder);
}
[TestMethod]
- public void AddRow_ShouldAddEmptyImageSize_WhenSuccessful()
+ public void AddRowShouldAddEmptyImageSizeWhenSuccessful()
{
// arrange
- var mockSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils();
+ var mockSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils();
Func SendMockIPCConfigMSG = msg => { return 0; };
- ImageResizerViewModel viewModel = new ImageResizerViewModel(mockSettingsUtils.Object, SendMockIPCConfigMSG);
+ ImageResizerViewModel viewModel = new ImageResizerViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG);
int sizeOfOriginalArray = viewModel.Sizes.Count;
// act
viewModel.AddRow();
// Assert
- Assert.AreEqual(viewModel.Sizes.Count, sizeOfOriginalArray + 1);
+ Assert.AreEqual(sizeOfOriginalArray + 1, viewModel.Sizes.Count);
}
[TestMethod]
- public void DeleteImageSize_ShouldDeleteImageSize_WhenSuccessful()
+ public void DeleteImageSizeShouldDeleteImageSizeWhenSuccessful()
{
// arrange
- var mockSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils();
+ var mockSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils();
Func SendMockIPCConfigMSG = msg => { return 0; };
- ImageResizerViewModel viewModel = new ImageResizerViewModel(mockSettingsUtils.Object, SendMockIPCConfigMSG);
+ ImageResizerViewModel viewModel = new ImageResizerViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG);
int sizeOfOriginalArray = viewModel.Sizes.Count;
ImageSize deleteCandidate = viewModel.Sizes.Where(x => x.Id == 0).First();
@@ -180,12 +231,12 @@ namespace ViewModelTests
viewModel.DeleteImageSize(0);
// Assert
- Assert.AreEqual(viewModel.Sizes.Count, sizeOfOriginalArray - 1);
+ Assert.AreEqual(sizeOfOriginalArray - 1, viewModel.Sizes.Count);
Assert.IsFalse(viewModel.Sizes.Contains(deleteCandidate));
}
[TestMethod]
- public void UpdateWidthAndHeight_ShouldUpateSize_WhenCorrectValuesAreProvided()
+ public void UpdateWidthAndHeightShouldUpateSizeWhenCorrectValuesAreProvided()
{
// arrange
ImageSize imageSize = new ImageSize()
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/KeyboardManager.cs b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/KeyboardManager.cs
index 970fe95512..75ee9479ea 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/KeyboardManager.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/KeyboardManager.cs
@@ -13,7 +13,7 @@ namespace ViewModelTests
[TestClass]
public class KeyboardManager
{
- public const string Module = "Keyboard Manager";
+ public const string Module = KeyboardManagerSettings.ModuleName;
[TestInitialize]
public void Setup()
@@ -26,7 +26,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void CombineShortcutLists_ShouldReturnEmptyList_WhenBothArgumentsAreEmptyLists()
+ public void CombineShortcutListsShouldReturnEmptyListWhenBothArgumentsAreEmptyLists()
{
// arrange
var firstList = new List();
@@ -38,11 +38,74 @@ namespace ViewModelTests
// Assert
var expectedResult = new List();
- Assert.AreEqual(expectedResult.Count(), result.Count());
+ Assert.AreEqual(expectedResult.Count, result.Count);
}
[TestMethod]
- public void CombineShortcutLists_ShouldReturnListWithOneAllAppsEntry_WhenFirstArgumentHasOneEntryAndSecondArgumentIsEmpty()
+ public void CombineShortcutListsShouldReturnEmptyListWhenBothArgumentsAreNull()
+ {
+ // act
+ var result = KeyboardManagerViewModel.CombineShortcutLists(null, null);
+
+ // Assert
+ var expectedResult = new List();
+
+ Assert.AreEqual(expectedResult.Count, result.Count);
+ }
+
+ [TestMethod]
+ public void CombineShortcutListsShouldReturnListWithOneAppSpecificEntryWhenFirstArgumentIsNullAndSecondArgumentHasOneEntry()
+ {
+ // arrange
+ var secondList = new List();
+ var entry = new AppSpecificKeysDataModel();
+ entry.OriginalKeys = "17;65";
+ entry.NewRemapKeys = "17;86";
+ entry.TargetApp = "msedge";
+ secondList.Add(entry);
+
+ // act
+ var result = KeyboardManagerViewModel.CombineShortcutLists(null, secondList);
+
+ // Assert
+ var expectedResult = new List();
+ var expectedEntry = new AppSpecificKeysDataModel();
+ expectedEntry.OriginalKeys = entry.OriginalKeys;
+ expectedEntry.NewRemapKeys = entry.NewRemapKeys;
+ expectedEntry.TargetApp = entry.TargetApp;
+ expectedResult.Add(expectedEntry);
+
+ Assert.AreEqual(expectedResult.Count, result.Count);
+ Assert.IsTrue(expectedResult[0].Compare(result[0]));
+ }
+
+ [TestMethod]
+ public void CombineShortcutListsShouldReturnListWithOneAllAppsEntryWhenFirstArgumentHasOneEntryAndSecondArgumentIsNull()
+ {
+ // arrange
+ var firstList = new List();
+ var entry = new KeysDataModel();
+ entry.OriginalKeys = "17;65";
+ entry.NewRemapKeys = "17;86";
+ firstList.Add(entry);
+
+ // act
+ var result = KeyboardManagerViewModel.CombineShortcutLists(firstList, null);
+
+ // Assert
+ var expectedResult = new List();
+ var expectedEntry = new AppSpecificKeysDataModel();
+ expectedEntry.OriginalKeys = entry.OriginalKeys;
+ expectedEntry.NewRemapKeys = entry.NewRemapKeys;
+ expectedEntry.TargetApp = "All Apps";
+ expectedResult.Add(expectedEntry);
+
+ Assert.AreEqual(expectedResult.Count, result.Count);
+ Assert.IsTrue(expectedResult[0].Compare(result[0]));
+ }
+
+ [TestMethod]
+ public void CombineShortcutListsShouldReturnListWithOneAllAppsEntryWhenFirstArgumentHasOneEntryAndSecondArgumentIsEmpty()
{
// arrange
var firstList = new List();
@@ -63,12 +126,12 @@ namespace ViewModelTests
expectedEntry.TargetApp = "All Apps";
expectedResult.Add(expectedEntry);
var x = expectedResult[0].Equals(result[0]);
- Assert.AreEqual(expectedResult.Count(), result.Count());
+ Assert.AreEqual(expectedResult.Count, result.Count);
Assert.IsTrue(expectedResult[0].Compare(result[0]));
}
[TestMethod]
- public void CombineShortcutLists_ShouldReturnListWithOneAppSpecificEntry_WhenFirstArgumentIsEmptyAndSecondArgumentHasOneEntry()
+ public void CombineShortcutListsShouldReturnListWithOneAppSpecificEntryWhenFirstArgumentIsEmptyAndSecondArgumentHasOneEntry()
{
// arrange
var firstList = new List();
@@ -90,12 +153,12 @@ namespace ViewModelTests
expectedEntry.TargetApp = entry.TargetApp;
expectedResult.Add(expectedEntry);
- Assert.AreEqual(expectedResult.Count(), result.Count());
+ Assert.AreEqual(expectedResult.Count, result.Count);
Assert.IsTrue(expectedResult[0].Compare(result[0]));
}
[TestMethod]
- public void CombineShortcutLists_ShouldReturnListWithOneAllAppsEntryAndOneAppSpecificEntry_WhenFirstArgumentHasOneEntryAndSecondArgumentHasOneEntry()
+ public void CombineShortcutListsShouldReturnListWithOneAllAppsEntryAndOneAppSpecificEntryWhenFirstArgumentHasOneEntryAndSecondArgumentHasOneEntry()
{
// arrange
var firstList = new List();
@@ -126,7 +189,7 @@ namespace ViewModelTests
expectedSecondEntry.TargetApp = secondListEntry.TargetApp;
expectedResult.Add(expectedSecondEntry);
- Assert.AreEqual(expectedResult.Count(), result.Count());
+ Assert.AreEqual(expectedResult.Count, result.Count);
Assert.IsTrue(expectedResult[0].Compare(result[0]));
Assert.IsTrue(expectedResult[1].Compare(result[1]));
}
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/PowerLauncherViewModelTest.cs b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/PowerLauncherViewModelTest.cs
index c1adf71bd0..7a5c26649a 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/PowerLauncherViewModelTest.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/PowerLauncherViewModelTest.cs
@@ -5,7 +5,11 @@
using Microsoft.PowerToys.Settings.UI.Lib;
using Microsoft.PowerToys.Settings.UI.Lib.ViewModels;
using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System;
using Moq;
+using Microsoft.PowerToys.Settings.UI.UnitTests.Mocks;
+using Microsoft.PowerToys.Settings.UI.UnitTests.BackwardsCompatibility;
+using System.Globalization;
namespace ViewModelTests
{
@@ -16,7 +20,9 @@ namespace ViewModelTests
{
public int TimesSent { get; set; }
- public void OnSend(PowerLauncherSettings settings)
+ // PowerLauncherSettings is unused, but required according to SendCallback's signature.
+ // Naming parameter with discard symbol to suppress FxCop warnings.
+ public void OnSend(PowerLauncherSettings _)
{
TimesSent++;
}
@@ -35,8 +41,51 @@ namespace ViewModelTests
new PowerLauncherViewModel.SendCallback(sendCallbackMock.OnSend));
}
+ ///
+ /// Test if the original settings files were modified.
+ ///
[TestMethod]
- public void SearchPreference_ShouldUpdatePreferences()
+ [DataRow("v0.18.2", "settings.json")]
+ [DataRow("v0.19.2", "settings.json")]
+ [DataRow("v0.20.1", "settings.json")]
+ [DataRow("v0.21.1", "settings.json")]
+ [DataRow("v0.22.0", "settings.json")]
+ public void OriginalFilesModificationTest(string version, string fileName)
+ {
+ var mockIOProvider = BackCompatTestProperties.GetModuleIOProvider(version, PowerLauncherSettings.ModuleName, fileName);
+ var mockSettingsUtils = new SettingsUtils(mockIOProvider.Object);
+ PowerLauncherSettings originalSettings = mockSettingsUtils.GetSettings(PowerLauncherSettings.ModuleName);
+
+ var mockGeneralIOProvider = BackCompatTestProperties.GetGeneralSettingsIOProvider(version);
+ var mockGeneralSettingsUtils = new SettingsUtils(mockGeneralIOProvider.Object);
+ GeneralSettings originalGeneralSettings = mockGeneralSettingsUtils.GetSettings();
+ var generalSettingsRepository = new BackCompatTestProperties.MockSettingsRepository(mockGeneralSettingsUtils);
+
+ // Initialise View Model with test Config files
+ Func SendMockIPCConfigMSG = msg => { return 0; };
+ PowerLauncherViewModel viewModel = new PowerLauncherViewModel(mockSettingsUtils, generalSettingsRepository, SendMockIPCConfigMSG, 32);
+
+ // Verifiy that the old settings persisted
+ Assert.AreEqual(originalGeneralSettings.Enabled.PowerLauncher, viewModel.EnablePowerLauncher);
+ Assert.AreEqual(originalSettings.Properties.ClearInputOnLaunch, viewModel.ClearInputOnLaunch);
+ Assert.AreEqual(originalSettings.Properties.CopyPathLocation.ToString(), viewModel.CopyPathLocation.ToString());
+ Assert.AreEqual(originalSettings.Properties.DisableDriveDetectionWarning, viewModel.DisableDriveDetectionWarning);
+ Assert.AreEqual(originalSettings.Properties.IgnoreHotkeysInFullscreen, viewModel.IgnoreHotkeysInFullScreen);
+ Assert.AreEqual(originalSettings.Properties.MaximumNumberOfResults, viewModel.MaximumNumberOfResults);
+ Assert.AreEqual(originalSettings.Properties.OpenPowerLauncher.ToString(), viewModel.OpenPowerLauncher.ToString());
+ Assert.AreEqual(originalSettings.Properties.OverrideWinkeyR, viewModel.OverrideWinRKey);
+ Assert.AreEqual(originalSettings.Properties.OverrideWinkeyS, viewModel.OverrideWinSKey);
+ Assert.AreEqual(originalSettings.Properties.SearchResultPreference, viewModel.SearchResultPreference);
+ Assert.AreEqual(originalSettings.Properties.SearchTypePreference, viewModel.SearchTypePreference);
+
+ //Verify that the stub file was used
+ var expectedCallCount = 2; //once via the view model, and once by the test (GetSettings)
+ BackCompatTestProperties.VerifyModuleIOProviderWasRead(mockIOProvider, PowerLauncherSettings.ModuleName, expectedCallCount);
+ BackCompatTestProperties.VerifyGeneralSettingsIOProviderWasRead(mockGeneralIOProvider, expectedCallCount);
+ }
+
+ [TestMethod]
+ public void SearchPreferenceShouldUpdatePreferences()
{
viewModel.SearchResultPreference = "SearchOptionsAreNotValidated";
viewModel.SearchTypePreference = "SearchOptionsAreNotValidated";
@@ -46,17 +95,23 @@ namespace ViewModelTests
Assert.IsTrue(mockSettings.Properties.SearchTypePreference == "SearchOptionsAreNotValidated");
}
- public void AssertHotkeySettings(HotkeySettings setting, bool win, bool ctrl, bool alt, bool shift, int code)
+ public static void AssertHotkeySettings(HotkeySettings setting, bool win, bool ctrl, bool alt, bool shift, int code)
{
- Assert.AreEqual(win, setting.Win);
- Assert.AreEqual(ctrl, setting.Ctrl);
- Assert.AreEqual(alt, setting.Alt);
- Assert.AreEqual(shift, setting.Shift);
- Assert.AreEqual(code, setting.Code);
+ if (setting != null)
+ {
+ Assert.AreEqual(win, setting.Win);
+ Assert.AreEqual(ctrl, setting.Ctrl);
+ Assert.AreEqual(alt, setting.Alt);
+ Assert.AreEqual(shift, setting.Shift);
+ Assert.AreEqual(code, setting.Code);
+ } else
+ {
+ Assert.Fail("setting parameter is null");
+ }
}
[TestMethod]
- public void Hotkeys_ShouldUpdateHotkeys()
+ public void HotkeysShouldUpdateHotkeys()
{
var openPowerLauncher = new HotkeySettings();
openPowerLauncher.Win = true;
@@ -104,7 +159,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void Override_ShouldUpdateOverrides()
+ public void OverrideShouldUpdateOverrides()
{
viewModel.OverrideWinRKey = true;
viewModel.OverrideWinSKey = false;
@@ -116,7 +171,7 @@ namespace ViewModelTests
}
[TestMethod]
- public void DriveDetectionViewModel_WhenSet_MustUpdateOverrides()
+ public void DriveDetectionViewModelWhenSetMustUpdateOverrides()
{
// Act
viewModel.DisableDriveDetectionWarning = true;
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/PowerPreview.cs b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/PowerPreview.cs
index 7833d2ff7e..4175bff6c0 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/PowerPreview.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/PowerPreview.cs
@@ -7,6 +7,7 @@ using System.IO;
using System.Text.Json;
using Microsoft.PowerToys.Settings.UI.Lib;
using Microsoft.PowerToys.Settings.UI.Lib.ViewModels;
+using Microsoft.PowerToys.Settings.UI.UnitTests.BackwardsCompatibility;
using Microsoft.PowerToys.Settings.UI.UnitTests.Mocks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
@@ -16,10 +17,57 @@ namespace ViewModelTests
[TestClass]
public class PowerPreview
{
- public const string Module = "Test\\File Explorer";
+
+ private Mock mockPowerPreviewSettingsUtils;
+
+ private Mock mockGeneralSettingsUtils;
+
+ [TestInitialize]
+ public void SetUpStubSettingUtils()
+ {
+ mockPowerPreviewSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils();
+ mockGeneralSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils();
+ }
+
+ ///
+ /// Test if the original settings files were modified.
+ ///
+ [TestMethod]
+ [DataRow("v0.18.2", "settings.json")]
+ [DataRow("v0.19.2", "settings.json")]
+ [DataRow("v0.20.1", "settings.json")]
+ [DataRow("v0.21.1", "settings.json")]
+ [DataRow("v0.22.0", "settings.json")]
+ public void OriginalFilesModificationTest(string version, string fileName)
+ {
+ var mockIOProvider = BackCompatTestProperties.GetModuleIOProvider(version, PowerPreviewSettings.ModuleName, fileName);
+ var mockSettingsUtils = new SettingsUtils(mockIOProvider.Object);
+ PowerPreviewSettings originalSettings = mockSettingsUtils.GetSettings(PowerPreviewSettings.ModuleName);
+ var repository = new BackCompatTestProperties.MockSettingsRepository(mockSettingsUtils);
+
+ var mockGeneralIOProvider = BackCompatTestProperties.GetGeneralSettingsIOProvider(version);
+ var mockGeneralSettingsUtils = new SettingsUtils(mockGeneralIOProvider.Object);
+ GeneralSettings originalGeneralSettings = mockGeneralSettingsUtils.GetSettings();
+ var generalSettingsRepository = new BackCompatTestProperties.MockSettingsRepository(mockGeneralSettingsUtils);
+
+ // Initialise View Model with test Config files
+ Func SendMockIPCConfigMSG = msg => { return 0; };
+ PowerPreviewViewModel viewModel = new PowerPreviewViewModel(repository, generalSettingsRepository, SendMockIPCConfigMSG);
+
+ // Verifiy that the old settings persisted
+ Assert.AreEqual(originalGeneralSettings.IsElevated, viewModel.IsElevated);
+ Assert.AreEqual(originalSettings.Properties.EnableMdPreview, viewModel.MDRenderIsEnabled);
+ Assert.AreEqual(originalSettings.Properties.EnableSvgPreview, viewModel.SVGRenderIsEnabled);
+ Assert.AreEqual(originalSettings.Properties.EnableSvgThumbnail, viewModel.SVGThumbnailIsEnabled);
+
+ //Verify that the stub file was used
+ var expectedCallCount = 2; //once via the view model, and once by the test (GetSettings)
+ BackCompatTestProperties.VerifyModuleIOProviderWasRead(mockIOProvider, PowerPreviewSettings.ModuleName, expectedCallCount);
+ }
+
[TestMethod]
- public void SVGRenderIsEnabled_ShouldPrevHandler_WhenSuccessful()
+ public void SVGRenderIsEnabledShouldPrevHandlerWhenSuccessful()
{
// Assert
Func SendMockIPCConfigMSG = msg =>
@@ -30,14 +78,14 @@ namespace ViewModelTests
};
// arrange
- PowerPreviewViewModel viewModel = new PowerPreviewViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, Module);
+ PowerPreviewViewModel viewModel = new PowerPreviewViewModel(SettingsRepository.GetInstance(mockPowerPreviewSettingsUtils.Object), SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG, PowerPreviewSettings.ModuleName);
// act
viewModel.SVGRenderIsEnabled = true;
}
[TestMethod]
- public void SVGThumbnailIsEnabled_ShouldPrevHandler_WhenSuccessful()
+ public void SVGThumbnailIsEnabledShouldPrevHandlerWhenSuccessful()
{
// Assert
Func SendMockIPCConfigMSG = msg =>
@@ -48,14 +96,14 @@ namespace ViewModelTests
};
// arrange
- PowerPreviewViewModel viewModel = new PowerPreviewViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, Module);
+ PowerPreviewViewModel viewModel = new PowerPreviewViewModel(SettingsRepository.GetInstance(mockPowerPreviewSettingsUtils.Object), SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG, PowerPreviewSettings.ModuleName);
// act
viewModel.SVGThumbnailIsEnabled = true;
}
[TestMethod]
- public void MDRenderIsEnabled_ShouldPrevHandler_WhenSuccessful()
+ public void MDRenderIsEnabledShouldPrevHandlerWhenSuccessful()
{
// Assert
Func SendMockIPCConfigMSG = msg =>
@@ -66,7 +114,7 @@ namespace ViewModelTests
};
// arrange
- PowerPreviewViewModel viewModel = new PowerPreviewViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, Module);;
+ PowerPreviewViewModel viewModel = new PowerPreviewViewModel(SettingsRepository.GetInstance(mockPowerPreviewSettingsUtils.Object), SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG, PowerPreviewSettings.ModuleName); ;
// act
viewModel.MDRenderIsEnabled = true;
diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/PowerRename.cs b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/PowerRename.cs
index 9f9a9d809e..99559ce12f 100644
--- a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/PowerRename.cs
+++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/PowerRename.cs
@@ -7,6 +7,7 @@ using System.IO;
using System.Text.Json;
using Microsoft.PowerToys.Settings.UI.Lib;
using Microsoft.PowerToys.Settings.UI.Lib.ViewModels;
+using Microsoft.PowerToys.Settings.UI.UnitTests.BackwardsCompatibility;
using Microsoft.PowerToys.Settings.UI.UnitTests.Mocks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
@@ -16,11 +17,56 @@ namespace ViewModelTests
[TestClass]
public class PowerRename
{
- public const string ModuleName = "PowerRename";
- public const string generalSettings_file_name = "Test\\PowerRename";
+ public const string generalSettingsFileName = "Test\\PowerRename";
+
+ private Mock mockGeneralSettingsUtils;
+
+ private Mock mockPowerRenamePropertiesUtils;
+
+ [TestInitialize]
+ public void SetUpStubSettingUtils()
+ {
+ mockGeneralSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils