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. + +![Image of PowerToys Run UI](/doc/images/launcher/pt_run_ui.png) +**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. +![Flow of data between UI(view) and ViewModels](/doc/images/launcher/ui_vm_interaction.PNG) +**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); + } +``` +![Flow of data between UI(view) and ViewModels](/doc/images/launcher/vm_plugin_interaction.PNG) +**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. + +![Image of Calculator plugin](/doc/images/launcher/plugins/calculator.png) + +### [`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. +![Image of Folder plugin](/doc/images/launcher/plugins/folder.png) + +### [`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. + +![Image of Indexer plugin](/doc/images/launcher/plugins/indexer.png) + +### [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. + +![Image of Program plugin](/doc/images/launcher/plugins/program.png) + +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 `>`. + +![Image of Shell plugin](/doc/images/launcher/plugins/shell.png) + +### 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. + +![Image of URI plugin](/doc/images/launcher/plugins/uri.png) + +### [`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. + +![Image of Window Walker plugin](/doc/images/launcher/plugins/windowwalker.png) + +### [`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. + +![Image of project dependency](/doc/images/launcher/launcher_dependency.PNG) +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 +![Image of hotkey control](/doc/images/settingsv2/settingshotkeycontrol.png) + +## 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. + +![Settings UI architecture](/doc/images/settingsv2/ui-architecture.png) +**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(); + mockPowerRenamePropertiesUtils = ISettingsUtilsMocks.GetStubSettingsUtils(); + } + + /// + /// Test if the original settings files were modified. + /// + [TestMethod] + [DataRow("v0.18.2", "power-rename-settings.json")] + [DataRow("v0.19.2", "power-rename-settings.json")] + [DataRow("v0.20.1", "power-rename-settings.json")] + [DataRow("v0.22.0", "power-rename-settings.json")] + public void OriginalFilesModificationTest(string version, string fileName) + { + var mockIOProvider = BackCompatTestProperties.GetModuleIOProvider(version, PowerRenameSettings.ModuleName, fileName); + var mockSettingsUtils = new SettingsUtils(mockIOProvider.Object); + PowerRenameLocalProperties originalSettings = mockSettingsUtils.GetSettings(PowerRenameSettings.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; }; + PowerRenameViewModel viewModel = new PowerRenameViewModel(mockSettingsUtils, generalSettingsRepository, SendMockIPCConfigMSG); + + // Verifiy that the old settings persisted + Assert.AreEqual(originalGeneralSettings.Enabled.PowerRename, viewModel.IsEnabled); + Assert.AreEqual(originalSettings.ExtendedContextMenuOnly, viewModel.EnabledOnContextExtendedMenu); + Assert.AreEqual(originalSettings.MRUEnabled, viewModel.MRUEnabled); + Assert.AreEqual(originalSettings.ShowIcon, viewModel.EnabledOnContextMenu); + + //Verify that the stub file was used + var expectedCallCount = 2; //once via the view model, and once by the test (GetSettings) + BackCompatTestProperties.VerifyModuleIOProviderWasRead(mockIOProvider, PowerRenameSettings.ModuleName, expectedCallCount); + BackCompatTestProperties.VerifyGeneralSettingsIOProviderWasRead(mockGeneralIOProvider, expectedCallCount); + } [TestMethod] - public void IsEnabled_ShouldEnableModule_WhenSuccessful() + public void IsEnabledShouldEnableModuleWhenSuccessful() { // Assert Func SendMockIPCConfigMSG = msg => @@ -31,14 +77,14 @@ namespace ViewModelTests }; // arrange - PowerRenameViewModel viewModel = new PowerRenameViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, generalSettings_file_name); + PowerRenameViewModel viewModel = new PowerRenameViewModel(mockPowerRenamePropertiesUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG, generalSettingsFileName); // act viewModel.IsEnabled = true; } [TestMethod] - public void MRUEnabled_ShouldSetValue2True_WhenSuccessful() + public void MRUEnabledShouldSetValue2TrueWhenSuccessful() { // Assert Func SendMockIPCConfigMSG = msg => @@ -49,7 +95,7 @@ namespace ViewModelTests }; // arrange - PowerRenameViewModel viewModel = new PowerRenameViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, generalSettings_file_name); + PowerRenameViewModel viewModel = new PowerRenameViewModel(mockPowerRenamePropertiesUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG, generalSettingsFileName); // act viewModel.MRUEnabled = true; @@ -59,7 +105,7 @@ namespace ViewModelTests public void WhenIsEnabledIsOffAndMRUEnabledIsOffGlobalAndMruShouldBeOff() { Func SendMockIPCConfigMSG = msg => { return 0; }; - PowerRenameViewModel viewModel = new PowerRenameViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, generalSettings_file_name); + PowerRenameViewModel viewModel = new PowerRenameViewModel(mockPowerRenamePropertiesUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG, generalSettingsFileName); viewModel.IsEnabled = false; viewModel.MRUEnabled = false; @@ -71,7 +117,7 @@ namespace ViewModelTests public void WhenIsEnabledIsOffAndMRUEnabledIsOnGlobalAndMruShouldBeOff() { Func SendMockIPCConfigMSG = msg => { return 0; }; - PowerRenameViewModel viewModel = new PowerRenameViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, generalSettings_file_name); + PowerRenameViewModel viewModel = new PowerRenameViewModel(mockPowerRenamePropertiesUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG, generalSettingsFileName); viewModel.IsEnabled = false; viewModel.MRUEnabled = true; @@ -83,7 +129,7 @@ namespace ViewModelTests public void WhenIsEnabledIsOnAndMRUEnabledIsOffGlobalAndMruShouldBeOff() { Func SendMockIPCConfigMSG = msg => { return 0; }; - PowerRenameViewModel viewModel = new PowerRenameViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, generalSettings_file_name); + PowerRenameViewModel viewModel = new PowerRenameViewModel(mockPowerRenamePropertiesUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG, generalSettingsFileName); viewModel.IsEnabled = true; viewModel.MRUEnabled = false; @@ -95,7 +141,7 @@ namespace ViewModelTests public void WhenIsEnabledIsOnAndMRUEnabledIsOnGlobalAndMruShouldBeOn() { Func SendMockIPCConfigMSG = msg => { return 0; }; - PowerRenameViewModel viewModel = new PowerRenameViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, generalSettings_file_name); + PowerRenameViewModel viewModel = new PowerRenameViewModel(mockPowerRenamePropertiesUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG, generalSettingsFileName); viewModel.IsEnabled = true; viewModel.MRUEnabled = true; @@ -104,7 +150,7 @@ namespace ViewModelTests } [TestMethod] - public void EnabledOnContextMenu_ShouldSetValue2True_WhenSuccessful() + public void EnabledOnContextMenuShouldSetValue2TrueWhenSuccessful() { // Assert Func SendMockIPCConfigMSG = msg => @@ -115,14 +161,14 @@ namespace ViewModelTests }; // arrange - PowerRenameViewModel viewModel = new PowerRenameViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, generalSettings_file_name); + PowerRenameViewModel viewModel = new PowerRenameViewModel(mockPowerRenamePropertiesUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG, generalSettingsFileName); // act viewModel.EnabledOnContextMenu = true; } [TestMethod] - public void EnabledOnContextExtendedMenu_ShouldSetValue2True_WhenSuccessful() + public void EnabledOnContextExtendedMenuShouldSetValue2TrueWhenSuccessful() { // Assert Func SendMockIPCConfigMSG = msg => @@ -133,14 +179,14 @@ namespace ViewModelTests }; // arrange - PowerRenameViewModel viewModel = new PowerRenameViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, generalSettings_file_name); + PowerRenameViewModel viewModel = new PowerRenameViewModel(mockPowerRenamePropertiesUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG, generalSettingsFileName); // act viewModel.EnabledOnContextMenu = true; } [TestMethod] - public void RestoreFlagsOnLaunch_ShouldSetValue2True_WhenSuccessful() + public void RestoreFlagsOnLaunchShouldSetValue2TrueWhenSuccessful() { // Assert Func SendMockIPCConfigMSG = msg => @@ -151,14 +197,14 @@ namespace ViewModelTests }; // arrange - PowerRenameViewModel viewModel = new PowerRenameViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, generalSettings_file_name); + PowerRenameViewModel viewModel = new PowerRenameViewModel(mockPowerRenamePropertiesUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG, generalSettingsFileName); // act viewModel.RestoreFlagsOnLaunch = true; } [TestMethod] - public void MaxDispListNum_ShouldSetMaxSuggListTo20_WhenSuccessful() + public void MaxDispListNumShouldSetMaxSuggListTo20WhenSuccessful() { // Assert Func SendMockIPCConfigMSG = msg => @@ -169,7 +215,7 @@ namespace ViewModelTests }; // arrange - PowerRenameViewModel viewModel = new PowerRenameViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, generalSettings_file_name); + PowerRenameViewModel viewModel = new PowerRenameViewModel(mockPowerRenamePropertiesUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SendMockIPCConfigMSG, generalSettingsFileName); // act viewModel.MaxDispListNum = 20; diff --git a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/ShortcutGuide.cs b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/ShortcutGuide.cs index d788154ffd..0979abacdf 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/ShortcutGuide.cs +++ b/src/core/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/ShortcutGuide.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; @@ -18,8 +19,55 @@ namespace ViewModelTests { public const string ShortCutGuideTestFolderName = "Test\\ShortCutGuide"; + /// + /// Test if the original settings files were modified. + /// [TestMethod] - public void IsEnabled_ShouldEnableModule_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, ShortcutGuideSettings.ModuleName, fileName); + var mockSettingsUtils = new SettingsUtils(mockIOProvider.Object); + ShortcutGuideSettings originalSettings = mockSettingsUtils.GetSettings(ShortcutGuideSettings.ModuleName); + + var mockGeneralIOProvider = BackCompatTestProperties.GetGeneralSettingsIOProvider(version); + var mockGeneralSettingsUtils = new SettingsUtils(mockGeneralIOProvider.Object); + GeneralSettings originalGeneralSettings = mockGeneralSettingsUtils.GetSettings(); + var generalSettingsRepository = new BackCompatTestProperties.MockSettingsRepository(mockGeneralSettingsUtils); + var shortcutSettingsRepository = new BackCompatTestProperties.MockSettingsRepository(mockSettingsUtils); + + // Initialise View Model with test Config files + Func SendMockIPCConfigMSG = msg => { return 0; }; + ShortcutGuideViewModel viewModel = new ShortcutGuideViewModel(generalSettingsRepository, shortcutSettingsRepository, SendMockIPCConfigMSG); + + // Verifiy that the old settings persisted + Assert.AreEqual(originalGeneralSettings.Enabled.ShortcutGuide, viewModel.IsEnabled); + Assert.AreEqual(originalSettings.Properties.OverlayOpacity.Value, viewModel.OverlayOpacity); + Assert.AreEqual(originalSettings.Properties.PressTime.Value, viewModel.PressTime); + + //Verify that the stub file was used + var expectedCallCount = 2; //once via the view model, and once by the test (GetSettings) + BackCompatTestProperties.VerifyModuleIOProviderWasRead(mockIOProvider, ShortcutGuideSettings.ModuleName, expectedCallCount); + BackCompatTestProperties.VerifyGeneralSettingsIOProviderWasRead(mockGeneralIOProvider, expectedCallCount); + } + + private Mock mockGeneralSettingsUtils; + + private Mock mockShortcutGuideSettingsUtils; + + [TestInitialize] + public void SetUpStubSettingUtils() + { + mockGeneralSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils(); + mockShortcutGuideSettingsUtils = ISettingsUtilsMocks.GetStubSettingsUtils(); + } + + [TestMethod] + public void IsEnabledShouldEnableModuleWhenSuccessful() { // Assert // Initialize mock function of sending IPC message. @@ -31,14 +79,14 @@ namespace ViewModelTests }; // Arrange - ShortcutGuideViewModel viewModel = new ShortcutGuideViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, ShortCutGuideTestFolderName); + ShortcutGuideViewModel viewModel = new ShortcutGuideViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockShortcutGuideSettingsUtils.Object), SendMockIPCConfigMSG, ShortCutGuideTestFolderName); // Act viewModel.IsEnabled = true; } [TestMethod] - public void ThemeIndex_ShouldSetThemeToDark_WhenSuccessful() + public void ThemeIndexShouldSetThemeToDarkWhenSuccessful() { // Assert // Initialize mock function of sending IPC message. @@ -50,15 +98,16 @@ namespace ViewModelTests }; // Arrange - ShortcutGuideViewModel viewModel = new ShortcutGuideViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, ShortCutGuideTestFolderName); - Assert.AreEqual(1, viewModel.ThemeIndex); + ShortcutGuideViewModel viewModel = new ShortcutGuideViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockShortcutGuideSettingsUtils.Object), SendMockIPCConfigMSG, ShortCutGuideTestFolderName); + // Initialize shortcut guide settings theme to 'system' to be in sync with shortcut_guide.h. + Assert.AreEqual(2, viewModel.ThemeIndex); // Act viewModel.ThemeIndex = 0; } [TestMethod] - public void PressTime_ShouldSetPressTimeToOneHundred_WhenSuccessful() + public void PressTimeShouldSetPressTimeToOneHundredWhenSuccessful() { // Assert // Initialize mock function of sending IPC message. @@ -70,7 +119,7 @@ namespace ViewModelTests }; // Arrange - ShortcutGuideViewModel viewModel = new ShortcutGuideViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, ShortCutGuideTestFolderName); + ShortcutGuideViewModel viewModel = new ShortcutGuideViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockShortcutGuideSettingsUtils.Object), SendMockIPCConfigMSG, ShortCutGuideTestFolderName); Assert.AreEqual(900, viewModel.PressTime); // Act @@ -78,7 +127,7 @@ namespace ViewModelTests } [TestMethod] - public void OverlayOpacity_ShouldSeOverlayOpacityToOneHundred_WhenSuccessful() + public void OverlayOpacityShouldSeOverlayOpacityToOneHundredWhenSuccessful() { // Assert // Initialize mock function of sending IPC message. @@ -92,7 +141,7 @@ namespace ViewModelTests }; // Arrange - ShortcutGuideViewModel viewModel = new ShortcutGuideViewModel(ISettingsUtilsMocks.GetStubSettingsUtils().Object, SendMockIPCConfigMSG, ShortCutGuideTestFolderName); + ShortcutGuideViewModel viewModel = new ShortcutGuideViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockShortcutGuideSettingsUtils.Object), SendMockIPCConfigMSG, ShortCutGuideTestFolderName); Assert.AreEqual(90, viewModel.OverlayOpacity); // Act diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Controls/HotkeySettingsControl.xaml.cs b/src/core/Microsoft.PowerToys.Settings.UI/Controls/HotkeySettingsControl.xaml.cs index 1cd2f4d9a8..10d39ebfe2 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Controls/HotkeySettingsControl.xaml.cs +++ b/src/core/Microsoft.PowerToys.Settings.UI/Controls/HotkeySettingsControl.xaml.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using Microsoft.PowerToys.Settings.UI.Helpers; using Microsoft.PowerToys.Settings.UI.Lib; using Windows.UI.Core; using Windows.UI.Xaml; @@ -12,6 +13,12 @@ namespace Microsoft.PowerToys.Settings.UI.Controls { public sealed partial class HotkeySettingsControl : UserControl { + private readonly UIntPtr ignoreKeyEventFlag = (UIntPtr)0x5555; + + private bool _shiftKeyDownOnEntering = false; + + private bool _shiftToggled = false; + public string Header { get; set; } public string Keys { get; set; } @@ -93,7 +100,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls HotkeyTextBox.GettingFocus += HotkeyTextBox_GettingFocus; HotkeyTextBox.LosingFocus += HotkeyTextBox_LosingFocus; HotkeyTextBox.Unloaded += HotkeyTextBox_Unloaded; - hook = new HotkeySettingsControlHook(Hotkey_KeyDown, Hotkey_KeyUp, Hotkey_IsActive); + hook = new HotkeySettingsControlHook(Hotkey_KeyDown, Hotkey_KeyUp, Hotkey_IsActive, FilterAccessibleKeyboardEvents); } private void HotkeyTextBox_Unloaded(object sender, RoutedEventArgs e) @@ -123,6 +130,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls case Windows.System.VirtualKey.Shift: case Windows.System.VirtualKey.LeftShift: case Windows.System.VirtualKey.RightShift: + _shiftToggled = true; internalSettings.Shift = matchValue; break; case Windows.System.VirtualKey.Escape: @@ -135,15 +143,99 @@ namespace Microsoft.PowerToys.Settings.UI.Controls } } + // Function to send a single key event to the system which would be ignored by the hotkey control. + private void SendSingleKeyboardInput(short keyCode, uint keyStatus) + { + NativeKeyboardHelper.INPUT inputShift = new NativeKeyboardHelper.INPUT + { + type = NativeKeyboardHelper.INPUTTYPE.INPUT_KEYBOARD, + data = new NativeKeyboardHelper.InputUnion + { + ki = new NativeKeyboardHelper.KEYBDINPUT + { + wVk = keyCode, + dwFlags = keyStatus, + + // Any keyevent with the extraInfo set to this value will be ignored by the keyboard hook and sent to the system instead. + dwExtraInfo = ignoreKeyEventFlag, + }, + }, + }; + + NativeKeyboardHelper.INPUT[] inputs = new NativeKeyboardHelper.INPUT[] { inputShift }; + + _ = NativeMethods.SendInput(1, inputs, NativeKeyboardHelper.INPUT.Size); + } + + private bool FilterAccessibleKeyboardEvents(int key, UIntPtr extraInfo) + { + // A keyboard event sent with this value in the extra Information field should be ignored by the hook so that it can be captured by the system instead. + if (extraInfo == ignoreKeyEventFlag) + { + return false; + } + + // If the current key press is tab, based on the other keys ignore the key press so as to shift focus out of the hotkey control. + if ((Windows.System.VirtualKey)key == Windows.System.VirtualKey.Tab) + { + // Shift was not pressed while entering and Shift is not pressed while leaving the hotkey control, treat it as a normal tab key press. + if (!internalSettings.Shift && !_shiftKeyDownOnEntering && !internalSettings.Win && !internalSettings.Alt && !internalSettings.Ctrl) + { + return false; + } + + // Shift was not pressed while entering but it was pressed while leaving the hotkey, therefore simulate a shift key press as the system does not know about shift being pressed in the hotkey. + else if (internalSettings.Shift && !_shiftKeyDownOnEntering && !internalSettings.Win && !internalSettings.Alt && !internalSettings.Ctrl) + { + // This is to reset the shift key press within the control as it was not used within the control but rather was used to leave the hotkey. + internalSettings.Shift = false; + + SendSingleKeyboardInput((short)Windows.System.VirtualKey.Shift, (uint)NativeKeyboardHelper.KeyEventF.KeyDown); + + return false; + } + + // Shift was pressed on entering and remained pressed, therefore only ignore the tab key so that it can be passed to the system. + // As the shift key is already assumed to be pressed by the system while it entered the hotkey control, shift would still remain pressed, hence ignoring the tab input would simulate a Shift+Tab key press. + else if (!internalSettings.Shift && _shiftKeyDownOnEntering && !_shiftToggled && !internalSettings.Win && !internalSettings.Alt && !internalSettings.Ctrl) + { + return false; + } + + // Shift was pressed on entering but it was released and later pressed again. + // Ignore the tab key and the system already has the shift key pressed, therefore this would simulate Shift+Tab. + // However, since the last shift key was only used to move out of the control, reset the status of shift within the control. + else if (internalSettings.Shift && _shiftKeyDownOnEntering && _shiftToggled && !internalSettings.Win && !internalSettings.Alt && !internalSettings.Ctrl) + { + internalSettings.Shift = false; + + return false; + } + + // Shift was pressed on entering and was later released. + // The system still has shift in the key pressed status, therefore pass a Shift KeyUp message to the system, to release the shift key, therefore simulating only the Tab key press. + else if (!internalSettings.Shift && _shiftKeyDownOnEntering && _shiftToggled && !internalSettings.Win && !internalSettings.Alt && !internalSettings.Ctrl) + { + SendSingleKeyboardInput((short)Windows.System.VirtualKey.Shift, (uint)NativeKeyboardHelper.KeyEventF.KeyUp); + + return false; + } + } + + return true; + } + private async void Hotkey_KeyDown(int key) { await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { KeyEventHandler(key, true, key, Lib.Utilities.Helper.GetKeyName((uint)key)); - if (internalSettings.Code > 0) + + // Tab and Shift+Tab are accessible keys and should not be displayed in the hotkey control. + if (internalSettings.Code > 0 && !internalSettings.IsAccessibleShortcut()) { + HotkeyTextBox.Text = internalSettings.ToString(); lastValidSettings = internalSettings.Clone(); - HotkeyTextBox.Text = lastValidSettings.ToString(); } }); } @@ -163,6 +255,16 @@ namespace Microsoft.PowerToys.Settings.UI.Controls private void HotkeyTextBox_GettingFocus(object sender, RoutedEventArgs e) { + // Reset the status on entering the hotkey each time. + _shiftKeyDownOnEntering = false; + _shiftToggled = false; + + // To keep track of the shift key, whether it was pressed on entering. + if ((NativeMethods.GetAsyncKeyState((int)Windows.System.VirtualKey.Shift) & 0x8000) != 0) + { + _shiftKeyDownOnEntering = true; + } + _isActive = true; } diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Converters/ModuleEnabledToForegroundConverter.cs b/src/core/Microsoft.PowerToys.Settings.UI/Converters/ModuleEnabledToForegroundConverter.cs index 72cf1129c4..415fa97ba7 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Converters/ModuleEnabledToForegroundConverter.cs +++ b/src/core/Microsoft.PowerToys.Settings.UI/Converters/ModuleEnabledToForegroundConverter.cs @@ -15,15 +15,15 @@ namespace Microsoft.PowerToys.Settings.UI.Converters { private readonly ISettingsUtils settingsUtils = new SettingsUtils(new SystemIOProvider()); + private string selectedTheme = string.Empty; + public object Convert(object value, Type targetType, object parameter, string language) { bool isEnabled = (bool)value; - GeneralSettings generalSettings = settingsUtils.GetSettings(string.Empty); var defaultTheme = new Windows.UI.ViewManagement.UISettings(); var uiTheme = defaultTheme.GetColorValue(Windows.UI.ViewManagement.UIColorType.Background).ToString(); - - string selectedTheme = generalSettings.Theme.ToLower(); + selectedTheme = SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Theme.ToLower(); if (selectedTheme == "dark" || (selectedTheme == "system" && uiTheme == "#FF000000")) { diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Helpers/NativeKeyboardHelper.cs b/src/core/Microsoft.PowerToys.Settings.UI/Helpers/NativeKeyboardHelper.cs new file mode 100644 index 0000000000..9d3961f907 --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Helpers/NativeKeyboardHelper.cs @@ -0,0 +1,86 @@ +// 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.Runtime.InteropServices; + +namespace Microsoft.PowerToys.Settings.UI.Helpers +{ + internal static class NativeKeyboardHelper + { + [StructLayout(LayoutKind.Sequential)] + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching Native Structure")] + internal struct INPUT + { + internal INPUTTYPE type; + internal InputUnion data; + + internal static int Size + { + get { return Marshal.SizeOf(typeof(INPUT)); } + } + } + + [StructLayout(LayoutKind.Explicit)] + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching Native Structure")] + internal struct InputUnion + { + [FieldOffset(0)] + internal MOUSEINPUT mi; + [FieldOffset(0)] + internal KEYBDINPUT ki; + [FieldOffset(0)] + internal HARDWAREINPUT hi; + } + + [StructLayout(LayoutKind.Sequential)] + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching Native Structure")] + internal struct MOUSEINPUT + { + internal int dx; + internal int dy; + internal int mouseData; + internal uint dwFlags; + internal uint time; + internal UIntPtr dwExtraInfo; + } + + [StructLayout(LayoutKind.Sequential)] + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching Native Structure")] + internal struct KEYBDINPUT + { + internal short wVk; + internal short wScan; + internal uint dwFlags; + internal int time; + internal UIntPtr dwExtraInfo; + } + + [StructLayout(LayoutKind.Sequential)] + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching Native Structure")] + internal struct HARDWAREINPUT + { + internal int uMsg; + internal short wParamL; + internal short wParamH; + } + + internal enum INPUTTYPE : uint + { + INPUT_MOUSE = 0, + INPUT_KEYBOARD = 1, + INPUT_HARDWARE = 2, + } + + [Flags] + internal enum KeyEventF + { + KeyDown = 0x0000, + ExtendedKey = 0x0001, + KeyUp = 0x0002, + Unicode = 0x0004, + Scancode = 0x0008, + } + } +} diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Helpers/NativeMethods.cs b/src/core/Microsoft.PowerToys.Settings.UI/Helpers/NativeMethods.cs new file mode 100644 index 0000000000..9ed4efe3b2 --- /dev/null +++ b/src/core/Microsoft.PowerToys.Settings.UI/Helpers/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.Runtime.InteropServices; + +namespace Microsoft.PowerToys.Settings.UI.Helpers +{ + internal static class NativeMethods + { + [DllImport("user32.dll")] + internal static extern uint SendInput(uint nInputs, NativeKeyboardHelper.INPUT[] pInputs, int cbSize); + + [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)] + internal static extern short GetAsyncKeyState(int vKey); + } +} diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj b/src/core/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj index 3cfa080421..b4c8952141 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj +++ b/src/core/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj @@ -10,6 +10,7 @@ Copyright (C) 2020 Microsoft Corp. PowerToys true + x64 @@ -101,6 +102,8 @@ Code + + @@ -185,7 +188,7 @@ 6.1.2 - 2.5.0-prerelease.200812001 + 2.5.0-prerelease.200923002 2.0.1 @@ -200,7 +203,7 @@ - + diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw b/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw index 45e1124cef..774adf76d0 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw +++ b/src/core/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw @@ -759,4 +759,19 @@ Preview Pane + + You need to run as administrator to modify these settings + + + The settings on this page affect all users on the system + + + A reboot may be required for changes to these settings to take effect + + + Example: outlook.exe + + + Example: %1 (%2) + \ No newline at end of file diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Styles/_Thickness.xaml b/src/core/Microsoft.PowerToys.Settings.UI/Styles/_Thickness.xaml index 05a28978f4..e1f3c28650 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Styles/_Thickness.xaml +++ b/src/core/Microsoft.PowerToys.Settings.UI/Styles/_Thickness.xaml @@ -16,13 +16,14 @@ 12, 0, 0, 0 0, 12, 0, 0 0, 12, 0, 12 + 0, 0, 0, 12 12, 0, 12, 0 0, 0, 12, 0 12, 0, 12, 12 12, 12, 12, 12 -10, 12, 0, 0 - + 8, 0, 0, 0 0, 8, 0, 0 diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Views/ColorPickerPage.xaml.cs b/src/core/Microsoft.PowerToys.Settings.UI/Views/ColorPickerPage.xaml.cs index 6e47d7c8cf..929fc9398c 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Views/ColorPickerPage.xaml.cs +++ b/src/core/Microsoft.PowerToys.Settings.UI/Views/ColorPickerPage.xaml.cs @@ -16,7 +16,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views public ColorPickerPage() { var settingsUtils = new SettingsUtils(new SystemIOProvider()); - ViewModel = new ColorPickerViewModel(settingsUtils, ShellPage.SendDefaultIPCMessage); + ViewModel = new ColorPickerViewModel(settingsUtils, SettingsRepository.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage); DataContext = ViewModel; InitializeComponent(); } diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml b/src/core/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml index e4d4cf7c09..8775ea56bd 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml +++ b/src/core/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml @@ -285,7 +285,7 @@ Margin="{StaticResource SmallTopMargin}" Text="{x:Bind Mode=TwoWay, Path=ViewModel.ExcludedApps}" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}" - Width="380" + Width="375" Height="160" HorizontalAlignment="Left" ScrollViewer.VerticalScrollBarVisibility ="Visible" diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml.cs b/src/core/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml.cs index 56b513e517..e022e63fd9 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml.cs +++ b/src/core/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml.cs @@ -17,7 +17,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views { InitializeComponent(); var settingsUtils = new SettingsUtils(new SystemIOProvider()); - ViewModel = new FancyZonesViewModel(settingsUtils, ShellPage.SendDefaultIPCMessage); + ViewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(settingsUtils), SettingsRepository.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage); DataContext = ViewModel; } } diff --git a/src/core/Microsoft.PowerToys.Settings.UI/Views/GeneralPage.xaml b/src/core/Microsoft.PowerToys.Settings.UI/Views/GeneralPage.xaml index 3b400c3406..373f95ec68 100644 --- a/src/core/Microsoft.PowerToys.Settings.UI/Views/GeneralPage.xaml +++ b/src/core/Microsoft.PowerToys.Settings.UI/Views/GeneralPage.xaml @@ -125,7 +125,7 @@ diff --git a/src/modules/launcher/PowerLauncher/SettingsWatcher.cs b/src/modules/launcher/PowerLauncher/SettingsWatcher.cs index 1c1c318c64..347fe9ed10 100644 --- a/src/modules/launcher/PowerLauncher/SettingsWatcher.cs +++ b/src/modules/launcher/PowerLauncher/SettingsWatcher.cs @@ -44,7 +44,7 @@ namespace PowerLauncher { if (!_settingsUtils.SettingsExists(PowerLauncherSettings.ModuleName)) { - Log.Info("|SettingsWatcher.OverloadSettings|PT Run settings.json was missing, creating a new one"); + Log.Info("PT Run settings.json was missing, creating a new one", GetType()); var defaultSettings = new PowerLauncherSettings(); defaultSettings.Save(_settingsUtils); @@ -110,7 +110,7 @@ namespace PowerLauncher if (retryCount > MaxRetries) { retry = false; - Log.Exception($"|SettingsWatcher.OverloadSettings| Failed to Deserialize PowerToys settings, Retrying {e.Message}", e); + Log.Exception($"Failed to Deserialize PowerToys settings, Retrying {e.Message}", e, GetType()); } else { @@ -122,7 +122,7 @@ namespace PowerLauncher if (retryCount > MaxRetries) { retry = false; - Log.Exception($"|SettingsWatcher.OverloadSettings| Failed to Deserialize PowerToys settings, Creating new settings as file could be corrupted {e.Message}", e); + Log.Exception($"Failed to Deserialize PowerToys settings, Creating new settings as file could be corrupted {e.Message}", e, GetType()); // Settings.json could possibly be corrupted. To mitigate this we delete the // current file and replace it with a correct json value. diff --git a/src/modules/launcher/PowerLauncher/Themes/Dark.xaml b/src/modules/launcher/PowerLauncher/Themes/Dark.xaml index 4ef767a475..04ce193d61 100644 --- a/src/modules/launcher/PowerLauncher/Themes/Dark.xaml +++ b/src/modules/launcher/PowerLauncher/Themes/Dark.xaml @@ -3,7 +3,8 @@ xmlns:markup="clr-namespace:MahApps.Metro.Markup;assembly=MahApps.Metro" xmlns:markupWithAssembly="clr-namespace:MahApps.Metro.Markup;assembly=MahApps.Metro" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:options="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" xmlns:system="clr-namespace:System;assembly=System.Runtime" + xmlns:options="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" + xmlns:system="clr-namespace:System;assembly=System.Runtime" mc:Ignorable="options"> @@ -28,7 +29,7 @@ - + #FF767676 diff --git a/src/modules/launcher/PowerLauncher/Themes/HighContrast1.xaml b/src/modules/launcher/PowerLauncher/Themes/HighContrast1.xaml index 9bdaa26dcf..932fb45317 100644 --- a/src/modules/launcher/PowerLauncher/Themes/HighContrast1.xaml +++ b/src/modules/launcher/PowerLauncher/Themes/HighContrast1.xaml @@ -3,7 +3,8 @@ xmlns:markup="clr-namespace:MahApps.Metro.Markup;assembly=MahApps.Metro" xmlns:markupWithAssembly="clr-namespace:MahApps.Metro.Markup;assembly=MahApps.Metro" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:options="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" xmlns:system="clr-namespace:System;assembly=System.Runtime" + xmlns:options="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" + xmlns:system="clr-namespace:System;assembly=System.Runtime" mc:Ignorable="options"> @@ -28,7 +29,7 @@ - + #FF008000 diff --git a/src/modules/launcher/PowerLauncher/Themes/HighContrast2.xaml b/src/modules/launcher/PowerLauncher/Themes/HighContrast2.xaml index ab9d101c01..d7b41010bf 100644 --- a/src/modules/launcher/PowerLauncher/Themes/HighContrast2.xaml +++ b/src/modules/launcher/PowerLauncher/Themes/HighContrast2.xaml @@ -3,7 +3,8 @@ xmlns:markup="clr-namespace:MahApps.Metro.Markup;assembly=MahApps.Metro" xmlns:markupWithAssembly="clr-namespace:MahApps.Metro.Markup;assembly=MahApps.Metro" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:options="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" xmlns:system="clr-namespace:System;assembly=System.Runtime" + xmlns:options="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" + xmlns:system="clr-namespace:System;assembly=System.Runtime" mc:Ignorable="options"> @@ -28,7 +29,7 @@ - + #FF0000ff diff --git a/src/modules/launcher/PowerLauncher/Themes/HighContrastBlack.xaml b/src/modules/launcher/PowerLauncher/Themes/HighContrastBlack.xaml index acccfeb601..5bb7998ee9 100644 --- a/src/modules/launcher/PowerLauncher/Themes/HighContrastBlack.xaml +++ b/src/modules/launcher/PowerLauncher/Themes/HighContrastBlack.xaml @@ -3,7 +3,8 @@ xmlns:markup="clr-namespace:MahApps.Metro.Markup;assembly=MahApps.Metro" xmlns:markupWithAssembly="clr-namespace:MahApps.Metro.Markup;assembly=MahApps.Metro" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:options="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" xmlns:system="clr-namespace:System;assembly=System.Runtime" + xmlns:options="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" + xmlns:system="clr-namespace:System;assembly=System.Runtime" mc:Ignorable="options"> @@ -28,7 +29,8 @@ - + + #FF0ca7b6 #FF0ca7b6 diff --git a/src/modules/launcher/PowerLauncher/Themes/HighContrastWhite.xaml b/src/modules/launcher/PowerLauncher/Themes/HighContrastWhite.xaml index 0d21504e4a..4d7d71774d 100644 --- a/src/modules/launcher/PowerLauncher/Themes/HighContrastWhite.xaml +++ b/src/modules/launcher/PowerLauncher/Themes/HighContrastWhite.xaml @@ -3,7 +3,8 @@ xmlns:markup="clr-namespace:MahApps.Metro.Markup;assembly=MahApps.Metro" xmlns:markupWithAssembly="clr-namespace:MahApps.Metro.Markup;assembly=MahApps.Metro" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:options="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" xmlns:system="clr-namespace:System;assembly=System.Runtime" + xmlns:options="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" + xmlns:system="clr-namespace:System;assembly=System.Runtime" mc:Ignorable="options"> @@ -28,7 +29,8 @@ - + + #FF9a59db #FF9a59db diff --git a/src/modules/launcher/PowerLauncher/Themes/Light.xaml b/src/modules/launcher/PowerLauncher/Themes/Light.xaml index 48f356c207..0fae57d691 100644 --- a/src/modules/launcher/PowerLauncher/Themes/Light.xaml +++ b/src/modules/launcher/PowerLauncher/Themes/Light.xaml @@ -3,7 +3,8 @@ xmlns:markup="clr-namespace:MahApps.Metro.Markup;assembly=MahApps.Metro" xmlns:markupWithAssembly="clr-namespace:MahApps.Metro.Markup;assembly=MahApps.Metro" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:options="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" xmlns:system="clr-namespace:System;assembly=System.Runtime" + xmlns:options="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" + xmlns:system="clr-namespace:System;assembly=System.Runtime" mc:Ignorable="options"> @@ -28,7 +29,8 @@ - + + #FFa0a0a0 #FF747474 diff --git a/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs b/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs index 028e39b3e6..195a51a23b 100644 --- a/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs +++ b/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs @@ -500,6 +500,9 @@ namespace PowerLauncher.ViewModel { Thread.Sleep(20); + // Keep track of total number of results for telemetry + var numResults = 0; + // Contains all the plugins for which this raw query is valid var plugins = pluginQueryPairs.Keys.ToList(); @@ -535,8 +538,9 @@ namespace PowerLauncher.ViewModel } currentCancellationToken.ThrowIfCancellationRequested(); + numResults = Results.Results.Count; Results.Sort(); - Results.SelectedItem = Results.Results[0]; + Results.SelectedItem = Results.Results.FirstOrDefault(); } } @@ -573,8 +577,9 @@ namespace PowerLauncher.ViewModel UpdateResultView(results, queryText, currentCancellationToken); currentCancellationToken.ThrowIfCancellationRequested(); + numResults = Results.Results.Count; Results.Sort(); - Results.SelectedItem = Results.Results[0]; + Results.SelectedItem = Results.Results.FirstOrDefault(); } } @@ -598,7 +603,7 @@ namespace PowerLauncher.ViewModel var queryEvent = new LauncherQueryEvent() { QueryTimeMs = queryTimer.ElapsedMilliseconds, - NumResults = Results.Results.Count, + NumResults = numResults, QueryLength = queryText.Length, }; PowerToysTelemetry.Log.WriteEvent(queryEvent); @@ -611,7 +616,13 @@ namespace PowerLauncher.ViewModel _currentQuery = _emptyQuery; Results.SelectedItem = null; Results.Visibility = Visibility.Hidden; - Results.Clear(); + Task.Run(() => + { + lock (_addResultsLock) + { + Results.Clear(); + } + }); } } @@ -860,13 +871,25 @@ namespace PowerLauncher.ViewModel } } + public static bool ShouldAutoCompleteTextBeEmpty(string queryText, string autoCompleteText) + { + if (string.IsNullOrEmpty(autoCompleteText)) + { + return false; + } + else + { + return string.IsNullOrEmpty(queryText) || autoCompleteText.IndexOf(queryText, StringComparison.Ordinal) != 0; + } + } + public static string GetAutoCompleteText(int index, string input, string query) { if (!string.IsNullOrEmpty(input) && !string.IsNullOrEmpty(query)) { if (index == 0) { - if (input.IndexOf(query, StringComparison.InvariantCultureIgnoreCase) == 0) + if (input.IndexOf(query, StringComparison.OrdinalIgnoreCase) == 0) { // Use the same case as the input query for the matched portion of the string return query + input.Substring(query.Length); @@ -883,7 +906,7 @@ namespace PowerLauncher.ViewModel { if (index == 0 && !string.IsNullOrEmpty(query)) { - if (input.IndexOf(query, StringComparison.InvariantCultureIgnoreCase) == 0) + if (input.IndexOf(query, StringComparison.OrdinalIgnoreCase) == 0) { return query + input.Substring(query.Length); } diff --git a/src/modules/launcher/PowerLauncher/ViewModel/ResultViewModel.cs b/src/modules/launcher/PowerLauncher/ViewModel/ResultViewModel.cs index e07717a2c5..ba2e323dec 100644 --- a/src/modules/launcher/PowerLauncher/ViewModel/ResultViewModel.cs +++ b/src/modules/launcher/PowerLauncher/ViewModel/ResultViewModel.cs @@ -189,7 +189,7 @@ namespace PowerLauncher.ViewModel catch (Exception e) #pragma warning restore CA1031 // Do not catch general exception types { - Log.Exception($"|ResultViewModel.Image|IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e); + Log.Exception($"IcoPath is empty and exception when calling Icon() for result <{Result.Title}> of plugin <{Result.PluginDirectory}>", e, GetType()); imagePath = ImageLoader.ErrorIconPath; } } diff --git a/src/modules/launcher/PowerLauncher/ViewModel/ResultsViewModel.cs b/src/modules/launcher/PowerLauncher/ViewModel/ResultsViewModel.cs index b93aa35049..4b56d17b1c 100644 --- a/src/modules/launcher/PowerLauncher/ViewModel/ResultsViewModel.cs +++ b/src/modules/launcher/PowerLauncher/ViewModel/ResultsViewModel.cs @@ -174,7 +174,7 @@ namespace PowerLauncher.ViewModel { // Tabbing backwards should highlight the last item of the previous row SelectPrevResult(); - SelectedItem.SelectLastContextButton(); + SelectedItem?.SelectLastContextButton(); } } diff --git a/src/modules/launcher/PowerLauncher/loc/cs/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl b/src/modules/launcher/PowerLauncher/loc/cs/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..decc7d3432 --- /dev/null +++ b/src/modules/launcher/PowerLauncher/loc/cs/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/loc/de/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl b/src/modules/launcher/PowerLauncher/loc/de/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..b82019b99a --- /dev/null +++ b/src/modules/launcher/PowerLauncher/loc/de/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/loc/es/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl b/src/modules/launcher/PowerLauncher/loc/es/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..b7341e76dc --- /dev/null +++ b/src/modules/launcher/PowerLauncher/loc/es/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/loc/fr/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl b/src/modules/launcher/PowerLauncher/loc/fr/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..8b5555ebb6 --- /dev/null +++ b/src/modules/launcher/PowerLauncher/loc/fr/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/loc/hu/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl b/src/modules/launcher/PowerLauncher/loc/hu/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..cb8e8e6bd5 --- /dev/null +++ b/src/modules/launcher/PowerLauncher/loc/hu/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/loc/it/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl b/src/modules/launcher/PowerLauncher/loc/it/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..a612c156e0 --- /dev/null +++ b/src/modules/launcher/PowerLauncher/loc/it/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/loc/ja/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl b/src/modules/launcher/PowerLauncher/loc/ja/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..d22a91b524 --- /dev/null +++ b/src/modules/launcher/PowerLauncher/loc/ja/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/loc/ko/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl b/src/modules/launcher/PowerLauncher/loc/ko/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..3abeb17147 --- /dev/null +++ b/src/modules/launcher/PowerLauncher/loc/ko/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/loc/nl/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl b/src/modules/launcher/PowerLauncher/loc/nl/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..f1ce51dbbb --- /dev/null +++ b/src/modules/launcher/PowerLauncher/loc/nl/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/loc/pl/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl b/src/modules/launcher/PowerLauncher/loc/pl/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..be478d3c8f --- /dev/null +++ b/src/modules/launcher/PowerLauncher/loc/pl/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/loc/pt-BR/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl b/src/modules/launcher/PowerLauncher/loc/pt-BR/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..6e2ad15a0a --- /dev/null +++ b/src/modules/launcher/PowerLauncher/loc/pt-BR/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/loc/pt-PT/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl b/src/modules/launcher/PowerLauncher/loc/pt-PT/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..c4b586d739 --- /dev/null +++ b/src/modules/launcher/PowerLauncher/loc/pt-PT/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/loc/ru/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl b/src/modules/launcher/PowerLauncher/loc/ru/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..54cd57d146 --- /dev/null +++ b/src/modules/launcher/PowerLauncher/loc/ru/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/loc/sv/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl b/src/modules/launcher/PowerLauncher/loc/sv/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..841cfdefe9 --- /dev/null +++ b/src/modules/launcher/PowerLauncher/loc/sv/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/loc/tr/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl b/src/modules/launcher/PowerLauncher/loc/tr/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..27c4c0cc71 --- /dev/null +++ b/src/modules/launcher/PowerLauncher/loc/tr/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/loc/zh-Hans/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl b/src/modules/launcher/PowerLauncher/loc/zh-Hans/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..211ba2ca61 --- /dev/null +++ b/src/modules/launcher/PowerLauncher/loc/zh-Hans/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/loc/zh-Hant/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl b/src/modules/launcher/PowerLauncher/loc/zh-Hant/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..9a4dfa1529 --- /dev/null +++ b/src/modules/launcher/PowerLauncher/loc/zh-Hant/src/modules/launcher/PowerLauncher/Properties/Resources.resx.lcl @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/launcher/Wox.Core/Plugin/ExecutablePlugin.cs b/src/modules/launcher/Wox.Core/Plugin/ExecutablePlugin.cs deleted file mode 100644 index 0a595a4202..0000000000 --- a/src/modules/launcher/Wox.Core/Plugin/ExecutablePlugin.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Diagnostics; -using Wox.Plugin; - -namespace Wox.Core.Plugin -{ - internal class ExecutablePlugin : JsonRPCPlugin - { - private readonly ProcessStartInfo _startInfo; - - public override string SupportedLanguage { get; set; } = AllowedLanguage.Executable; - - public ExecutablePlugin(string filename) - { - _startInfo = new ProcessStartInfo - { - FileName = filename, - UseShellExecute = false, - CreateNoWindow = true, - RedirectStandardOutput = true, - RedirectStandardError = true, - }; - } - - protected override string ExecuteQuery(Query query) - { - JsonRPCServerRequestModel request = new JsonRPCServerRequestModel - { - Method = "query", - Parameters = new object[] { query.Search }, - }; - - _startInfo.Arguments = $"\"{request}\""; - - return Execute(_startInfo); - } - - protected override string ExecuteCallback(JsonRPCRequestModel rpcRequest) - { - _startInfo.Arguments = $"\"{rpcRequest}\""; - return Execute(_startInfo); - } - - protected override string ExecuteContextMenu(Result selectedResult) - { - JsonRPCServerRequestModel request = new JsonRPCServerRequestModel - { - Method = "contextmenu", - Parameters = new object[] { selectedResult.ContextData }, - }; - - _startInfo.Arguments = $"\"{request}\""; - - return Execute(_startInfo); - } - } -} diff --git a/src/modules/launcher/Wox.Core/Plugin/JsonRPCClientRequestModel.cs b/src/modules/launcher/Wox.Core/Plugin/JsonRPCClientRequestModel.cs deleted file mode 100644 index 22cdb05b17..0000000000 --- a/src/modules/launcher/Wox.Core/Plugin/JsonRPCClientRequestModel.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -/* We basically follow the Json-RPC 2.0 spec (http://www.jsonrpc.org/specification) to invoke methods between Wox and other plugins, - * like python or other self-execute program. But, we added additional infos (proxy and so on) into rpc request. Also, we didn't use the - * "id" and "jsonrpc" in the request, since it's not so useful in our request model. - * - * When execute a query: - * Wox -------JsonRPCServerRequestModel--------> client - * Wox <------JsonRPCQueryResponseModel--------- client - * - * When execute a action (which mean user select an item in reulst item): - * Wox -------JsonRPCServerRequestModel--------> client - * Wox <------JsonRPCResponseModel-------------- client - * - */ - -namespace Wox.Core.Plugin -{ - /// - /// Json RPC Request(in query response) that client sent to Wox - /// - public class JsonRPCClientRequestModel : JsonRPCRequestModel - { - public bool DontHideAfterAction { get; set; } - - public override string ToString() - { - string rpc = base.ToString(); - return rpc + "}"; - } - } -} diff --git a/src/modules/launcher/Wox.Core/Plugin/JsonRPCErrorModel.cs b/src/modules/launcher/Wox.Core/Plugin/JsonRPCErrorModel.cs deleted file mode 100644 index 7130d1e604..0000000000 --- a/src/modules/launcher/Wox.Core/Plugin/JsonRPCErrorModel.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -/* We basically follow the Json-RPC 2.0 spec (http://www.jsonrpc.org/specification) to invoke methods between Wox and other plugins, - * like python or other self-execute program. But, we added additional infos (proxy and so on) into rpc request. Also, we didn't use the - * "id" and "jsonrpc" in the request, since it's not so useful in our request model. - * - * When execute a query: - * Wox -------JsonRPCServerRequestModel--------> client - * Wox <------JsonRPCQueryResponseModel--------- client - * - * When execute a action (which mean user select an item in reulst item): - * Wox -------JsonRPCServerRequestModel--------> client - * Wox <------JsonRPCResponseModel-------------- client - * - */ - -namespace Wox.Core.Plugin -{ - public class JsonRPCErrorModel - { - public int Code { get; set; } - - public string Message { get; set; } - - public string Data { get; set; } - } -} diff --git a/src/modules/launcher/Wox.Core/Plugin/JsonRPCModelBase.cs b/src/modules/launcher/Wox.Core/Plugin/JsonRPCModelBase.cs deleted file mode 100644 index 9040a345c1..0000000000 --- a/src/modules/launcher/Wox.Core/Plugin/JsonRPCModelBase.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -/* We basically follow the Json-RPC 2.0 spec (http://www.jsonrpc.org/specification) to invoke methods between Wox and other plugins, - * like python or other self-execute program. But, we added additional infos (proxy and so on) into rpc request. Also, we didn't use the - * "id" and "jsonrpc" in the request, since it's not so useful in our request model. - * - * When execute a query: - * Wox -------JsonRPCServerRequestModel--------> client - * Wox <------JsonRPCQueryResponseModel--------- client - * - * When execute a action (which mean user select an item in reulst item): - * Wox -------JsonRPCServerRequestModel--------> client - * Wox <------JsonRPCResponseModel-------------- client - * - */ - -namespace Wox.Core.Plugin -{ - public class JsonRPCModelBase - { - public int Id { get; set; } - } -} diff --git a/src/modules/launcher/Wox.Core/Plugin/JsonRPCPlugin.cs b/src/modules/launcher/Wox.Core/Plugin/JsonRPCPlugin.cs deleted file mode 100644 index 152e501203..0000000000 --- a/src/modules/launcher/Wox.Core/Plugin/JsonRPCPlugin.cs +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Reflection; -using System.Windows.Forms; -using Newtonsoft.Json; -using Wox.Infrastructure.Logger; -using Wox.Plugin; - -namespace Wox.Core.Plugin -{ - /// - /// Represent the plugin that using JsonPRC - /// every JsonRPC plugin should has its own plugin instance - /// - internal abstract class JsonRPCPlugin : IPlugin, IContextMenu - { - protected PluginInitContext Context { get; set; } - - public const string JsonRPC = "JsonRPC"; - - /// - /// Gets or sets the language this JsonRPCPlugin support - /// - public abstract string SupportedLanguage { get; set; } - - protected abstract string ExecuteQuery(Query query); - - protected abstract string ExecuteCallback(JsonRPCRequestModel rpcRequest); - - protected abstract string ExecuteContextMenu(Result selectedResult); - - public List Query(Query query) - { - string output = ExecuteQuery(query); - try - { - return DeserializedResult(output); - } - catch (Exception e) - { - Log.Exception($"|JsonRPCPlugin.Query|Exception when query <{query}>", e); - return null; - } - } - - public List LoadContextMenus(Result selectedResult) - { - string output = ExecuteContextMenu(selectedResult); - try - { - // This should not hit. If it does it's because Wox shares the same interface for querying context menu items as well as search results. In this case please file a bug. - // To my knowledge we aren't supporting this JSonRPC commands in Launcher, and am not able to repro this, but I will leave this here for the time being in case I'm proven wrong. - // We should remove this, or identify and test officially supported use cases and Deserialize this properly. - // return DeserializedResult(output); - throw new NotImplementedException(); - } - catch (Exception e) - { - Log.Exception($"|JsonRPCPlugin.LoadContextMenus| THIS IS A BUG - Exception on result <{selectedResult}>", e); - return null; - } - } - - private List DeserializedResult(string output) - { - if (!string.IsNullOrEmpty(output)) - { - List results = new List(); - - JsonRPCQueryResponseModel queryResponseModel = JsonConvert.DeserializeObject(output); - if (queryResponseModel.Result == null) - { - return null; - } - - foreach (JsonRPCResult result in queryResponseModel.Result) - { - JsonRPCResult result1 = result; - result.Action = c => - { - if (result1.JsonRPCAction == null) - { - return false; - } - - if (!string.IsNullOrEmpty(result1.JsonRPCAction.Method)) - { - if (result1.JsonRPCAction.Method.StartsWith("Wox.")) - { - ExecuteWoxAPI(result1.JsonRPCAction.Method.Substring(4), result1.JsonRPCAction.Parameters); - } - else - { - string actionResponse = ExecuteCallback(result1.JsonRPCAction); - JsonRPCRequestModel jsonRpcRequestModel = JsonConvert.DeserializeObject(actionResponse); - if (jsonRpcRequestModel != null - && !string.IsNullOrEmpty(jsonRpcRequestModel.Method) - && jsonRpcRequestModel.Method.StartsWith("Wox.")) - { - ExecuteWoxAPI(jsonRpcRequestModel.Method.Substring(4), jsonRpcRequestModel.Parameters); - } - } - } - - return !result1.JsonRPCAction.DontHideAfterAction; - }; - results.Add(result); - } - - return results; - } - else - { - return null; - } - } - - private void ExecuteWoxAPI(string method, object[] parameters) - { - MethodInfo methodInfo = PluginManager.API.GetType().GetMethod(method); - if (methodInfo != null) - { - try - { - methodInfo.Invoke(PluginManager.API, parameters); - } - catch (Exception) - { -#if DEBUG - { - throw; - } -#endif - } - } - } - - /// - /// Execute external program and return the output - /// - /// file to execute - /// args to pass in to that exe - /// results - protected string Execute(string fileName, string arguments) - { - ProcessStartInfo start = new ProcessStartInfo - { - FileName = fileName, - Arguments = arguments, - UseShellExecute = false, - CreateNoWindow = true, - RedirectStandardOutput = true, - RedirectStandardError = true, - }; - - return Execute(start); - } - - protected string Execute(ProcessStartInfo startInfo) - { - try - { - using (var process = Process.Start(startInfo)) - { - if (process != null) - { - using (var standardOutput = process.StandardOutput) - { - var result = standardOutput.ReadToEnd(); - if (string.IsNullOrEmpty(result)) - { - using (var standardError = process.StandardError) - { - var error = standardError.ReadToEnd(); - if (!string.IsNullOrEmpty(error)) - { - Log.Error($"|JsonRPCPlugin.Execute|{error}"); - return string.Empty; - } - else - { - Log.Error("|JsonRPCPlugin.Execute|Empty standard output and standard error."); - return string.Empty; - } - } - } - else if (result.StartsWith("DEBUG:")) - { - MessageBox.Show(new Form { TopMost = true }, result.Substring(6)); - return string.Empty; - } - else - { - return result; - } - } - } - else - { - Log.Error("|JsonRPCPlugin.Execute|Can't start new process"); - return string.Empty; - } - } - } - catch (Exception e) - { - Log.Exception($"|JsonRPCPlugin.Execute|Exception for filename <{startInfo.FileName}> with argument <{startInfo.Arguments}>", e); - return string.Empty; - } - } - - public void Init(PluginInitContext ctx) - { - Context = ctx; - } - } -} diff --git a/src/modules/launcher/Wox.Core/Plugin/JsonRPCQueryResponseModel.cs b/src/modules/launcher/Wox.Core/Plugin/JsonRPCQueryResponseModel.cs deleted file mode 100644 index 9ded1a4fcf..0000000000 --- a/src/modules/launcher/Wox.Core/Plugin/JsonRPCQueryResponseModel.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -/* We basically follow the Json-RPC 2.0 spec (http://www.jsonrpc.org/specification) to invoke methods between Wox and other plugins, - * like python or other self-execute program. But, we added additional infos (proxy and so on) into rpc request. Also, we didn't use the - * "id" and "jsonrpc" in the request, since it's not so useful in our request model. - * - * When execute a query: - * Wox -------JsonRPCServerRequestModel--------> client - * Wox <------JsonRPCQueryResponseModel--------- client - * - * When execute a action (which mean user select an item in reulst item): - * Wox -------JsonRPCServerRequestModel--------> client - * Wox <------JsonRPCResponseModel-------------- client - * - */ - -using System.Collections.Generic; - -namespace Wox.Core.Plugin -{ - public class JsonRPCQueryResponseModel : JsonRPCResponseModel - { - public new List Result { get; set; } - } -} diff --git a/src/modules/launcher/Wox.Core/Plugin/JsonRPCRequestModel.cs b/src/modules/launcher/Wox.Core/Plugin/JsonRPCRequestModel.cs deleted file mode 100644 index 967cd1930b..0000000000 --- a/src/modules/launcher/Wox.Core/Plugin/JsonRPCRequestModel.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -/* We basically follow the Json-RPC 2.0 spec (http://www.jsonrpc.org/specification) to invoke methods between Wox and other plugins, - * like python or other self-execute program. But, we added additional infos (proxy and so on) into rpc request. Also, we didn't use the - * "id" and "jsonrpc" in the request, since it's not so useful in our request model. - * - * When execute a query: - * Wox -------JsonRPCServerRequestModel--------> client - * Wox <------JsonRPCQueryResponseModel--------- client - * - * When execute a action (which mean user select an item in reulst item): - * Wox -------JsonRPCServerRequestModel--------> client - * Wox <------JsonRPCResponseModel-------------- client - * - */ - -using System.Linq; - -namespace Wox.Core.Plugin -{ - public class JsonRPCRequestModel : JsonRPCModelBase - { - public string Method { get; set; } - - public object[] Parameters { get; set; } - - public override string ToString() - { - string rpc = string.Empty; - if (Parameters != null && Parameters.Length > 0) - { - string parameters = Parameters.Aggregate("[", (current, o) => current + (GetParameterByType(o) + ",")); - parameters = parameters.Substring(0, parameters.Length - 1) + "]"; - rpc = string.Format(@"{{\""method\"":\""{0}\"",\""parameters\"":{1}", Method, parameters); - } - else - { - rpc = string.Format(@"{{\""method\"":\""{0}\"",\""parameters\"":[]", Method); - } - - return rpc; - } - - private string GetParameterByType(object parameter) - { - if (parameter == null) - { - return "null"; - } - - if (parameter is string) - { - return string.Format(@"\""{0}\""", ReplaceEscapes(parameter.ToString())); - } - - if (parameter is int || parameter is float || parameter is double) - { - return string.Format(@"{0}", parameter); - } - - if (parameter is bool) - { - return string.Format(@"{0}", parameter.ToString().ToLower()); - } - - return parameter.ToString(); - } - - private string ReplaceEscapes(string str) - { - return str.Replace(@"\", @"\\") // Escapes in ProcessStartInfo - .Replace(@"\", @"\\") // Escapes itself when passed to client - .Replace(@"""", @"\\"""""); - } - } -} diff --git a/src/modules/launcher/Wox.Core/Plugin/JsonRPCResponseModel.cs b/src/modules/launcher/Wox.Core/Plugin/JsonRPCResponseModel.cs deleted file mode 100644 index a6b5723a2b..0000000000 --- a/src/modules/launcher/Wox.Core/Plugin/JsonRPCResponseModel.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -/* We basically follow the Json-RPC 2.0 spec (http://www.jsonrpc.org/specification) to invoke methods between Wox and other plugins, - * like python or other self-execute program. But, we added additional infos (proxy and so on) into rpc request. Also, we didn't use the - * "id" and "jsonrpc" in the request, since it's not so useful in our request model. - * - * When execute a query: - * Wox -------JsonRPCServerRequestModel--------> client - * Wox <------JsonRPCQueryResponseModel--------- client - * - * When execute a action (which mean user select an item in reulst item): - * Wox -------JsonRPCServerRequestModel--------> client - * Wox <------JsonRPCResponseModel-------------- client - * - */ - -namespace Wox.Core.Plugin -{ - public class JsonRPCResponseModel : JsonRPCModelBase - { - public string Result { get; set; } - - public JsonRPCErrorModel Error { get; set; } - } -} diff --git a/src/modules/launcher/Wox.Core/Plugin/JsonRPCResult.cs b/src/modules/launcher/Wox.Core/Plugin/JsonRPCResult.cs deleted file mode 100644 index a9eb036957..0000000000 --- a/src/modules/launcher/Wox.Core/Plugin/JsonRPCResult.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -/* We basically follow the Json-RPC 2.0 spec (http://www.jsonrpc.org/specification) to invoke methods between Wox and other plugins, - * like python or other self-execute program. But, we added additional infos (proxy and so on) into rpc request. Also, we didn't use the - * "id" and "jsonrpc" in the request, since it's not so useful in our request model. - * - * When execute a query: - * Wox -------JsonRPCServerRequestModel--------> client - * Wox <------JsonRPCQueryResponseModel--------- client - * - * When execute a action (which mean user select an item in reulst item): - * Wox -------JsonRPCServerRequestModel--------> client - * Wox <------JsonRPCResponseModel-------------- client - * - */ - -using Wox.Plugin; - -namespace Wox.Core.Plugin -{ - /// - /// Represent the json-rpc result item that client send to Wox - /// Typically, we will send back this request model to client after user select the result item - /// But if the request method starts with "Wox.", we will invoke the public APIs we expose. - /// - public class JsonRPCResult : Result - { - public JsonRPCClientRequestModel JsonRPCAction { get; set; } - } -} diff --git a/src/modules/launcher/Wox.Core/Plugin/JsonRPCServerRequestModel.cs b/src/modules/launcher/Wox.Core/Plugin/JsonRPCServerRequestModel.cs deleted file mode 100644 index a7d7f57b95..0000000000 --- a/src/modules/launcher/Wox.Core/Plugin/JsonRPCServerRequestModel.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -/* We basically follow the Json-RPC 2.0 spec (http://www.jsonrpc.org/specification) to invoke methods between Wox and other plugins, - * like python or other self-execute program. But, we added additional infos (proxy and so on) into rpc request. Also, we didn't use the - * "id" and "jsonrpc" in the request, since it's not so useful in our request model. - * - * When execute a query: - * Wox -------JsonRPCServerRequestModel--------> client - * Wox <------JsonRPCQueryResponseModel--------- client - * - * When execute a action (which mean user select an item in reulst item): - * Wox -------JsonRPCServerRequestModel--------> client - * Wox <------JsonRPCResponseModel-------------- client - * - */ - -namespace Wox.Core.Plugin -{ - /// - /// Json RPC Request that Wox sent to client - /// - public class JsonRPCServerRequestModel : JsonRPCRequestModel - { - public override string ToString() - { - string rpc = base.ToString(); - return rpc + "}"; - } - } -} diff --git a/src/modules/launcher/Wox.Core/Plugin/PluginConfig.cs b/src/modules/launcher/Wox.Core/Plugin/PluginConfig.cs index 9f86fe0a7b..11094c0a4e 100644 --- a/src/modules/launcher/Wox.Core/Plugin/PluginConfig.cs +++ b/src/modules/launcher/Wox.Core/Plugin/PluginConfig.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using Newtonsoft.Json; using Wox.Infrastructure.Logger; using Wox.Plugin; @@ -27,9 +28,11 @@ namespace Wox.Core.Plugin PluginMetadatas.Clear(); var directories = pluginDirectories.SelectMany(Directory.GetDirectories); ParsePluginConfigs(directories); + return PluginMetadatas; } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "All exception information is being logged")] private static void ParsePluginConfigs(IEnumerable directories) { // todo use linq when diable plugin is implemented since parallel.foreach + list is not thread saft @@ -43,7 +46,7 @@ namespace Wox.Core.Plugin } catch (Exception e) { - Log.Exception($"|PluginConfig.ParsePLuginConfigs|Can't delete <{directory}>", e); + Log.Exception($"Can't delete <{directory}>", e, MethodBase.GetCurrentMethod().DeclaringType); } } else @@ -57,12 +60,14 @@ namespace Wox.Core.Plugin } } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "All exception information is being logged")] private static PluginMetadata GetPluginMetadata(string pluginDirectory) { string configPath = Path.Combine(pluginDirectory, PluginConfigName); if (!File.Exists(configPath)) { - Log.Error($"|PluginConfig.GetPluginMetadata|Didn't find config file <{configPath}>"); + Log.Error($"Didn't find config file <{configPath}>", MethodBase.GetCurrentMethod().DeclaringType); + return null; } @@ -80,19 +85,19 @@ namespace Wox.Core.Plugin } catch (Exception e) { - Log.Exception($"|PluginConfig.GetPluginMetadata|invalid json for config <{configPath}>", e); + Log.Exception($"|PluginConfig.GetPluginMetadata|invalid json for config <{configPath}>", e, MethodBase.GetCurrentMethod().DeclaringType); return null; } if (!AllowedLanguage.IsAllowed(metadata.Language)) { - Log.Error($"|PluginConfig.GetPluginMetadata|Invalid language <{metadata.Language}> for config <{configPath}>"); + Log.Error($"|PluginConfig.GetPluginMetadata|Invalid language <{metadata.Language}> for config <{configPath}>", MethodBase.GetCurrentMethod().DeclaringType); return null; } if (!File.Exists(metadata.ExecuteFilePath)) { - Log.Error($"|PluginConfig.GetPluginMetadata|execute file path didn't exist <{metadata.ExecuteFilePath}> for config <{configPath}"); + Log.Error($"|PluginConfig.GetPluginMetadata|execute file path didn't exist <{metadata.ExecuteFilePath}> for config <{configPath}", MethodBase.GetCurrentMethod().DeclaringType); return null; } diff --git a/src/modules/launcher/Wox.Core/Plugin/PluginInstaller.cs b/src/modules/launcher/Wox.Core/Plugin/PluginInstaller.cs index 1c1a316066..e70745189d 100644 --- a/src/modules/launcher/Wox.Core/Plugin/PluginInstaller.cs +++ b/src/modules/launcher/Wox.Core/Plugin/PluginInstaller.cs @@ -4,9 +4,11 @@ using System; using System.IO; +using System.Reflection; using System.Windows; using ICSharpCode.SharpZipLib.Zip; using Newtonsoft.Json; +using Wox.Infrastructure.Logger; using Wox.Plugin; namespace Wox.Core.Plugin @@ -41,15 +43,16 @@ namespace Wox.Core.Plugin string pluginFolderPath = Infrastructure.Constant.PluginsDirectory; + // Using Ordinal since this is part of a path string newPluginName = plugin.Name - .Replace("/", "_") - .Replace("\\", "_") - .Replace(":", "_") - .Replace("<", "_") - .Replace(">", "_") - .Replace("?", "_") - .Replace("*", "_") - .Replace("|", "_") + .Replace("/", "_", StringComparison.Ordinal) + .Replace("\\", "_", StringComparison.Ordinal) + .Replace(":", "_", StringComparison.Ordinal) + .Replace("<", "_", StringComparison.Ordinal) + .Replace(">", "_", StringComparison.Ordinal) + .Replace("?", "_", StringComparison.Ordinal) + .Replace("*", "_", StringComparison.Ordinal) + .Replace("|", "_", StringComparison.Ordinal) + "-" + Guid.NewGuid(); string newPluginPath = Path.Combine(pluginFolderPath, newPluginName); string content = $"Do you want to install following plugin?{Environment.NewLine}{Environment.NewLine}" + @@ -95,6 +98,7 @@ namespace Wox.Core.Plugin } } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "All exception information is being logged")] private static PluginMetadata GetMetadataFromJson(string pluginDirectory) { string configPath = Path.Combine(pluginDirectory, "plugin.json"); @@ -110,9 +114,10 @@ namespace Wox.Core.Plugin metadata = JsonConvert.DeserializeObject(File.ReadAllText(configPath)); metadata.PluginDirectory = pluginDirectory; } - catch (Exception) + catch (Exception e) { string error = $"Parse plugin config {configPath} failed: json format is not valid"; + Log.Exception(error, e, MethodBase.GetCurrentMethod().DeclaringType); #if DEBUG { throw new Exception(error); @@ -157,12 +162,13 @@ namespace Wox.Core.Plugin /// overwrite private static void UnZip(string zippedFile, string strDirectory, bool overWrite) { - if (strDirectory == string.Empty) + if (string.IsNullOrEmpty(strDirectory)) { strDirectory = Directory.GetCurrentDirectory(); } - if (!strDirectory.EndsWith("\\")) + // Using Ordinal since this is a path + if (!strDirectory.EndsWith("\\", StringComparison.Ordinal)) { strDirectory += "\\"; } @@ -177,7 +183,7 @@ namespace Wox.Core.Plugin string pathToZip = string.Empty; pathToZip = theEntry.Name; - if (pathToZip != string.Empty) + if (!string.IsNullOrEmpty(pathToZip)) { directoryName = Path.GetDirectoryName(pathToZip) + "\\"; } @@ -186,7 +192,7 @@ namespace Wox.Core.Plugin Directory.CreateDirectory(strDirectory + directoryName); - if (fileName != string.Empty) + if (!string.IsNullOrEmpty(fileName)) { if ((File.Exists(strDirectory + directoryName + fileName) && overWrite) || (!File.Exists(strDirectory + directoryName + fileName))) { diff --git a/src/modules/launcher/Wox.Core/Plugin/PluginManager.cs b/src/modules/launcher/Wox.Core/Plugin/PluginManager.cs index 1e1d8a446e..d225090f3d 100644 --- a/src/modules/launcher/Wox.Core/Plugin/PluginManager.cs +++ b/src/modules/launcher/Wox.Core/Plugin/PluginManager.cs @@ -5,8 +5,10 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using Wox.Infrastructure; using Wox.Infrastructure.Logger; @@ -26,7 +28,7 @@ namespace Wox.Core.Plugin /// /// Gets directories that will hold Wox plugin directory /// - public static List AllPlugins { get; private set; } + public static List AllPlugins { get; private set; } = new List(); public static IPublicAPI API { get; private set; } @@ -78,17 +80,18 @@ namespace Wox.Core.Plugin public static void LoadPlugins(PluginSettings settings) { _metadatas = PluginConfig.Parse(Directories); - Settings = settings; + Settings = settings ?? throw new ArgumentNullException(nameof(settings)); Settings.UpdatePluginSettings(_metadatas); - AllPlugins = PluginsLoader.Plugins(_metadatas, Settings); + AllPlugins = PluginsLoader.Plugins(_metadatas); } /// /// Call initialize for all plugins /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "All exception information is being logged")] public static void InitializePlugins(IPublicAPI api) { - API = api; + API = api ?? throw new ArgumentNullException(nameof(api)); var failedPlugins = new ConcurrentQueue(); Parallel.ForEach(AllPlugins, pair => { @@ -103,11 +106,11 @@ namespace Wox.Core.Plugin }); }); pair.Metadata.InitTime += milliseconds; - Log.Info($"|PluginManager.InitializePlugins|Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>"); + Log.Info($"Total init cost for <{pair.Metadata.Name}> is <{pair.Metadata.InitTime}ms>", MethodBase.GetCurrentMethod().DeclaringType); } catch (Exception e) { - Log.Exception(nameof(PluginManager), $"Fail to Init plugin: {pair.Metadata.Name}", e); + Log.Exception($"Fail to Init plugin: {pair.Metadata.Name}", e, MethodBase.GetCurrentMethod().DeclaringType); pair.Metadata.Disabled = true; failedPlugins.Enqueue(pair); } @@ -141,6 +144,11 @@ namespace Wox.Core.Plugin public static List ValidPluginsForQuery(Query query) { + if (query == null) + { + throw new ArgumentNullException(nameof(query)); + } + if (NonGlobalPlugins.ContainsKey(query.ActionKeyword)) { var plugin = NonGlobalPlugins[query.ActionKeyword]; @@ -152,8 +160,14 @@ namespace Wox.Core.Plugin } } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "All exception information is being logged")] public static List QueryForPlugin(PluginPair pair, Query query, bool delayedExecution = false) { + if (pair == null) + { + throw new ArgumentNullException(nameof(pair)); + } + try { List results = null; @@ -175,13 +189,16 @@ namespace Wox.Core.Plugin UpdateResultWithActionKeyword(results, query); } }); + metadata.QueryCount += 1; metadata.AvgQueryTime = metadata.QueryCount == 1 ? milliseconds : (metadata.AvgQueryTime + milliseconds) / 2; + return results; } catch (Exception e) { - Log.Exception($"|PluginManager.QueryForPlugin|Exception for plugin <{pair.Metadata.Name}> when query <{query}>", e); + Log.Exception($"Exception for plugin <{pair.Metadata.Name}> when query <{query}>", e, MethodBase.GetCurrentMethod().DeclaringType); + return new List(); } } @@ -190,13 +207,15 @@ namespace Wox.Core.Plugin { foreach (Result result in results) { - if (!string.IsNullOrEmpty(result.QueryTextDisplay)) + if (string.IsNullOrEmpty(result.QueryTextDisplay)) { - result.QueryTextDisplay = string.Format("{0} {1}", query.ActionKeyword, result.QueryTextDisplay); + result.QueryTextDisplay = result.Title; } - else + + if (!string.IsNullOrEmpty(query.ActionKeyword)) { - result.QueryTextDisplay = string.Format("{0} {1}", query.ActionKeyword, result.Title); + // Using CurrentCulture since this is user facing + result.QueryTextDisplay = string.Format(CultureInfo.CurrentCulture, "{0} {1}", query.ActionKeyword, result.QueryTextDisplay); } } @@ -205,6 +224,16 @@ namespace Wox.Core.Plugin public static void UpdatePluginMetadata(List results, PluginMetadata metadata, Query query) { + if (results == null) + { + throw new ArgumentNullException(nameof(results)); + } + + if (metadata == null) + { + throw new ArgumentNullException(nameof(metadata)); + } + foreach (var r in results) { r.PluginDirectory = metadata.PluginDirectory; @@ -234,6 +263,7 @@ namespace Wox.Core.Plugin return AllPlugins.Where(p => p.Plugin is T); } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "All exception information is being logged")] public static List GetContextMenusForPlugin(Result result) { var pluginPair = _contextMenuPlugins.FirstOrDefault(o => o.Metadata.ID == result.PluginID); @@ -245,11 +275,13 @@ namespace Wox.Core.Plugin try { var results = plugin.LoadContextMenus(result); + return results; } catch (Exception e) { - Log.Exception($"|PluginManager.GetContextMenusForPlugin|Can't load context menus for plugin <{metadata.Name}>", e); + Log.Exception($"Can't load context menus for plugin <{metadata.Name}>", e, MethodBase.GetCurrentMethod().DeclaringType); + return new List(); } } diff --git a/src/modules/launcher/Wox.Core/Plugin/PluginsLoader.cs b/src/modules/launcher/Wox.Core/Plugin/PluginsLoader.cs index 0f1550b3c0..d946d7fc14 100644 --- a/src/modules/launcher/Wox.Core/Plugin/PluginsLoader.cs +++ b/src/modules/launcher/Wox.Core/Plugin/PluginsLoader.cs @@ -17,18 +17,17 @@ namespace Wox.Core.Plugin { public const string PATH = "PATH"; - public static List Plugins(List metadatas, PluginSettings settings) + public static List Plugins(List metadatas) { var csharpPlugins = CSharpPlugins(metadatas).ToList(); - var executablePlugins = ExecutablePlugins(metadatas); - var plugins = csharpPlugins.Concat(executablePlugins).ToList(); - return plugins; + return csharpPlugins; } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "All exception information is being logged")] public static IEnumerable CSharpPlugins(List source) { var plugins = new List(); - var metadatas = source.Where(o => o.Language.ToUpper() == AllowedLanguage.CSharp); + var metadatas = source.Where(o => o.Language.ToUpperInvariant() == AllowedLanguage.CSharp); foreach (var metadata in metadatas) { @@ -47,7 +46,7 @@ namespace Wox.Core.Plugin } catch (Exception e) { - Infrastructure.Logger.Log.Exception($"|PluginsLoader.CSharpPlugins|Couldn't load assembly for {metadata.Name}", e); + Infrastructure.Logger.Log.Exception($"Couldn't load assembly for {metadata.Name}", e, MethodBase.GetCurrentMethod().DeclaringType); return; } @@ -59,7 +58,7 @@ namespace Wox.Core.Plugin } catch (InvalidOperationException e) { - Infrastructure.Logger.Log.Exception($"|PluginsLoader.CSharpPlugins|Can't find class implement IPlugin for <{metadata.Name}>", e); + Infrastructure.Logger.Log.Exception($"Can't find class implement IPlugin for <{metadata.Name}>", e, MethodBase.GetCurrentMethod().DeclaringType); return; } @@ -70,7 +69,7 @@ namespace Wox.Core.Plugin } catch (Exception e) { - Infrastructure.Logger.Log.Exception($"|PluginsLoader.CSharpPlugins|Can't create instance for <{metadata.Name}>", e); + Infrastructure.Logger.Log.Exception($"Can't create instance for <{metadata.Name}>", e, MethodBase.GetCurrentMethod().DeclaringType); return; } #endif @@ -86,17 +85,5 @@ namespace Wox.Core.Plugin return plugins; } - - public static IEnumerable ExecutablePlugins(IEnumerable source) - { - var metadatas = source.Where(o => o.Language.ToUpper() == AllowedLanguage.Executable); - - var plugins = metadatas.Select(metadata => new PluginPair - { - Plugin = new ExecutablePlugin(metadata.ExecuteFilePath), - Metadata = metadata, - }); - return plugins; - } } } diff --git a/src/modules/launcher/Wox.Core/Plugin/QueryBuilder.cs b/src/modules/launcher/Wox.Core/Plugin/QueryBuilder.cs index d9976e4752..6db5def1c5 100644 --- a/src/modules/launcher/Wox.Core/Plugin/QueryBuilder.cs +++ b/src/modules/launcher/Wox.Core/Plugin/QueryBuilder.cs @@ -13,6 +13,16 @@ namespace Wox.Core.Plugin { public static Dictionary Build(ref string text, Dictionary nonGlobalPlugins) { + if (text == null) + { + throw new ArgumentNullException(nameof(text)); + } + + if (nonGlobalPlugins == null) + { + throw new ArgumentNullException(nameof(nonGlobalPlugins)); + } + // replace multiple white spaces with one white space var terms = text.Split(new[] { Query.TermSeparator }, StringSplitOptions.RemoveEmptyEntries); if (terms.Length == 0) @@ -32,7 +42,8 @@ namespace Wox.Core.Plugin foreach (string pluginActionKeyword in nonGlobalPlugins.Keys) { - if (possibleActionKeyword.StartsWith(pluginActionKeyword)) + // Using Ordinal since this is used internally + if (possibleActionKeyword.StartsWith(pluginActionKeyword, StringComparison.Ordinal)) { if (nonGlobalPlugins.TryGetValue(pluginActionKeyword, out var pluginPair) && !pluginPair.Metadata.Disabled) { @@ -59,12 +70,20 @@ namespace Wox.Core.Plugin } } - var globalplugins = PluginManager.GlobalPlugins; - - foreach (PluginPair globalPlugin in PluginManager.GlobalPlugins) + // If the user has specified a matching action keyword, then do not + // add the global plugins to the list. + if (pluginQueryPair.Count == 0) { - var query = new Query(rawQuery, rawQuery, terms, string.Empty); - pluginQueryPair.Add(globalPlugin, query); + var globalplugins = PluginManager.GlobalPlugins; + + foreach (PluginPair globalPlugin in PluginManager.GlobalPlugins) + { + if (!pluginQueryPair.ContainsKey(globalPlugin)) + { + var query = new Query(rawQuery, rawQuery, terms, string.Empty); + pluginQueryPair.Add(globalPlugin, query); + } + } } return pluginQueryPair; diff --git a/src/modules/launcher/Wox.Core/Resource/FontHelper.cs b/src/modules/launcher/Wox.Core/Resource/FontHelper.cs index 191cf8daad..90f6931fef 100644 --- a/src/modules/launcher/Wox.Core/Resource/FontHelper.cs +++ b/src/modules/launcher/Wox.Core/Resource/FontHelper.cs @@ -3,9 +3,12 @@ // See the LICENSE file in the project root for more information. using System; +using System.Diagnostics; using System.Linq; +using System.Reflection; using System.Windows; using System.Windows.Media; +using Wox.Infrastructure.Logger; namespace Wox.Core.Resource { @@ -24,8 +27,9 @@ namespace Wox.Core.Resource { return (FontWeight)_fontWeightConverter.ConvertFromInvariantString(value); } - catch + catch (NotSupportedException e) { + Log.Exception($"Can't convert {value} to FontWeight", e, MethodBase.GetCurrentMethod().DeclaringType); return FontWeights.Normal; } } @@ -43,8 +47,9 @@ namespace Wox.Core.Resource { return (FontStyle)_fontStyleConverter.ConvertFromInvariantString(value); } - catch + catch (NotSupportedException e) { + Log.Exception($"Can't convert {value} to FontStyle", e, MethodBase.GetCurrentMethod().DeclaringType); return FontStyles.Normal; } } @@ -62,14 +67,20 @@ namespace Wox.Core.Resource { return (FontStretch)_fontStretchConverter.ConvertFromInvariantString(value); } - catch + catch (NotSupportedException e) { + Log.Exception($"Can't convert {value} to FontStretch", e, MethodBase.GetCurrentMethod().DeclaringType); return FontStretches.Normal; } } public static FamilyTypeface ChooseRegularFamilyTypeface(this FontFamily family) { + if (family == null) + { + throw new ArgumentNullException(nameof(family)); + } + return family.FamilyTypefaces.OrderBy(o => { return (Math.Abs(o.Stretch.ToOpenTypeStretch() - FontStretches.Normal.ToOpenTypeStretch()) * 100) + @@ -80,6 +91,11 @@ namespace Wox.Core.Resource public static FamilyTypeface ConvertFromInvariantStringsOrNormal(this FontFamily family, string style, string weight, string stretch) { + if (family == null) + { + throw new ArgumentNullException(nameof(family)); + } + var styleObj = GetFontStyleFromInvariantStringOrNormal(style); var weightObj = GetFontWeightFromInvariantStringOrNormal(weight); var stretchObj = GetFontStretchFromInvariantStringOrNormal(stretch); diff --git a/src/modules/launcher/Wox.Core/Wox.Core.csproj b/src/modules/launcher/Wox.Core/Wox.Core.csproj index 98697616a2..2708128a28 100644 --- a/src/modules/launcher/Wox.Core/Wox.Core.csproj +++ b/src/modules/launcher/Wox.Core/Wox.Core.csproj @@ -65,6 +65,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + all diff --git a/src/modules/launcher/Wox.Infrastructure/Alphabet.cs b/src/modules/launcher/Wox.Infrastructure/Alphabet.cs index ba057eb373..5aa8768945 100644 --- a/src/modules/launcher/Wox.Infrastructure/Alphabet.cs +++ b/src/modules/launcher/Wox.Infrastructure/Alphabet.cs @@ -41,7 +41,7 @@ namespace Wox.Infrastructure // force pinyin library static constructor initialize PinyinHelper.toHanyuPinyinStringArray('T', _pinyinFormat); }); - Log.Info($"|Wox.Infrastructure.Alphabet.Initialize|Number of preload pinyin combination<{_pinyinCache.Count}>"); + Log.Info($"Number of preload pinyin combination<{_pinyinCache.Count}>", GetType()); } public string Translate(string str) diff --git a/src/modules/launcher/Wox.Infrastructure/FileSystemHelper/FileWrapper.cs b/src/modules/launcher/Wox.Infrastructure/FileSystemHelper/FileWrapper.cs index 244a8a77e7..4acd853061 100644 --- a/src/modules/launcher/Wox.Infrastructure/FileSystemHelper/FileWrapper.cs +++ b/src/modules/launcher/Wox.Infrastructure/FileSystemHelper/FileWrapper.cs @@ -2,7 +2,9 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.IO; +using System.Security; using Wox.Infrastructure.Logger; namespace Wox.Infrastructure.FileSystemHelper @@ -19,9 +21,10 @@ namespace Wox.Infrastructure.FileSystemHelper { return File.ReadAllLines(path); } - catch (IOException ex) + catch (System.Exception ex) when (ex is SecurityException || ex is UnauthorizedAccessException || ex is IOException) { - Log.Info($"File {path} is being accessed by another process| {ex.Message}"); + Log.Info($"Unable to read File: {path}| {ex.Message}", GetType()); + return new string[] { string.Empty }; } } diff --git a/src/modules/launcher/Wox.Infrastructure/Helper.cs b/src/modules/launcher/Wox.Infrastructure/Helper.cs index 9da3e31b75..6b5f839521 100644 --- a/src/modules/launcher/Wox.Infrastructure/Helper.cs +++ b/src/modules/launcher/Wox.Infrastructure/Helper.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Reflection; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Wox.Infrastructure.Logger; @@ -95,7 +96,7 @@ namespace Wox.Infrastructure } catch (System.Exception ex) { - Log.Exception($"Wox.Infrastructure.Helper| Unable to Run {path} as admin : {ex.Message}", ex); + Log.Exception($"Unable to Run {path} as admin : {ex.Message}", ex, MethodBase.GetCurrentMethod().DeclaringType); } } diff --git a/src/modules/launcher/Wox.Infrastructure/Http/Http.cs b/src/modules/launcher/Wox.Infrastructure/Http/Http.cs index cdff6125d0..1fdac292c9 100644 --- a/src/modules/launcher/Wox.Infrastructure/Http/Http.cs +++ b/src/modules/launcher/Wox.Infrastructure/Http/Http.cs @@ -5,6 +5,7 @@ using System.IO; using System.Net; using System.Net.Http; +using System.Reflection; using System.Text; using System.Threading.Tasks; using JetBrains.Annotations; @@ -61,7 +62,8 @@ namespace Wox.Infrastructure.Http public static async Task Get([NotNull] string url, string encoding = "UTF-8") { - Log.Debug($"|Http.Get|Url <{url}>"); + Log.Debug($"Url <{url}>", MethodBase.GetCurrentMethod().DeclaringType); + var request = WebRequest.CreateHttp(url); request.Method = "GET"; request.Timeout = 1000; diff --git a/src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs b/src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs index 9988745a51..8a4d47ca5d 100644 --- a/src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs +++ b/src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs @@ -7,6 +7,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using System.Windows.Media; using System.Windows.Media.Imaging; @@ -62,7 +63,8 @@ namespace Wox.Infrastructure.Image Load(x.Key); }); }); - Log.Info($"|ImageLoader.Initialize|Number of preload images is <{ImageCache.Usage.Count}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}"); + + Log.Info($"Number of preload images is <{ImageCache.Usage.Count}>, Images Number: {ImageCache.CacheSize()}, Unique Items {ImageCache.UniqueImagesInCache()}", MethodBase.GetCurrentMethod().DeclaringType); }); } @@ -188,7 +190,7 @@ namespace Wox.Infrastructure.Image } catch (System.Exception e) { - Log.Exception($"|ImageLoader.Load|Failed to get thumbnail for {path}", e); + Log.Exception($"Failed to get thumbnail for {path}", e, MethodBase.GetCurrentMethod().DeclaringType); type = ImageType.Error; image = ImageCache[ErrorIconPath]; ImageCache[path] = image; diff --git a/src/modules/launcher/Wox.Infrastructure/Logger/Log.cs b/src/modules/launcher/Wox.Infrastructure/Logger/Log.cs index 15cc2ff62a..ba97966bad 100644 --- a/src/modules/launcher/Wox.Infrastructure/Logger/Log.cs +++ b/src/modules/launcher/Wox.Infrastructure/Logger/Log.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.IO; using System.Runtime.CompilerServices; using NLog; @@ -37,67 +38,25 @@ namespace Wox.Infrastructure.Logger LogManager.Configuration = configuration; } - private static void LogFaultyFormat(string message) + private static void LogInternalException(string message, System.Exception e, Type fullClassName, [CallerMemberName] string methodName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) { - var logger = LogManager.GetLogger("FaultyLogger"); - message = $"Wrong logger message format <{message}>"; - System.Diagnostics.Debug.WriteLine($"FATAL|{message}"); - logger.Fatal(message); - } + var logger = GetLogger(fullClassName.FullName, methodName); - private static bool FormatValid(string message) - { - var parts = message.Split('|'); - var valid = parts.Length == 3 && !string.IsNullOrWhiteSpace(parts[1]) && !string.IsNullOrWhiteSpace(parts[2]); - return valid; - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public static void Exception(string className, string message, System.Exception exception, [CallerMemberName] string methodName = "") - { - var classNameWithMethod = CheckClassAndMessageAndReturnFullClassWithMethod(className, message, methodName); - - ExceptionInternal(classNameWithMethod, message, exception); - } - - private static string CheckClassAndMessageAndReturnFullClassWithMethod(string className, string message, string methodName) - { - if (string.IsNullOrWhiteSpace(className)) - { - LogFaultyFormat($"Fail to specify a class name during logging of message: {message ?? "no message entered"}"); - } - - if (string.IsNullOrWhiteSpace(message)) - { - // todo: not sure we really need that - LogFaultyFormat($"Fail to specify a message during logging"); - } - - if (!string.IsNullOrWhiteSpace(methodName)) - { - return className + "." + methodName; - } - - return className; - } - - private static void ExceptionInternal(string classAndMethod, string message, System.Exception e) - { - var logger = LogManager.GetLogger(classAndMethod); - - System.Diagnostics.Debug.WriteLine($"ERROR|{message}"); + LogInternal(LogLevel.Error, message, fullClassName, logger, methodName, sourceFilePath, sourceLineNumber); logger.Error("-------------------------- Begin exception --------------------------"); - logger.Error(message); + logger.Error($"\n\tMessage:\n\t {message}"); do { - logger.Error($"Exception full name:\n <{e.GetType().FullName}>"); - logger.Error($"Exception message:\n <{e.Message}>"); - logger.Error($"Exception stack trace:\n <{e.StackTrace}>"); - logger.Error($"Exception source:\n <{e.Source}>"); - logger.Error($"Exception target site:\n <{e.TargetSite}>"); - logger.Error($"Exception HResult:\n <{e.HResult}>"); + logger.Error( + $"\n\tException full name:\n\t <{e.GetType().FullName}>" + + $"\n\tException message:\n\t <{e.Message}>" + + $"\n\tException stack trace:\n\t <{e.StackTrace}>" + + $"\n\tException source:\n\t <{e.Source}>" + + $"\n\tException target site:\n\t <{e.TargetSite}>" + + $"\n\tException HResult:\n\t <{e.HResult}>"); + e = e.InnerException; } while (e != null); @@ -105,93 +64,53 @@ namespace Wox.Infrastructure.Logger logger.Error("-------------------------- End exception --------------------------"); } - private static void LogInternal(string message, LogLevel level) + public static void Info(string message, Type fullClassName, [CallerMemberName] string methodName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) { - if (FormatValid(message)) - { - var parts = message.Split('|'); - var prefix = parts[1]; - var unprefixed = parts[2]; - var logger = LogManager.GetLogger(prefix); - - System.Diagnostics.Debug.WriteLine($"{level.Name}|{message}"); - logger.Log(level, unprefixed); - } - else - { - LogFaultyFormat(message); - } + LogInternal(LogLevel.Info, message, fullClassName, methodName, sourceFilePath, sourceLineNumber); } - /// example: "|prefix|unprefixed" - [MethodImpl(MethodImplOptions.Synchronized)] - public static void Exception(string message, System.Exception e) + public static void Debug(string message, Type fullClassName, [CallerMemberName] string methodName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) { - if (FormatValid(message)) - { - var parts = message.Split('|'); - var prefix = parts[1]; - var unprefixed = parts[2]; - ExceptionInternal(prefix, unprefixed, e); - } - else - { - LogFaultyFormat(message); - } + LogInternal(LogLevel.Debug, message, fullClassName, methodName, sourceFilePath, sourceLineNumber); } - /// example: "|prefix|unprefixed" - public static void Error(string message) + public static void Warn(string message, Type fullClassName, [CallerMemberName] string methodName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) { - LogInternal(message, LogLevel.Error); + LogInternal(LogLevel.Warn, message, fullClassName, methodName, sourceFilePath, sourceLineNumber); } - public static void Error(string className, string message, [CallerMemberName] string methodName = "") + public static void Error(string message, Type fullClassName, [CallerMemberName] string methodName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) { - LogInternal(LogLevel.Error, className, message, methodName); + LogInternal(LogLevel.Error, message, fullClassName, methodName, sourceFilePath, sourceLineNumber); } - private static void LogInternal(LogLevel level, string className, string message, [CallerMemberName] string methodName = "") + public static void Exception(string message, System.Exception ex, Type fullClassName, [CallerMemberName] string methodName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) { - var classNameWithMethod = CheckClassAndMessageAndReturnFullClassWithMethod(className, message, methodName); - - var logger = LogManager.GetLogger(classNameWithMethod); - - System.Diagnostics.Debug.WriteLine($"{level.Name}|{message}"); - logger.Log(level, message); + LogInternalException(message, ex, fullClassName, methodName, sourceFilePath, sourceLineNumber); } - public static void Debug(string className, string message, [CallerMemberName] string methodName = "") + private static void LogInternal(LogLevel level, string message, Type fullClassName, [CallerMemberName] string methodName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) { - LogInternal(LogLevel.Debug, className, message, methodName); + var logger = GetLogger(fullClassName.FullName, methodName); + + LogInternal(level, message, fullClassName, logger, methodName, sourceFilePath, sourceLineNumber); } - /// example: "|prefix|unprefixed" - public static void Debug(string message) + private static void LogInternal(LogLevel level, string message, Type fullClassName, NLog.Logger logger, [CallerMemberName] string methodName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) { - LogInternal(message, LogLevel.Debug); + // System.Diagnostics.Debug.WriteLine($" {level.Name} | {message}"); + var msg = $"\n\tMessage: {message}" + + $"\n\tArea: {fullClassName}.{methodName}" + + $"\n\tSource Path: {sourceFilePath}::{sourceLineNumber}\n"; + + logger.Log(level, msg); } - public static void Info(string className, string message, [CallerMemberName] string methodName = "") + private static NLog.Logger GetLogger(string fullClassName, string methodName) { - LogInternal(LogLevel.Info, className, message, methodName); - } + var classNameWithMethod = $"{fullClassName}.{methodName}"; - /// example: "|prefix|unprefixed" - public static void Info(string message) - { - LogInternal(message, LogLevel.Info); - } - - public static void Warn(string className, string message, [CallerMemberName] string methodName = "") - { - LogInternal(LogLevel.Warn, className, message, methodName); - } - - /// example: "|prefix|unprefixed" - public static void Warn(string message) - { - LogInternal(message, LogLevel.Warn); + return LogManager.GetLogger(classNameWithMethod); } } } diff --git a/src/modules/launcher/Wox.Infrastructure/Stopwatch.cs b/src/modules/launcher/Wox.Infrastructure/Stopwatch.cs index f2cfe14f91..54e39b3b3c 100644 --- a/src/modules/launcher/Wox.Infrastructure/Stopwatch.cs +++ b/src/modules/launcher/Wox.Infrastructure/Stopwatch.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Reflection; using Wox.Infrastructure.Logger; namespace Wox.Infrastructure @@ -24,7 +25,7 @@ namespace Wox.Infrastructure stopWatch.Stop(); var milliseconds = stopWatch.ElapsedMilliseconds; string info = $"{message} <{milliseconds}ms>"; - Log.Debug(info); + Log.Debug(info, MethodBase.GetCurrentMethod().DeclaringType); return milliseconds; } @@ -36,7 +37,7 @@ namespace Wox.Infrastructure stopWatch.Stop(); var milliseconds = stopWatch.ElapsedMilliseconds; string info = $"{message} <{milliseconds}ms>"; - Log.Info(info); + Log.Info(info, MethodBase.GetCurrentMethod().DeclaringType); return milliseconds; } @@ -65,7 +66,7 @@ namespace Wox.Infrastructure foreach (var key in Count.Keys) { string info = $"{key} already cost {Count[key]}ms"; - Log.Debug(info); + Log.Debug(info, MethodBase.GetCurrentMethod().DeclaringType); } } } diff --git a/src/modules/launcher/Wox.Infrastructure/Storage/BinaryStorage`1.cs b/src/modules/launcher/Wox.Infrastructure/Storage/BinaryStorage`1.cs index c79dabcbf2..62dce38403 100644 --- a/src/modules/launcher/Wox.Infrastructure/Storage/BinaryStorage`1.cs +++ b/src/modules/launcher/Wox.Infrastructure/Storage/BinaryStorage`1.cs @@ -44,7 +44,8 @@ namespace Wox.Infrastructure.Storage if (File.Exists(FilePath)) { File.Delete(FilePath); - Log.Info($"|BinaryStorage.TryLoad|Deleting cached data at <{FilePath}>"); + + Log.Info($"Deleting cached data at <{FilePath}>", GetType()); } } @@ -52,7 +53,8 @@ namespace Wox.Infrastructure.Storage { if (new FileInfo(FilePath).Length == 0) { - Log.Error($"|BinaryStorage.TryLoad|Zero length cache file <{FilePath}>"); + Log.Error($"Zero length cache file <{FilePath}>", GetType()); + Save(defaultData); return defaultData; } @@ -65,7 +67,8 @@ namespace Wox.Infrastructure.Storage } else { - Log.Info("|BinaryStorage.TryLoad|Cache file not exist, load default data"); + Log.Info("Cache file not exist, load default data", GetType()); + Save(defaultData); return defaultData; } @@ -87,7 +90,8 @@ namespace Wox.Infrastructure.Storage } catch (System.Exception e) { - Log.Exception($"|BinaryStorage.Deserialize|Deserialize error for file <{FilePath}>", e); + Log.Exception($"Deserialize error for file <{FilePath}>", e, GetType()); + return defaultData; } finally @@ -128,12 +132,12 @@ namespace Wox.Infrastructure.Storage } catch (SerializationException e) { - Log.Exception($"|BinaryStorage.Save|serialize error for file <{FilePath}>", e); + Log.Exception($"Serialize error for file <{FilePath}>", e, GetType()); } } _storageHelper.Close(); - Log.Info($"|BinaryStorage.Save|Saving cached data at <{FilePath}>"); + Log.Info($"Saving cached data at <{FilePath}>", GetType()); } } } diff --git a/src/modules/launcher/Wox.Infrastructure/Storage/JsonStorage`1.cs b/src/modules/launcher/Wox.Infrastructure/Storage/JsonStorage`1.cs index 2b22029dec..75eea78946 100644 --- a/src/modules/launcher/Wox.Infrastructure/Storage/JsonStorage`1.cs +++ b/src/modules/launcher/Wox.Infrastructure/Storage/JsonStorage`1.cs @@ -51,7 +51,7 @@ namespace Wox.Infrastructure.Storage if (File.Exists(FilePath)) { File.Delete(FilePath); - Log.Info($"|JsonStorage.TryLoad|Deleting cached data at <{FilePath}>"); + Log.Info($"Deleting cached data at <{FilePath}>", GetType()); } } @@ -84,7 +84,7 @@ namespace Wox.Infrastructure.Storage catch (JsonException e) { LoadDefault(); - Log.Exception($"|JsonStorage.Deserialize|Deserialize error for json <{FilePath}>", e); + Log.Exception($"Deserialize error for json <{FilePath}>", e, GetType()); } if (_data == null) @@ -123,11 +123,12 @@ namespace Wox.Infrastructure.Storage string serialized = JsonConvert.SerializeObject(_data, Formatting.Indented); File.WriteAllText(FilePath, serialized); _storageHelper.Close(); - Log.Info($"|JsonStorage.Save|Saving cached data at <{FilePath}>"); + + Log.Info($"Saving cached data at <{FilePath}>", GetType()); } catch (IOException e) { - Log.Error($"|JsonStorage.Save|Error in saving data at <{FilePath}>", e.Message); + Log.Exception($"Error in saving data at <{FilePath}>", e, GetType()); } } } diff --git a/src/modules/launcher/Wox.Infrastructure/Storage/ListRepository`1.cs b/src/modules/launcher/Wox.Infrastructure/Storage/ListRepository`1.cs index dd0759a907..ef86817a44 100644 --- a/src/modules/launcher/Wox.Infrastructure/Storage/ListRepository`1.cs +++ b/src/modules/launcher/Wox.Infrastructure/Storage/ListRepository`1.cs @@ -38,7 +38,7 @@ namespace Wox.Infrastructure.Storage } catch (ArgumentException e) { - Log.Info($"|LisRepository.Set| Trying to insert a duplicate item", e.Message); + Log.Info($"Trying to insert a duplicate item {e.Message}", GetType()); } } @@ -51,7 +51,7 @@ namespace Wox.Infrastructure.Storage { if (!_items.TryAdd(insertedItem.GetHashCode(), insertedItem)) { - Log.Error($"|ListRepository.Add| Item Already Exists <{insertedItem}>"); + Log.Error($"Item Already Exists <{insertedItem}>", GetType()); } } @@ -59,7 +59,7 @@ namespace Wox.Infrastructure.Storage { if (!_items.TryRemove(removedItem.GetHashCode(), out _)) { - Log.Error($"|ListRepository.Remove| Item Not Found <{removedItem}>"); + Log.Error($"Item Not Found <{removedItem}>", GetType()); } } diff --git a/src/modules/launcher/Wox.Infrastructure/UserSettings/Settings.cs b/src/modules/launcher/Wox.Infrastructure/UserSettings/Settings.cs index 2d901d95e6..6bdcb53e81 100644 --- a/src/modules/launcher/Wox.Infrastructure/UserSettings/Settings.cs +++ b/src/modules/launcher/Wox.Infrastructure/UserSettings/Settings.cs @@ -89,7 +89,7 @@ namespace Wox.Infrastructure.UserSettings } catch (ArgumentException e) { - Logger.Log.Exception(nameof(Settings), "Failed to load QuerySearchPrecisionString value from Settings file", e); + Logger.Log.Exception("Failed to load QuerySearchPrecisionString value from Settings file", e, GetType()); QuerySearchPrecision = StringMatcher.SearchPrecisionScore.Regular; StringMatcher.Instance.UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Regular; diff --git a/src/modules/launcher/Wox.Plugin/ThemeManager.cs b/src/modules/launcher/Wox.Plugin/ThemeManager.cs index 76dd75cb7b..5b73bbeb88 100644 --- a/src/modules/launcher/Wox.Plugin/ThemeManager.cs +++ b/src/modules/launcher/Wox.Plugin/ThemeManager.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Diagnostics; using System.Linq; using System.Windows; using ControlzEx.Theming; @@ -63,17 +64,8 @@ namespace Wox.Plugin MahAppsLibraryThemeProvider.DefaultInstance)); ResetTheme(); - ControlzEx.Theming.ThemeManager.Current.ThemeSyncMode = ThemeSyncMode.SyncWithAppMode; + ControlzEx.Theming.ThemeManager.Current.ThemeSyncMode = ThemeSyncMode.SyncAll; ControlzEx.Theming.ThemeManager.Current.ThemeChanged += Current_ThemeChanged; - SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged; - } - - private void SystemParameters_StaticPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(SystemParameters.HighContrast)) - { - ResetTheme(); - } } public Theme GetCurrentTheme() @@ -104,7 +96,7 @@ namespace Wox.Plugin private void ResetTheme() { - if (SystemParameters.HighContrast) + if (WindowsThemeHelper.IsHighContrastEnabled()) { Theme highContrastBaseType = GetHighContrastBaseType(); ChangeTheme(highContrastBaseType); @@ -119,10 +111,6 @@ namespace Wox.Plugin private void ChangeTheme(Theme theme) { Theme oldTheme = currentTheme; - if (theme == currentTheme) - { - return; - } if (theme == Theme.HighContrastOne) { @@ -174,7 +162,6 @@ namespace Wox.Plugin if (disposing) { ControlzEx.Theming.ThemeManager.Current.ThemeChanged -= Current_ThemeChanged; - SystemParameters.StaticPropertyChanged -= SystemParameters_StaticPropertyChanged; _disposed = true; } } diff --git a/src/modules/launcher/Wox.Test/MainViewModelTest.cs b/src/modules/launcher/Wox.Test/MainViewModelTest.cs index cc3a14b6d0..8701df2e2a 100644 --- a/src/modules/launcher/Wox.Test/MainViewModelTest.cs +++ b/src/modules/launcher/Wox.Test/MainViewModelTest.cs @@ -22,7 +22,7 @@ namespace Wox.Test string autoCompleteText = MainViewModel.GetAutoCompleteText(index, input, query); // Assert - Assert.AreEqual(autoCompleteText, string.Empty); + Assert.AreEqual(string.Empty, autoCompleteText); } [Test] @@ -37,7 +37,7 @@ namespace Wox.Test string autoCompleteText = MainViewModel.GetAutoCompleteText(index, input, query); // Assert - Assert.AreEqual(autoCompleteText, string.Empty); + Assert.AreEqual(string.Empty, autoCompleteText); } [Test] @@ -52,7 +52,7 @@ namespace Wox.Test string autoCompleteText = MainViewModel.GetAutoCompleteText(index, input, query); // Assert - Assert.AreEqual(autoCompleteText, string.Empty); + Assert.AreEqual(string.Empty, autoCompleteText); } [Test] @@ -67,7 +67,7 @@ namespace Wox.Test string autoCompleteText = MainViewModel.GetAutoCompleteText(index, input, query); // Assert - Assert.AreEqual(autoCompleteText, string.Empty); + Assert.AreEqual(string.Empty, autoCompleteText); } [Test] @@ -82,7 +82,7 @@ namespace Wox.Test string autoCompleteText = MainViewModel.GetAutoCompleteText(index, input, query); // Assert - Assert.AreEqual(autoCompleteText, string.Empty); + Assert.AreEqual(string.Empty, autoCompleteText); } [Test] @@ -98,7 +98,7 @@ namespace Wox.Test string autoCompleteText = MainViewModel.GetAutoCompleteText(index, input, query); // Assert - Assert.AreEqual(autoCompleteText, expectedAutoCompleteText); + Assert.AreEqual(expectedAutoCompleteText, autoCompleteText); } [Test] @@ -114,7 +114,7 @@ namespace Wox.Test string autoCompleteText = MainViewModel.GetAutoCompleteText(index, input, query); // Assert - Assert.AreEqual(autoCompleteText, expectedAutoCompleteText); + Assert.AreEqual(expectedAutoCompleteText, autoCompleteText); } [Test] @@ -129,7 +129,7 @@ namespace Wox.Test string autoCompleteText = MainViewModel.GetSearchText(index, input, query); // Assert - Assert.AreEqual(autoCompleteText, string.Empty); + Assert.AreEqual(string.Empty, autoCompleteText); } [Test] @@ -144,7 +144,7 @@ namespace Wox.Test string autoCompleteText = MainViewModel.GetSearchText(index, input, query); // Assert - Assert.AreEqual(autoCompleteText, string.Empty); + Assert.AreEqual(string.Empty, autoCompleteText); } [Test] @@ -159,7 +159,7 @@ namespace Wox.Test string autoCompleteText = MainViewModel.GetSearchText(index, input, query); // Assert - Assert.AreEqual(autoCompleteText, input); + Assert.AreEqual(input, autoCompleteText); } [Test] @@ -174,7 +174,7 @@ namespace Wox.Test string autoCompleteText = MainViewModel.GetSearchText(index, input, query); // Assert - Assert.AreEqual(autoCompleteText, input); + Assert.AreEqual(input, autoCompleteText); } [Test] @@ -190,7 +190,7 @@ namespace Wox.Test string autoCompleteText = MainViewModel.GetSearchText(index, input, query); // Assert - Assert.AreEqual(autoCompleteText, expectedAutoCompleteText); + Assert.AreEqual(expectedAutoCompleteText, autoCompleteText); } [Test] @@ -205,7 +205,63 @@ namespace Wox.Test string autoCompleteText = MainViewModel.GetSearchText(index, input, query); // Assert - Assert.AreEqual(autoCompleteText, input); + Assert.AreEqual(input, autoCompleteText); + } + + [Test] + public void ShouldAutoCompleteTextBeEmpty_ShouldReturnFalse_WhenAutoCompleteTextIsEmpty() + { + // Arrange + string queryText = "Te"; + string autoCompleteText = string.Empty; + + // Act + bool result = MainViewModel.ShouldAutoCompleteTextBeEmpty(queryText, autoCompleteText); + + // Assert + Assert.AreEqual(false, result); + } + + [Test] + public void ShouldAutoCompleteTextBeEmpty_ShouldReturnTrue_WhenQueryTextMatchAutoCompleteText() + { + // Arrange + string queryText = "Te"; + string autoCompleteText = "Teams"; + + // Act + bool result = MainViewModel.ShouldAutoCompleteTextBeEmpty(queryText, autoCompleteText); + + // Assert + Assert.AreEqual(false, result); + } + + [Test] + public void ShouldAutoCompleteTextBeEmpty_ShouldReturnTrue_WhenQueryTextIsEmpty() + { + // Arrange + string queryText = string.Empty; + string autoCompleteText = "Teams"; + + // Act + bool result = MainViewModel.ShouldAutoCompleteTextBeEmpty(queryText, autoCompleteText); + + // Assert + Assert.AreEqual(true, result); + } + + [Test] + public void ShouldAutoCompleteTextBeEmpty_ShouldReturnTrue_WhenQueryTextDoesNotMatchAutoCompleteText() + { + // Arrange + string queryText = "TE"; + string autoCompleteText = "Teams"; + + // Act + bool result = MainViewModel.ShouldAutoCompleteTextBeEmpty(queryText, autoCompleteText); + + // Assert + Assert.AreEqual(true, result); } } } diff --git a/src/modules/launcher/Wox.Test/PluginManagerTest.cs b/src/modules/launcher/Wox.Test/PluginManagerTest.cs index 0c346d49cc..0d1ee03ab0 100644 --- a/src/modules/launcher/Wox.Test/PluginManagerTest.cs +++ b/src/modules/launcher/Wox.Test/PluginManagerTest.cs @@ -15,13 +15,18 @@ namespace Wox.Test [TestFixture] public class PluginManagerTest { - [Test] - public void QueryForPlugin_SetsActionKeyword_WhenQueryTextDisplayIsSet() + [TestCase(">", "dummyQueryText", "dummyTitle", "> dummyQueryText")] + [TestCase(">", null, "dummyTitle", "> dummyTitle")] + [TestCase(">", "", "dummyTitle", "> dummyTitle")] + [TestCase("", "dummyQueryText", "dummyTitle", "dummyQueryText")] + [TestCase("", null, "dummyTitle", "dummyTitle")] + [TestCase("", "", "dummyTitle", "dummyTitle")] + [TestCase(null, "dummyQueryText", "dummyTitle", "dummyQueryText")] + [TestCase(null, null, "dummyTitle", "dummyTitle")] + [TestCase(null, "", "dummyTitle", "dummyTitle")] + public void QueryForPlugin_SetsActionKeyword_WhenQueryTextDisplayIsEmpty(string actionKeyword, string queryTextDisplay, string title, string expectedResult) { // Arrange - var actionKeyword = ">"; - var title = "dummyTitle"; - var queryTextDisplay = "dummyQueryTextDisplay"; var query = new Query { ActionKeyword = actionKeyword, @@ -51,46 +56,7 @@ namespace Wox.Test var queryOutput = PluginManager.QueryForPlugin(pluginPair, query); // Assert - Assert.AreEqual(string.Format("{0} {1}", ">", queryTextDisplay), queryOutput[0].QueryTextDisplay); - } - - [TestCase("")] - [TestCase(null)] - public void QueryForPlugin_SetsActionKeyword_WhenQueryTextDisplayIsEmpty(string queryTextDisplay) - { - // Arrange - var actionKeyword = ">"; - var title = "dummyTitle"; - var query = new Query - { - ActionKeyword = actionKeyword, - }; - var metadata = new PluginMetadata - { - ID = "dummyName", - IcoPath = "dummyIcoPath", - ExecuteFileName = "dummyExecuteFileName", - PluginDirectory = "dummyPluginDirectory", - }; - var result = new Result() - { - QueryTextDisplay = queryTextDisplay, - Title = title, - }; - var results = new List() { result }; - var pluginMock = new Mock(); - pluginMock.Setup(r => r.Query(query)).Returns(results); - var pluginPair = new PluginPair - { - Plugin = pluginMock.Object, - Metadata = metadata, - }; - - // Act - var queryOutput = PluginManager.QueryForPlugin(pluginPair, query); - - // Assert - Assert.AreEqual(string.Format("{0} {1}", ">", title), queryOutput[0].QueryTextDisplay); + Assert.AreEqual(expectedResult, queryOutput[0].QueryTextDisplay); } } } diff --git a/src/modules/launcher/Wox.Test/Plugins/FolderPluginTest.cs b/src/modules/launcher/Wox.Test/Plugins/FolderPluginTest.cs index 43dd47087b..cc541d4ea2 100644 --- a/src/modules/launcher/Wox.Test/Plugins/FolderPluginTest.cs +++ b/src/modules/launcher/Wox.Test/Plugins/FolderPluginTest.cs @@ -26,9 +26,9 @@ namespace Wox.Test.Plugins List contextMenuResults = contextMenuLoader.LoadContextMenus(result); // Assert - Assert.AreEqual(contextMenuResults.Count, 2); - Assert.AreEqual(contextMenuResults[0].Title, Microsoft.Plugin.Folder.Properties.Resources.Microsoft_plugin_folder_copy_path); - Assert.AreEqual(contextMenuResults[1].Title, Microsoft.Plugin.Folder.Properties.Resources.Microsoft_plugin_folder_open_in_console); + Assert.AreEqual(2, contextMenuResults.Count); + Assert.AreEqual(Microsoft.Plugin.Folder.Properties.Resources.Microsoft_plugin_folder_copy_path, contextMenuResults[0].Title); + Assert.AreEqual(Microsoft.Plugin.Folder.Properties.Resources.Microsoft_plugin_folder_open_in_console, contextMenuResults[1].Title); } [Test] @@ -45,10 +45,10 @@ namespace Wox.Test.Plugins List contextMenuResults = contextMenuLoader.LoadContextMenus(result); // Assert - Assert.AreEqual(contextMenuResults.Count, 3); - Assert.AreEqual(contextMenuResults[0].Title, Microsoft.Plugin.Folder.Properties.Resources.Microsoft_plugin_folder_open_containing_folder); - Assert.AreEqual(contextMenuResults[1].Title, Microsoft.Plugin.Folder.Properties.Resources.Microsoft_plugin_folder_copy_path); - Assert.AreEqual(contextMenuResults[2].Title, Microsoft.Plugin.Folder.Properties.Resources.Microsoft_plugin_folder_open_in_console); + Assert.AreEqual(3, contextMenuResults.Count); + Assert.AreEqual(Microsoft.Plugin.Folder.Properties.Resources.Microsoft_plugin_folder_open_containing_folder, contextMenuResults[0].Title); + Assert.AreEqual(Microsoft.Plugin.Folder.Properties.Resources.Microsoft_plugin_folder_copy_path, contextMenuResults[1].Title); + Assert.AreEqual(Microsoft.Plugin.Folder.Properties.Resources.Microsoft_plugin_folder_open_in_console, contextMenuResults[2].Title); } } } diff --git a/src/modules/launcher/Wox.Test/Plugins/ResultTest.cs b/src/modules/launcher/Wox.Test/Plugins/ResultTest.cs index 04232837a4..dbe279c6ae 100644 --- a/src/modules/launcher/Wox.Test/Plugins/ResultTest.cs +++ b/src/modules/launcher/Wox.Test/Plugins/ResultTest.cs @@ -22,7 +22,7 @@ namespace Wox.Test.Plugins res.ToolTipData = new ToolTipData(toolTipText, string.Empty); // Assert - Assert.AreEqual(res.ToolTipVisibility, Visibility.Visible); + Assert.AreEqual(Visibility.Visible, res.ToolTipVisibility); } [Test] @@ -32,7 +32,7 @@ namespace Wox.Test.Plugins Result res = new Result(); // Assert - Assert.AreEqual(res.ToolTipVisibility, Visibility.Collapsed); + Assert.AreEqual(Visibility.Collapsed, res.ToolTipVisibility); } } } diff --git a/src/modules/launcher/Wox.Test/Plugins/WindowsIndexerTest.cs b/src/modules/launcher/Wox.Test/Plugins/WindowsIndexerTest.cs index 5824a8429d..1df80a8e41 100644 --- a/src/modules/launcher/Wox.Test/Plugins/WindowsIndexerTest.cs +++ b/src/modules/launcher/Wox.Test/Plugins/WindowsIndexerTest.cs @@ -53,7 +53,7 @@ namespace Wox.Test.Plugins // Assert Assert.IsNotNull(queryHelper); - Assert.AreEqual(queryHelper.QueryMaxResults, maxCount); + Assert.AreEqual(maxCount, queryHelper.QueryMaxResults); } [Test] @@ -281,11 +281,11 @@ namespace Wox.Test.Plugins List contextMenuItems = contextMenuLoader.LoadContextMenus(result); // Assert - Assert.AreEqual(contextMenuItems.Count, 4); - Assert.AreEqual(contextMenuItems[0].Title, Microsoft.Plugin.Indexer.Properties.Resources.Microsoft_plugin_indexer_open_containing_folder); - Assert.AreEqual(contextMenuItems[1].Title, Microsoft.Plugin.Indexer.Properties.Resources.Microsoft_plugin_indexer_run_as_administrator); - Assert.AreEqual(contextMenuItems[2].Title, Microsoft.Plugin.Indexer.Properties.Resources.Microsoft_plugin_indexer_copy_path); - Assert.AreEqual(contextMenuItems[3].Title, Microsoft.Plugin.Indexer.Properties.Resources.Microsoft_plugin_indexer_open_in_console); + Assert.AreEqual(4, contextMenuItems.Count); + Assert.AreEqual(Microsoft.Plugin.Indexer.Properties.Resources.Microsoft_plugin_indexer_open_containing_folder, contextMenuItems[0].Title); + Assert.AreEqual(Microsoft.Plugin.Indexer.Properties.Resources.Microsoft_plugin_indexer_run_as_administrator, contextMenuItems[1].Title); + Assert.AreEqual(Microsoft.Plugin.Indexer.Properties.Resources.Microsoft_plugin_indexer_copy_path, contextMenuItems[2].Title); + Assert.AreEqual(Microsoft.Plugin.Indexer.Properties.Resources.Microsoft_plugin_indexer_open_in_console, contextMenuItems[3].Title); } [TestCase("item.pdf")] @@ -309,10 +309,10 @@ namespace Wox.Test.Plugins List contextMenuItems = contextMenuLoader.LoadContextMenus(result); // Assert - Assert.AreEqual(contextMenuItems.Count, 3); - Assert.AreEqual(contextMenuItems[0].Title, Microsoft.Plugin.Indexer.Properties.Resources.Microsoft_plugin_indexer_open_containing_folder); - Assert.AreEqual(contextMenuItems[1].Title, Microsoft.Plugin.Indexer.Properties.Resources.Microsoft_plugin_indexer_copy_path); - Assert.AreEqual(contextMenuItems[2].Title, Microsoft.Plugin.Indexer.Properties.Resources.Microsoft_plugin_indexer_open_in_console); + Assert.AreEqual(3, contextMenuItems.Count); + Assert.AreEqual(Microsoft.Plugin.Indexer.Properties.Resources.Microsoft_plugin_indexer_open_containing_folder, contextMenuItems[0].Title); + Assert.AreEqual(Microsoft.Plugin.Indexer.Properties.Resources.Microsoft_plugin_indexer_copy_path, contextMenuItems[1].Title); + Assert.AreEqual(Microsoft.Plugin.Indexer.Properties.Resources.Microsoft_plugin_indexer_open_in_console, contextMenuItems[2].Title); } [TestCase("C:/DummyFolder")] @@ -334,9 +334,9 @@ namespace Wox.Test.Plugins List contextMenuItems = contextMenuLoader.LoadContextMenus(result); // Assert - Assert.AreEqual(contextMenuItems.Count, 2); - Assert.AreEqual(contextMenuItems[0].Title, Microsoft.Plugin.Indexer.Properties.Resources.Microsoft_plugin_indexer_copy_path); - Assert.AreEqual(contextMenuItems[1].Title, Microsoft.Plugin.Indexer.Properties.Resources.Microsoft_plugin_indexer_open_in_console); + Assert.AreEqual(2, contextMenuItems.Count); + Assert.AreEqual(Microsoft.Plugin.Indexer.Properties.Resources.Microsoft_plugin_indexer_copy_path, contextMenuItems[0].Title); + Assert.AreEqual(Microsoft.Plugin.Indexer.Properties.Resources.Microsoft_plugin_indexer_open_in_console, contextMenuItems[1].Title); } [TestCase(0, false, ExpectedResult = true)] diff --git a/src/modules/launcher/Wox.Test/ResultsViewModelTest.cs b/src/modules/launcher/Wox.Test/ResultsViewModelTest.cs index 29e9bed85d..fae10cc511 100644 --- a/src/modules/launcher/Wox.Test/ResultsViewModelTest.cs +++ b/src/modules/launcher/Wox.Test/ResultsViewModelTest.cs @@ -25,7 +25,7 @@ namespace Wox.Test rvm.SelectedItem = selectedItem; // Assert - Assert.AreEqual(selectedItem.ContextMenuSelectedIndex, ResultViewModel.NoSelectionIndex); + Assert.AreEqual(ResultViewModel.NoSelectionIndex, selectedItem.ContextMenuSelectedIndex); } [Test] @@ -45,7 +45,7 @@ namespace Wox.Test rvm.SelectNextContextMenuItem(); // Assert - Assert.AreEqual(selectedItem.ContextMenuSelectedIndex, 0); + Assert.AreEqual(0, selectedItem.ContextMenuSelectedIndex); } [Test] @@ -65,7 +65,7 @@ namespace Wox.Test rvm.SelectNextContextMenuItem(); // Assert - Assert.AreEqual(selectedItem.ContextMenuSelectedIndex, 0); + Assert.AreEqual(0, selectedItem.ContextMenuSelectedIndex); } [Test] @@ -96,7 +96,7 @@ namespace Wox.Test rvm.SelectPreviousContextMenuItem(); // Assert - Assert.AreEqual(selectedItem.ContextMenuSelectedIndex, 1); + Assert.AreEqual(1, selectedItem.ContextMenuSelectedIndex); } [Test] @@ -117,7 +117,7 @@ namespace Wox.Test rvm.SelectPreviousContextMenuItem(); // Assert - Assert.AreEqual(selectedItem.ContextMenuSelectedIndex, ResultViewModel.NoSelectionIndex); + Assert.AreEqual(ResultViewModel.NoSelectionIndex, selectedItem.ContextMenuSelectedIndex); } [Test] diff --git a/src/modules/powerrename/dll/PowerRenameConstants.h b/src/modules/powerrename/dll/PowerRenameConstants.h new file mode 100644 index 0000000000..fbbcffe02e --- /dev/null +++ b/src/modules/powerrename/dll/PowerRenameConstants.h @@ -0,0 +1,8 @@ +#pragma once +#include + +namespace PowerRenameConstants +{ + // Name of the powertoy module. + inline const std::wstring ModuleKey = L"PowerRename"; +} \ No newline at end of file diff --git a/src/modules/powerrename/dll/PowerRenameExt.vcxproj b/src/modules/powerrename/dll/PowerRenameExt.vcxproj index 4644f17a96..9561fd7aa0 100644 --- a/src/modules/powerrename/dll/PowerRenameExt.vcxproj +++ b/src/modules/powerrename/dll/PowerRenameExt.vcxproj @@ -178,6 +178,7 @@ + diff --git a/src/modules/powerrename/dll/dllmain.cpp b/src/modules/powerrename/dll/dllmain.cpp index 00069bf084..d9aede9323 100644 --- a/src/modules/powerrename/dll/dllmain.cpp +++ b/src/modules/powerrename/dll/dllmain.cpp @@ -7,6 +7,7 @@ #include #include "Generated Files/resource.h" #include +#include std::atomic g_dwModuleRefCount = 0; HINSTANCE g_hInst = 0; @@ -155,14 +156,22 @@ private: // Enabled by default bool m_enabled = true; std::wstring app_name; + //contains the non localized key of the powertoy + std::wstring app_key; public: - // Return the display name of the powertoy, this will be cached + // Return the localized display name of the powertoy virtual PCWSTR get_name() override { return app_name.c_str(); } + // Return the non localized key of the powertoy, this will be cached by the runner + virtual const wchar_t* get_key() override + { + return app_key.c_str(); + } + // Enable the powertoy virtual void enable() { @@ -236,7 +245,7 @@ public: { // Parse the input JSON string. PowerToysSettings::PowerToyValues values = - PowerToysSettings::PowerToyValues::from_json_string(config); + PowerToysSettings::PowerToyValues::from_json_string(config, get_key()); CSettingsInstance().SetPersistState(values.get_bool_value(L"bool_persist_input").value()); CSettingsInstance().SetMRUEnabled(values.get_bool_value(L"bool_mru_enabled").value()); @@ -282,6 +291,7 @@ public: { init_settings(); app_name = GET_RESOURCE_STRING(IDS_POWERRENAME_APP_NAME); + app_key = PowerRenameConstants::ModuleKey; } ~PowerRenameModule(){}; diff --git a/src/modules/powerrename/dll/loc/cs/src/modules/powerrename/dll/Resources.resx.lcl b/src/modules/powerrename/dll/loc/cs/src/modules/powerrename/dll/Resources.resx.lcl new file mode 100644 index 0000000000..784fc8967c --- /dev/null +++ b/src/modules/powerrename/dll/loc/cs/src/modules/powerrename/dll/Resources.resx.lcl @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/dll/loc/de/src/modules/powerrename/dll/Resources.resx.lcl b/src/modules/powerrename/dll/loc/de/src/modules/powerrename/dll/Resources.resx.lcl new file mode 100644 index 0000000000..a0a0f0ebd3 --- /dev/null +++ b/src/modules/powerrename/dll/loc/de/src/modules/powerrename/dll/Resources.resx.lcl @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/dll/loc/es/src/modules/powerrename/dll/Resources.resx.lcl b/src/modules/powerrename/dll/loc/es/src/modules/powerrename/dll/Resources.resx.lcl new file mode 100644 index 0000000000..032aa00959 --- /dev/null +++ b/src/modules/powerrename/dll/loc/es/src/modules/powerrename/dll/Resources.resx.lcl @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/dll/loc/fr/src/modules/powerrename/dll/Resources.resx.lcl b/src/modules/powerrename/dll/loc/fr/src/modules/powerrename/dll/Resources.resx.lcl new file mode 100644 index 0000000000..2f70022493 --- /dev/null +++ b/src/modules/powerrename/dll/loc/fr/src/modules/powerrename/dll/Resources.resx.lcl @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/dll/loc/hu/src/modules/powerrename/dll/Resources.resx.lcl b/src/modules/powerrename/dll/loc/hu/src/modules/powerrename/dll/Resources.resx.lcl new file mode 100644 index 0000000000..2ad8a94df2 --- /dev/null +++ b/src/modules/powerrename/dll/loc/hu/src/modules/powerrename/dll/Resources.resx.lcl @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/dll/loc/it/src/modules/powerrename/dll/Resources.resx.lcl b/src/modules/powerrename/dll/loc/it/src/modules/powerrename/dll/Resources.resx.lcl new file mode 100644 index 0000000000..bbe5f5aa47 --- /dev/null +++ b/src/modules/powerrename/dll/loc/it/src/modules/powerrename/dll/Resources.resx.lcl @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/dll/loc/ja/src/modules/powerrename/dll/Resources.resx.lcl b/src/modules/powerrename/dll/loc/ja/src/modules/powerrename/dll/Resources.resx.lcl new file mode 100644 index 0000000000..387226a507 --- /dev/null +++ b/src/modules/powerrename/dll/loc/ja/src/modules/powerrename/dll/Resources.resx.lcl @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/dll/loc/ko/src/modules/powerrename/dll/Resources.resx.lcl b/src/modules/powerrename/dll/loc/ko/src/modules/powerrename/dll/Resources.resx.lcl new file mode 100644 index 0000000000..4aa3126296 --- /dev/null +++ b/src/modules/powerrename/dll/loc/ko/src/modules/powerrename/dll/Resources.resx.lcl @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/dll/loc/nl/src/modules/powerrename/dll/Resources.resx.lcl b/src/modules/powerrename/dll/loc/nl/src/modules/powerrename/dll/Resources.resx.lcl new file mode 100644 index 0000000000..bd9e79ea04 --- /dev/null +++ b/src/modules/powerrename/dll/loc/nl/src/modules/powerrename/dll/Resources.resx.lcl @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/dll/loc/pl/src/modules/powerrename/dll/Resources.resx.lcl b/src/modules/powerrename/dll/loc/pl/src/modules/powerrename/dll/Resources.resx.lcl new file mode 100644 index 0000000000..cac28739fb --- /dev/null +++ b/src/modules/powerrename/dll/loc/pl/src/modules/powerrename/dll/Resources.resx.lcl @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/dll/loc/pt-BR/src/modules/powerrename/dll/Resources.resx.lcl b/src/modules/powerrename/dll/loc/pt-BR/src/modules/powerrename/dll/Resources.resx.lcl new file mode 100644 index 0000000000..d4bf80db60 --- /dev/null +++ b/src/modules/powerrename/dll/loc/pt-BR/src/modules/powerrename/dll/Resources.resx.lcl @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/dll/loc/pt-PT/src/modules/powerrename/dll/Resources.resx.lcl b/src/modules/powerrename/dll/loc/pt-PT/src/modules/powerrename/dll/Resources.resx.lcl new file mode 100644 index 0000000000..10147f0a1c --- /dev/null +++ b/src/modules/powerrename/dll/loc/pt-PT/src/modules/powerrename/dll/Resources.resx.lcl @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/dll/loc/ru/src/modules/powerrename/dll/Resources.resx.lcl b/src/modules/powerrename/dll/loc/ru/src/modules/powerrename/dll/Resources.resx.lcl new file mode 100644 index 0000000000..59bb934c0d --- /dev/null +++ b/src/modules/powerrename/dll/loc/ru/src/modules/powerrename/dll/Resources.resx.lcl @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/dll/loc/sv/src/modules/powerrename/dll/Resources.resx.lcl b/src/modules/powerrename/dll/loc/sv/src/modules/powerrename/dll/Resources.resx.lcl new file mode 100644 index 0000000000..df3de7ce4d --- /dev/null +++ b/src/modules/powerrename/dll/loc/sv/src/modules/powerrename/dll/Resources.resx.lcl @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/dll/loc/tr/src/modules/powerrename/dll/Resources.resx.lcl b/src/modules/powerrename/dll/loc/tr/src/modules/powerrename/dll/Resources.resx.lcl new file mode 100644 index 0000000000..2a01cde1f5 --- /dev/null +++ b/src/modules/powerrename/dll/loc/tr/src/modules/powerrename/dll/Resources.resx.lcl @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/dll/loc/zh-Hans/src/modules/powerrename/dll/Resources.resx.lcl b/src/modules/powerrename/dll/loc/zh-Hans/src/modules/powerrename/dll/Resources.resx.lcl new file mode 100644 index 0000000000..4ad8d28baa --- /dev/null +++ b/src/modules/powerrename/dll/loc/zh-Hans/src/modules/powerrename/dll/Resources.resx.lcl @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/dll/loc/zh-Hant/src/modules/powerrename/dll/Resources.resx.lcl b/src/modules/powerrename/dll/loc/zh-Hant/src/modules/powerrename/dll/Resources.resx.lcl new file mode 100644 index 0000000000..daa102fe38 --- /dev/null +++ b/src/modules/powerrename/dll/loc/zh-Hant/src/modules/powerrename/dll/Resources.resx.lcl @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/lib/Settings.cpp b/src/modules/powerrename/lib/Settings.cpp index 9de15f6b68..b0d9b87b87 100644 --- a/src/modules/powerrename/lib/Settings.cpp +++ b/src/modules/powerrename/lib/Settings.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace { @@ -92,7 +93,7 @@ public: pushIdx(0), nextIdx(1), size(size), - jsonFilePath(PTSettingsHelper::get_module_save_folder_location(L"PowerRename") + filePath), + jsonFilePath(PTSettingsHelper::get_module_save_folder_location(PowerRenameConstants::ModuleKey) + filePath), registryFilePath(regPath) { items.resize(size); @@ -395,7 +396,7 @@ IFACEMETHODIMP CRenameMRU::AddMRUString(_In_ PCWSTR entry) CSettings::CSettings() { - std::wstring result = PTSettingsHelper::get_module_save_folder_location(L"PowerRename"); + std::wstring result = PTSettingsHelper::get_module_save_folder_location(PowerRenameConstants::ModuleKey); jsonFilePath = result + std::wstring(c_powerRenameDataFilePath); UIFlagsFilePath = result + std::wstring(c_powerRenameUIFlagsFilePath); Load(); diff --git a/src/modules/powerrename/ui/PowerRename.ico b/src/modules/powerrename/ui/PowerRename.ico index e365002805..4f111feaac 100644 Binary files a/src/modules/powerrename/ui/PowerRename.ico and b/src/modules/powerrename/ui/PowerRename.ico differ diff --git a/src/modules/powerrename/ui/loc/cs/src/modules/powerrename/ui/Resources.resx.lcl b/src/modules/powerrename/ui/loc/cs/src/modules/powerrename/ui/Resources.resx.lcl new file mode 100644 index 0000000000..7907dc9ef5 --- /dev/null +++ b/src/modules/powerrename/ui/loc/cs/src/modules/powerrename/ui/Resources.resx.lcl @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/ui/loc/de/src/modules/powerrename/ui/Resources.resx.lcl b/src/modules/powerrename/ui/loc/de/src/modules/powerrename/ui/Resources.resx.lcl new file mode 100644 index 0000000000..a261e2db06 --- /dev/null +++ b/src/modules/powerrename/ui/loc/de/src/modules/powerrename/ui/Resources.resx.lcl @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/ui/loc/es/src/modules/powerrename/ui/Resources.resx.lcl b/src/modules/powerrename/ui/loc/es/src/modules/powerrename/ui/Resources.resx.lcl new file mode 100644 index 0000000000..9c0284dbb7 --- /dev/null +++ b/src/modules/powerrename/ui/loc/es/src/modules/powerrename/ui/Resources.resx.lcl @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/ui/loc/fr/src/modules/powerrename/ui/Resources.resx.lcl b/src/modules/powerrename/ui/loc/fr/src/modules/powerrename/ui/Resources.resx.lcl new file mode 100644 index 0000000000..db6d7cc702 --- /dev/null +++ b/src/modules/powerrename/ui/loc/fr/src/modules/powerrename/ui/Resources.resx.lcl @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/ui/loc/hu/src/modules/powerrename/ui/Resources.resx.lcl b/src/modules/powerrename/ui/loc/hu/src/modules/powerrename/ui/Resources.resx.lcl new file mode 100644 index 0000000000..2f16c06802 --- /dev/null +++ b/src/modules/powerrename/ui/loc/hu/src/modules/powerrename/ui/Resources.resx.lcl @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/ui/loc/it/src/modules/powerrename/ui/Resources.resx.lcl b/src/modules/powerrename/ui/loc/it/src/modules/powerrename/ui/Resources.resx.lcl new file mode 100644 index 0000000000..e7f5b47c22 --- /dev/null +++ b/src/modules/powerrename/ui/loc/it/src/modules/powerrename/ui/Resources.resx.lcl @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/ui/loc/ja/src/modules/powerrename/ui/Resources.resx.lcl b/src/modules/powerrename/ui/loc/ja/src/modules/powerrename/ui/Resources.resx.lcl new file mode 100644 index 0000000000..3727839476 --- /dev/null +++ b/src/modules/powerrename/ui/loc/ja/src/modules/powerrename/ui/Resources.resx.lcl @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/ui/loc/ko/src/modules/powerrename/ui/Resources.resx.lcl b/src/modules/powerrename/ui/loc/ko/src/modules/powerrename/ui/Resources.resx.lcl new file mode 100644 index 0000000000..79d4336d1b --- /dev/null +++ b/src/modules/powerrename/ui/loc/ko/src/modules/powerrename/ui/Resources.resx.lcl @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/ui/loc/nl/src/modules/powerrename/ui/Resources.resx.lcl b/src/modules/powerrename/ui/loc/nl/src/modules/powerrename/ui/Resources.resx.lcl new file mode 100644 index 0000000000..3bdc44ab47 --- /dev/null +++ b/src/modules/powerrename/ui/loc/nl/src/modules/powerrename/ui/Resources.resx.lcl @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/ui/loc/pl/src/modules/powerrename/ui/Resources.resx.lcl b/src/modules/powerrename/ui/loc/pl/src/modules/powerrename/ui/Resources.resx.lcl new file mode 100644 index 0000000000..94b2680ebc --- /dev/null +++ b/src/modules/powerrename/ui/loc/pl/src/modules/powerrename/ui/Resources.resx.lcl @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/ui/loc/pt-BR/src/modules/powerrename/ui/Resources.resx.lcl b/src/modules/powerrename/ui/loc/pt-BR/src/modules/powerrename/ui/Resources.resx.lcl new file mode 100644 index 0000000000..ac6c4215a8 --- /dev/null +++ b/src/modules/powerrename/ui/loc/pt-BR/src/modules/powerrename/ui/Resources.resx.lcl @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/ui/loc/pt-PT/src/modules/powerrename/ui/Resources.resx.lcl b/src/modules/powerrename/ui/loc/pt-PT/src/modules/powerrename/ui/Resources.resx.lcl new file mode 100644 index 0000000000..a2d6a3fdb2 --- /dev/null +++ b/src/modules/powerrename/ui/loc/pt-PT/src/modules/powerrename/ui/Resources.resx.lcl @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/ui/loc/ru/src/modules/powerrename/ui/Resources.resx.lcl b/src/modules/powerrename/ui/loc/ru/src/modules/powerrename/ui/Resources.resx.lcl new file mode 100644 index 0000000000..97e05024d5 --- /dev/null +++ b/src/modules/powerrename/ui/loc/ru/src/modules/powerrename/ui/Resources.resx.lcl @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/ui/loc/sv/src/modules/powerrename/ui/Resources.resx.lcl b/src/modules/powerrename/ui/loc/sv/src/modules/powerrename/ui/Resources.resx.lcl new file mode 100644 index 0000000000..9063c0ff99 --- /dev/null +++ b/src/modules/powerrename/ui/loc/sv/src/modules/powerrename/ui/Resources.resx.lcl @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/ui/loc/tr/src/modules/powerrename/ui/Resources.resx.lcl b/src/modules/powerrename/ui/loc/tr/src/modules/powerrename/ui/Resources.resx.lcl new file mode 100644 index 0000000000..a7b522b146 --- /dev/null +++ b/src/modules/powerrename/ui/loc/tr/src/modules/powerrename/ui/Resources.resx.lcl @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/ui/loc/zh-Hans/src/modules/powerrename/ui/Resources.resx.lcl b/src/modules/powerrename/ui/loc/zh-Hans/src/modules/powerrename/ui/Resources.resx.lcl new file mode 100644 index 0000000000..15d6097287 --- /dev/null +++ b/src/modules/powerrename/ui/loc/zh-Hans/src/modules/powerrename/ui/Resources.resx.lcl @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/powerrename/ui/loc/zh-Hant/src/modules/powerrename/ui/Resources.resx.lcl b/src/modules/powerrename/ui/loc/zh-Hant/src/modules/powerrename/ui/Resources.resx.lcl new file mode 100644 index 0000000000..229d21e8d8 --- /dev/null +++ b/src/modules/powerrename/ui/loc/zh-Hant/src/modules/powerrename/ui/Resources.resx.lcl @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/MarkdownPreviewHandler/MarkdownPreviewHandler.cs b/src/modules/previewpane/MarkdownPreviewHandler/MarkdownPreviewHandler.cs index 156e635f54..6e9504831b 100644 --- a/src/modules/previewpane/MarkdownPreviewHandler/MarkdownPreviewHandler.cs +++ b/src/modules/previewpane/MarkdownPreviewHandler/MarkdownPreviewHandler.cs @@ -20,6 +20,14 @@ namespace Microsoft.PowerToys.PreviewHandler.Markdown private MarkdownPreviewHandlerControl _markdownPreviewHandlerControl; private bool disposedValue; + /// + /// Initializes a new instance of the class. + /// + public MarkdownPreviewHandler() + { + Initialize(); + } + /// public override void DoPreview() { diff --git a/src/modules/previewpane/MarkdownPreviewHandler/MarkdownPreviewHandler.csproj b/src/modules/previewpane/MarkdownPreviewHandler/MarkdownPreviewHandler.csproj index dbce2530bb..d120d518d0 100644 --- a/src/modules/previewpane/MarkdownPreviewHandler/MarkdownPreviewHandler.csproj +++ b/src/modules/previewpane/MarkdownPreviewHandler/MarkdownPreviewHandler.csproj @@ -158,6 +158,7 @@ ResXFileCodeGenerator Resources.Designer.cs + diff --git a/src/modules/previewpane/MarkdownPreviewHandler/loc/cs/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl b/src/modules/previewpane/MarkdownPreviewHandler/loc/cs/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..8063865f0c --- /dev/null +++ b/src/modules/previewpane/MarkdownPreviewHandler/loc/cs/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/MarkdownPreviewHandler/loc/de/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl b/src/modules/previewpane/MarkdownPreviewHandler/loc/de/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..972ea0a778 --- /dev/null +++ b/src/modules/previewpane/MarkdownPreviewHandler/loc/de/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/MarkdownPreviewHandler/loc/es/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl b/src/modules/previewpane/MarkdownPreviewHandler/loc/es/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..f17ce6f1d7 --- /dev/null +++ b/src/modules/previewpane/MarkdownPreviewHandler/loc/es/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/MarkdownPreviewHandler/loc/fr/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl b/src/modules/previewpane/MarkdownPreviewHandler/loc/fr/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..24d036a7d8 --- /dev/null +++ b/src/modules/previewpane/MarkdownPreviewHandler/loc/fr/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/MarkdownPreviewHandler/loc/hu/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl b/src/modules/previewpane/MarkdownPreviewHandler/loc/hu/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..72f9ed8884 --- /dev/null +++ b/src/modules/previewpane/MarkdownPreviewHandler/loc/hu/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/MarkdownPreviewHandler/loc/it/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl b/src/modules/previewpane/MarkdownPreviewHandler/loc/it/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..3044fa4f87 --- /dev/null +++ b/src/modules/previewpane/MarkdownPreviewHandler/loc/it/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/MarkdownPreviewHandler/loc/ja/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl b/src/modules/previewpane/MarkdownPreviewHandler/loc/ja/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..4f596a23c7 --- /dev/null +++ b/src/modules/previewpane/MarkdownPreviewHandler/loc/ja/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/MarkdownPreviewHandler/loc/ko/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl b/src/modules/previewpane/MarkdownPreviewHandler/loc/ko/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..ddd5cca4b7 --- /dev/null +++ b/src/modules/previewpane/MarkdownPreviewHandler/loc/ko/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/MarkdownPreviewHandler/loc/nl/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl b/src/modules/previewpane/MarkdownPreviewHandler/loc/nl/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..6e3ac04901 --- /dev/null +++ b/src/modules/previewpane/MarkdownPreviewHandler/loc/nl/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/MarkdownPreviewHandler/loc/pl/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl b/src/modules/previewpane/MarkdownPreviewHandler/loc/pl/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..a7a8bd701d --- /dev/null +++ b/src/modules/previewpane/MarkdownPreviewHandler/loc/pl/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/MarkdownPreviewHandler/loc/pt-BR/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl b/src/modules/previewpane/MarkdownPreviewHandler/loc/pt-BR/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..e8ec4cf7bf --- /dev/null +++ b/src/modules/previewpane/MarkdownPreviewHandler/loc/pt-BR/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/MarkdownPreviewHandler/loc/pt-PT/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl b/src/modules/previewpane/MarkdownPreviewHandler/loc/pt-PT/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..0422ccfb89 --- /dev/null +++ b/src/modules/previewpane/MarkdownPreviewHandler/loc/pt-PT/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/MarkdownPreviewHandler/loc/ru/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl b/src/modules/previewpane/MarkdownPreviewHandler/loc/ru/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..cf883f875d --- /dev/null +++ b/src/modules/previewpane/MarkdownPreviewHandler/loc/ru/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/MarkdownPreviewHandler/loc/sv/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl b/src/modules/previewpane/MarkdownPreviewHandler/loc/sv/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..2eeec89ce4 --- /dev/null +++ b/src/modules/previewpane/MarkdownPreviewHandler/loc/sv/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/MarkdownPreviewHandler/loc/tr/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl b/src/modules/previewpane/MarkdownPreviewHandler/loc/tr/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..47a03ddb4c --- /dev/null +++ b/src/modules/previewpane/MarkdownPreviewHandler/loc/tr/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/MarkdownPreviewHandler/loc/zh-Hans/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl b/src/modules/previewpane/MarkdownPreviewHandler/loc/zh-Hans/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..ed673eb475 --- /dev/null +++ b/src/modules/previewpane/MarkdownPreviewHandler/loc/zh-Hans/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/MarkdownPreviewHandler/loc/zh-Hant/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl b/src/modules/previewpane/MarkdownPreviewHandler/loc/zh-Hant/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl new file mode 100644 index 0000000000..bb48152c18 --- /dev/null +++ b/src/modules/previewpane/MarkdownPreviewHandler/loc/zh-Hant/src/modules/previewpane/MarkDownPreviewHandler/Properties/Resources.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/PreviewPaneUnitTests/HTMLParsingExtensionTest.cs b/src/modules/previewpane/PreviewPaneUnitTests/HTMLParsingExtensionTest.cs index f632d988fa..31fdc0577b 100644 --- a/src/modules/previewpane/PreviewPaneUnitTests/HTMLParsingExtensionTest.cs +++ b/src/modules/previewpane/PreviewPaneUnitTests/HTMLParsingExtensionTest.cs @@ -31,7 +31,8 @@ namespace PreviewPaneUnitTests string html = Markdown.ToHtml(mdString, markdownPipeline); // Assert - Assert.AreEqual(html, "\n\n\n\n\n\n\n
AB
\n"); + const string expected = "\n\n\n\n\n\n\n
AB
\n"; + Assert.AreEqual(expected, html); } [TestMethod] @@ -46,7 +47,8 @@ namespace PreviewPaneUnitTests string html = Markdown.ToHtml(mdString, markdownPipeline); // Assert - Assert.AreEqual(html, "
\n

Blockquotes.

\n
\n"); + const string expected = "
\n

Blockquotes.

\n
\n"; + Assert.AreEqual(expected, html); } [TestMethod] @@ -61,7 +63,8 @@ namespace PreviewPaneUnitTests string html = Markdown.ToHtml(mdString, markdownPipeline); // Assert - Assert.AreEqual(html, "

\"text\"

\n"); + const string expected = "

\"text\"

\n"; + Assert.AreEqual(expected, html); } [TestMethod] @@ -76,7 +79,8 @@ namespace PreviewPaneUnitTests string html = Markdown.ToHtml(mdString, markdownPipeline); // Assert - Assert.AreEqual(html, "
\n
This is a caption
\n
\n"); + const string expected = "
\n
This is a caption
\n
\n"; + Assert.AreEqual(expected, html); } [TestMethod] @@ -92,8 +96,9 @@ namespace PreviewPaneUnitTests string html = Markdown.ToHtml(mdString, markdownPipeline); // Assert - Assert.AreEqual(count, 1); - Assert.AreEqual(html, "

\"text\"

\n"); + Assert.AreEqual(1, count); + const string expected = "

\"text\"

\n"; + Assert.AreEqual(expected, html); } } } diff --git a/src/modules/previewpane/PreviewPaneUnitTests/MarkdownPreviewHandlerTest.cs b/src/modules/previewpane/PreviewPaneUnitTests/MarkdownPreviewHandlerTest.cs index 1efed2aa50..8986926ab5 100644 --- a/src/modules/previewpane/PreviewPaneUnitTests/MarkdownPreviewHandlerTest.cs +++ b/src/modules/previewpane/PreviewPaneUnitTests/MarkdownPreviewHandlerTest.cs @@ -23,7 +23,7 @@ namespace PreviewPaneUnitTests markdownPreviewHandlerControl.DoPreview("HelperFiles/MarkdownWithExternalImage.txt"); // Assert - Assert.AreEqual(markdownPreviewHandlerControl.Controls.Count, 2); + Assert.AreEqual(2, markdownPreviewHandlerControl.Controls.Count); Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[0], typeof(WebBrowserExt)); } } @@ -38,7 +38,7 @@ namespace PreviewPaneUnitTests markdownPreviewHandlerControl.DoPreview("HelperFiles/MarkdownWithExternalImage.txt"); // Assert - Assert.AreEqual(markdownPreviewHandlerControl.Controls.Count, 2); + Assert.AreEqual(2, markdownPreviewHandlerControl.Controls.Count); Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[1], typeof(RichTextBox)); } } @@ -53,7 +53,7 @@ namespace PreviewPaneUnitTests markdownPreviewHandlerControl.DoPreview("HelperFiles/MarkdownWithHTMLImageTag.txt"); // Assert - Assert.AreEqual(markdownPreviewHandlerControl.Controls.Count, 2); + Assert.AreEqual(2, markdownPreviewHandlerControl.Controls.Count); Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[1], typeof(RichTextBox)); } } @@ -68,7 +68,7 @@ namespace PreviewPaneUnitTests markdownPreviewHandlerControl.DoPreview("HelperFiles/MarkdownWithScript.txt"); // Assert - Assert.AreEqual(markdownPreviewHandlerControl.Controls.Count, 1); + Assert.AreEqual(1, markdownPreviewHandlerControl.Controls.Count); Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[0], typeof(WebBrowserExt)); } } @@ -85,11 +85,11 @@ namespace PreviewPaneUnitTests // Assert Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[0], typeof(WebBrowserExt)); Assert.IsNotNull(((WebBrowser)markdownPreviewHandlerControl.Controls[0]).DocumentText); - Assert.AreEqual(((WebBrowser)markdownPreviewHandlerControl.Controls[0]).Dock, DockStyle.Fill); - Assert.AreEqual(((WebBrowser)markdownPreviewHandlerControl.Controls[0]).IsWebBrowserContextMenuEnabled, false); - Assert.AreEqual(((WebBrowser)markdownPreviewHandlerControl.Controls[0]).ScriptErrorsSuppressed, true); - Assert.AreEqual(((WebBrowser)markdownPreviewHandlerControl.Controls[0]).ScrollBarsEnabled, true); - Assert.AreEqual(((WebBrowser)markdownPreviewHandlerControl.Controls[0]).AllowNavigation, false); + Assert.AreEqual(DockStyle.Fill, ((WebBrowser)markdownPreviewHandlerControl.Controls[0]).Dock); + Assert.AreEqual(false, ((WebBrowser)markdownPreviewHandlerControl.Controls[0]).IsWebBrowserContextMenuEnabled); + Assert.AreEqual(true, ((WebBrowser)markdownPreviewHandlerControl.Controls[0]).ScriptErrorsSuppressed); + Assert.AreEqual(true, ((WebBrowser)markdownPreviewHandlerControl.Controls[0]).ScrollBarsEnabled); + Assert.AreEqual(false, ((WebBrowser)markdownPreviewHandlerControl.Controls[0]).AllowNavigation); } } @@ -105,10 +105,10 @@ namespace PreviewPaneUnitTests // Assert Assert.IsInstanceOfType(markdownPreviewHandlerControl.Controls[1], typeof(RichTextBox)); Assert.IsNotNull(((RichTextBox)markdownPreviewHandlerControl.Controls[1]).Text); - Assert.AreEqual(((RichTextBox)markdownPreviewHandlerControl.Controls[1]).Dock, DockStyle.Top); - Assert.AreEqual(((RichTextBox)markdownPreviewHandlerControl.Controls[1]).BorderStyle, BorderStyle.None); - Assert.AreEqual(((RichTextBox)markdownPreviewHandlerControl.Controls[1]).BackColor, Color.LightYellow); - Assert.AreEqual(((RichTextBox)markdownPreviewHandlerControl.Controls[1]).Multiline, true); + Assert.AreEqual(DockStyle.Top, ((RichTextBox)markdownPreviewHandlerControl.Controls[1]).Dock); + Assert.AreEqual(BorderStyle.None, ((RichTextBox)markdownPreviewHandlerControl.Controls[1]).BorderStyle); + Assert.AreEqual(Color.LightYellow, ((RichTextBox)markdownPreviewHandlerControl.Controls[1]).BackColor); + Assert.AreEqual(true, ((RichTextBox)markdownPreviewHandlerControl.Controls[1]).Multiline); } } } diff --git a/src/modules/previewpane/README.md b/src/modules/previewpane/README.md index 86beebb93d..b787fd9c5c 100644 --- a/src/modules/previewpane/README.md +++ b/src/modules/previewpane/README.md @@ -83,7 +83,7 @@ FileExplorerPreviewSettings( ## Installation -### MSI +### MSI (Recommended) To add a new Previewer update the `Product.wxs` file in `PowerToysSetup` similar to existing Previewer to register the Preview Handler. More details about registration of Preview Handlers can be [found here.](https://docs.microsoft.com/en-us/windows/win32/shell/how-to-register-a-preview-handler) @@ -123,6 +123,38 @@ To add a new Previewer update the `Product.wxs` file in `PowerToysSetup` similar ``` +### Directly registering/unregistering DLL's +**[Important] This method of registering Preview Handler DLL's is not recommended. It could lead to registry corruption.** +#### Registering Preview Handler +1. Restart Visual studio as administrator. +2. Sign `XYZPreviewHandler` and it's dependencies. To sign an assembly in VS, follow steps given [here](https://docs.microsoft.com/en-us/dotnet/standard/assembly/sign-strong-name#create-and-sign-an-assembly-with-a-strong-name-by-using-visual-studio). +3. Build `XYZPreviewHandler` project. +4. Open developer command prompt from `Tools > Command Line > Developer Command Prompt`. +5. Run following command for each nuget and project dependency to add them to Global Assembly Cache(GAC). +``` +gacutil -i +``` +6. Run following commands to register preview handler. +``` +cd C:\Windows\Microsoft.NET\Framework64\4.0.x.x +gacutil -i +RegAsm.exe /codebase +``` +7. Restart Windows Explorer process. + +#### Unregistering Preview Handler +1. Run following commands in elevated developer command prompt to unregister preview handler. +``` +cd C:\Windows\Microsoft.NET\Framework64\4.0.x.x +RegAsm.exe /unregister +gacutil -u XYZPreviewHandler +``` + +## Debugging +Since in-process preview handlers run under a surrogate hosting process (prevhost.exe by default), to debug a preview handler, you need to attach the debugger to the host process. +1. Click on a file with registered extension to start host process. +2. Attach debugger in Visual studio from `Debug->Attach to Process` and select `prevhost.exe` with type `Managed(version), x64`. + ## Managing Preview Handlers After successful integration, your preview handler should appear in the PowerToys settings UI under the `File Explorer Preview` Tab. In here you should be able to enable and disable your preview handler. diff --git a/src/modules/previewpane/SvgPreviewHandler/SvgPreviewControl.cs b/src/modules/previewpane/SvgPreviewHandler/SvgPreviewControl.cs index 9a78ae7a98..21db1eda68 100644 --- a/src/modules/previewpane/SvgPreviewHandler/SvgPreviewControl.cs +++ b/src/modules/previewpane/SvgPreviewHandler/SvgPreviewControl.cs @@ -48,7 +48,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg { _infoBarAdded = false; string svgData = null; - using (var stream = new StreamWrapper(dataSource as IStream)) + using (var stream = new ReadonlyStream(dataSource as IStream)) { using (var reader = new StreamReader(stream)) { diff --git a/src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.cs b/src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.cs index 08d6519d73..dbf0a340ad 100644 --- a/src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.cs +++ b/src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.cs @@ -20,6 +20,14 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg private SvgPreviewControl _svgPreviewControl; private bool disposedValue; + /// + /// Initializes a new instance of the class. + /// + public SvgPreviewHandler() + { + Initialize(); + } + /// public override void DoPreview() { diff --git a/src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.csproj b/src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.csproj index 4d9863b0db..5366284d08 100644 --- a/src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.csproj +++ b/src/modules/previewpane/SvgPreviewHandler/SvgPreviewHandler.csproj @@ -148,7 +148,8 @@ ResXFileCodeGenerator Resource.Designer.cs - + +
\ No newline at end of file diff --git a/src/modules/previewpane/SvgPreviewHandler/loc/cs/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl b/src/modules/previewpane/SvgPreviewHandler/loc/cs/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl new file mode 100644 index 0000000000..bcc3bb0439 --- /dev/null +++ b/src/modules/previewpane/SvgPreviewHandler/loc/cs/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/SvgPreviewHandler/loc/de/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl b/src/modules/previewpane/SvgPreviewHandler/loc/de/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl new file mode 100644 index 0000000000..650f20155e --- /dev/null +++ b/src/modules/previewpane/SvgPreviewHandler/loc/de/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/SvgPreviewHandler/loc/es/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl b/src/modules/previewpane/SvgPreviewHandler/loc/es/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl new file mode 100644 index 0000000000..f00eb459f2 --- /dev/null +++ b/src/modules/previewpane/SvgPreviewHandler/loc/es/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/SvgPreviewHandler/loc/fr/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl b/src/modules/previewpane/SvgPreviewHandler/loc/fr/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl new file mode 100644 index 0000000000..8df5e618da --- /dev/null +++ b/src/modules/previewpane/SvgPreviewHandler/loc/fr/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/SvgPreviewHandler/loc/hu/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl b/src/modules/previewpane/SvgPreviewHandler/loc/hu/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl new file mode 100644 index 0000000000..546269db01 --- /dev/null +++ b/src/modules/previewpane/SvgPreviewHandler/loc/hu/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/SvgPreviewHandler/loc/it/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl b/src/modules/previewpane/SvgPreviewHandler/loc/it/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl new file mode 100644 index 0000000000..d3d218931d --- /dev/null +++ b/src/modules/previewpane/SvgPreviewHandler/loc/it/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/SvgPreviewHandler/loc/ja/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl b/src/modules/previewpane/SvgPreviewHandler/loc/ja/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl new file mode 100644 index 0000000000..b642fa09a0 --- /dev/null +++ b/src/modules/previewpane/SvgPreviewHandler/loc/ja/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/SvgPreviewHandler/loc/ko/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl b/src/modules/previewpane/SvgPreviewHandler/loc/ko/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl new file mode 100644 index 0000000000..9664956c96 --- /dev/null +++ b/src/modules/previewpane/SvgPreviewHandler/loc/ko/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/SvgPreviewHandler/loc/nl/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl b/src/modules/previewpane/SvgPreviewHandler/loc/nl/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl new file mode 100644 index 0000000000..70ca7af999 --- /dev/null +++ b/src/modules/previewpane/SvgPreviewHandler/loc/nl/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/SvgPreviewHandler/loc/pl/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl b/src/modules/previewpane/SvgPreviewHandler/loc/pl/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl new file mode 100644 index 0000000000..d54e6fedae --- /dev/null +++ b/src/modules/previewpane/SvgPreviewHandler/loc/pl/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/SvgPreviewHandler/loc/pt-BR/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl b/src/modules/previewpane/SvgPreviewHandler/loc/pt-BR/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl new file mode 100644 index 0000000000..1fc8175df5 --- /dev/null +++ b/src/modules/previewpane/SvgPreviewHandler/loc/pt-BR/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/SvgPreviewHandler/loc/pt-PT/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl b/src/modules/previewpane/SvgPreviewHandler/loc/pt-PT/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl new file mode 100644 index 0000000000..b47f1a873c --- /dev/null +++ b/src/modules/previewpane/SvgPreviewHandler/loc/pt-PT/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/SvgPreviewHandler/loc/ru/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl b/src/modules/previewpane/SvgPreviewHandler/loc/ru/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl new file mode 100644 index 0000000000..62400b90ac --- /dev/null +++ b/src/modules/previewpane/SvgPreviewHandler/loc/ru/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/SvgPreviewHandler/loc/sv/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl b/src/modules/previewpane/SvgPreviewHandler/loc/sv/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl new file mode 100644 index 0000000000..b0575e550b --- /dev/null +++ b/src/modules/previewpane/SvgPreviewHandler/loc/sv/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/SvgPreviewHandler/loc/tr/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl b/src/modules/previewpane/SvgPreviewHandler/loc/tr/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl new file mode 100644 index 0000000000..b19227bb70 --- /dev/null +++ b/src/modules/previewpane/SvgPreviewHandler/loc/tr/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/SvgPreviewHandler/loc/zh-Hans/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl b/src/modules/previewpane/SvgPreviewHandler/loc/zh-Hans/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl new file mode 100644 index 0000000000..462f99a0d0 --- /dev/null +++ b/src/modules/previewpane/SvgPreviewHandler/loc/zh-Hans/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/SvgPreviewHandler/loc/zh-Hant/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl b/src/modules/previewpane/SvgPreviewHandler/loc/zh-Hant/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl new file mode 100644 index 0000000000..e6d34f8222 --- /dev/null +++ b/src/modules/previewpane/SvgPreviewHandler/loc/zh-Hant/src/modules/previewpane/SvgPreviewHandler/Resource.resx.lcl @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/SvgThumbnailProvider/SvgThumbnailProvider.cs b/src/modules/previewpane/SvgThumbnailProvider/SvgThumbnailProvider.cs index 925091ae7b..52f52aa00c 100644 --- a/src/modules/previewpane/SvgThumbnailProvider/SvgThumbnailProvider.cs +++ b/src/modules/previewpane/SvgThumbnailProvider/SvgThumbnailProvider.cs @@ -225,7 +225,7 @@ namespace Microsoft.PowerToys.ThumbnailHandler.Svg } string svgData = null; - using (var stream = new StreamWrapper(this.Stream as IStream)) + using (var stream = new ReadonlyStream(this.Stream as IStream)) { using (var reader = new StreamReader(stream)) { diff --git a/src/modules/previewpane/UnitTests-PreviewHandlerCommon/FileBasedPreviewHandlerTests.cs b/src/modules/previewpane/UnitTests-PreviewHandlerCommon/FileBasedPreviewHandlerTests.cs index 5a5147840f..12a854ee49 100644 --- a/src/modules/previewpane/UnitTests-PreviewHandlerCommon/FileBasedPreviewHandlerTests.cs +++ b/src/modules/previewpane/UnitTests-PreviewHandlerCommon/FileBasedPreviewHandlerTests.cs @@ -7,12 +7,12 @@ using Common; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -namespace UnitTests_PreviewHandlerCommon +namespace PreviewHandlerCommonUnitTests { [TestClass] public class FileBasedPreviewHandlerTests { - public class TestFileBasedPreviewHandler : FileBasedPreviewHandler + internal class TestFileBasedPreviewHandler : FileBasedPreviewHandler { public override void DoPreview() { @@ -28,7 +28,7 @@ namespace UnitTests_PreviewHandlerCommon [DataTestMethod] [DataRow(0U)] [DataRow(1U)] - public void FileBasedPreviewHandler_ShouldSetFilePath_WhenInitializeCalled(uint grfMode) + public void FileBasedPreviewHandlerShouldSetFilePathWhenInitializeCalled(uint grfMode) { // Arrange var fileBasedPreviewHandler = new TestFileBasedPreviewHandler(); @@ -38,7 +38,7 @@ namespace UnitTests_PreviewHandlerCommon fileBasedPreviewHandler.Initialize(filePath, grfMode); // Assert - Assert.AreEqual(fileBasedPreviewHandler.FilePath, filePath); + Assert.AreEqual(filePath, fileBasedPreviewHandler.FilePath); } } } diff --git a/src/modules/previewpane/UnitTests-PreviewHandlerCommon/FormHandlerControlTests.cs b/src/modules/previewpane/UnitTests-PreviewHandlerCommon/FormHandlerControlTests.cs index e539d9e43b..ebf996b0f8 100644 --- a/src/modules/previewpane/UnitTests-PreviewHandlerCommon/FormHandlerControlTests.cs +++ b/src/modules/previewpane/UnitTests-PreviewHandlerCommon/FormHandlerControlTests.cs @@ -4,12 +4,12 @@ using System; using System.Drawing; -using System.Runtime.InteropServices; + using System.Windows.Forms; using Common; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace UnitTests_PreviewHandlerCommon +namespace PreviewHandlerCommonUnitTests { [TestClass] public class FormHandlerControlTests @@ -19,7 +19,7 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void FormHandlerControl_ShouldCreateHandle_OnInitialization() + public void FormHandlerControlShouldCreateHandleOnInitialization() { // Arrange and act using (var testFormHandlerControl = new TestFormControl()) @@ -30,7 +30,7 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void FormHandlerControl_ShouldSetVisibleFalse_OnInitialization() + public void FormHandlerControlShouldSetVisibleFalseOnInitialization() { // Arrange and act using (var testFormHandlerControl = new TestFormControl()) @@ -41,7 +41,7 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void FormHandlerControl_ShouldSetFormBorderStyle_OnInitialization() + public void FormHandlerControlShouldSetFormBorderStyleOnInitialization() { // Arrange and act using (var testFormHandlerControl = new TestFormControl()) @@ -52,13 +52,13 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void FormHandlerControl_ShouldReturnValidHandle_WhenGetHandleCalled() + public void FormHandlerControlShouldReturnValidHandleWhenGetHandleCalled() { // Arrange using (var testFormHandlerControl = new TestFormControl()) { // Act - var handle = testFormHandlerControl.GetHandle(); + var handle = testFormHandlerControl.Handle; // Assert Assert.AreEqual(testFormHandlerControl.Handle, handle); @@ -66,7 +66,7 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void FormHandlerControl_ShouldSetBackgroundColor_WhenSetBackgroundColorCalled() + public void FormHandlerControlShouldSetBackgroundColorWhenSetBackgroundColorCalled() { // Arrange using (var testFormHandlerControl = new TestFormControl()) @@ -82,23 +82,24 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void FormHandlerControl_ShouldSetFont_WhenSetFontCalled() + public void FormHandlerControlShouldSetFontWhenSetFontCalled() { // Arrange using (var testFormHandlerControl = new TestFormControl()) { - var font = new Font("Arial", 20); + using (var font = new Font("Arial", 20)) + { + // Act + testFormHandlerControl.SetFont(font); - // Act - testFormHandlerControl.SetFont(font); - - // Assert - Assert.AreEqual(font, testFormHandlerControl.Font); + // Assert + Assert.AreEqual(font, testFormHandlerControl.Font); + } } } [TestMethod] - public void FormHandlerControl_ShouldUpdateBounds_WhenSetRectCalled() + public void FormHandlerControlShouldUpdateBoundsWhenSetRectCalled() { // Arrange using (var testFormHandlerControl = new TestFormControl()) @@ -114,7 +115,7 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void FormHandlerControl_ShouldSetTextColor_WhenSetTextColorCalled() + public void FormHandlerControlShouldSetTextColorWhenSetTextColorCalled() { // Arrange using (var testFormHandlerControl = new TestFormControl()) @@ -130,7 +131,7 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void FormHandlerControl_ShouldClearAllControls_WhenUnloadCalled() + public void FormHandlerControlShouldClearAllControlsWhenUnloadCalled() { // Arrange using (var testFormHandlerControl = new TestFormControl()) @@ -147,7 +148,7 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void FormHandlerControl_ShouldSetVisibleFalse_WhenUnloadCalled() + public void FormHandlerControlShouldSetVisibleFalseWhenUnloadCalled() { // Arrange using (var testFormHandlerControl = new TestFormControl()) @@ -161,7 +162,7 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void FormHandlerControl_ShouldSetVisibletrue_WhenDoPreviewCalled() + public void FormHandlerControlShouldSetVisibletrueWhenDoPreviewCalled() { // Arrange using (var testFormHandlerControl = new TestFormControl()) @@ -175,26 +176,24 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void FormHandlerControl_ShouldSetParentHandle_WhenSetWindowCalled() + public void FormHandlerControlShouldSetParentHandleWhenSetWindowCalled() { // Arrange using (var testFormHandlerControl = new TestFormControl()) { - var parentFormWindow = new UserControl(); - var parentHwnd = parentFormWindow.Handle; - var rect = new Rectangle(2, 2, 4, 4); + using (var parentFormWindow = new UserControl()) + { + var parentHwnd = parentFormWindow.Handle; + var rect = new Rectangle(2, 2, 4, 4); - // Act - testFormHandlerControl.SetWindow(parentHwnd, rect); - var actualParentHwnd = GetAncestor(testFormHandlerControl.Handle, 1); // GA_PARENT 1 + // Act + testFormHandlerControl.SetWindow(parentHwnd, rect); + var actualParentHwnd = NativeMethods.GetAncestor(testFormHandlerControl.Handle, 1); // GA_PARENT 1 - // Assert - Assert.AreEqual(parentHwnd, actualParentHwnd); + // Assert + Assert.AreEqual(parentHwnd, actualParentHwnd); + } } } - - // Gets the ancestor window: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getancestor - [DllImport("user32.dll")] - private static extern IntPtr GetAncestor(IntPtr hWnd, uint gaFlags); } } diff --git a/src/modules/previewpane/UnitTests-PreviewHandlerCommon/NativeMethods.cs b/src/modules/previewpane/UnitTests-PreviewHandlerCommon/NativeMethods.cs new file mode 100644 index 0000000000..d84fddb457 --- /dev/null +++ b/src/modules/previewpane/UnitTests-PreviewHandlerCommon/NativeMethods.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. + +using System; +using System.Runtime.InteropServices; + +namespace PreviewHandlerCommonUnitTests +{ + public static class NativeMethods + { + // Gets the ancestor window: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getancestor + [DllImport("user32.dll")] + internal static extern IntPtr GetAncestor(IntPtr hWnd, uint gaFlags); + } +} diff --git a/src/modules/previewpane/UnitTests-PreviewHandlerCommon/PreviewHandlerBaseTests.cs b/src/modules/previewpane/UnitTests-PreviewHandlerCommon/PreviewHandlerBaseTests.cs index 6dba4ffa19..63dc505c5a 100644 --- a/src/modules/previewpane/UnitTests-PreviewHandlerCommon/PreviewHandlerBaseTests.cs +++ b/src/modules/previewpane/UnitTests-PreviewHandlerCommon/PreviewHandlerBaseTests.cs @@ -10,15 +10,20 @@ using Common.ComInterlop; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -namespace UnitTests_PreviewHandlerCommon +namespace PreviewHandlerCommonUnitTests { [TestClass] public class PreviewHandlerBaseTests { private static IPreviewHandlerControl previewHandlerControl; - public class TestPreviewHandler : PreviewHandlerBase + internal class TestPreviewHandler : PreviewHandlerBase { + public TestPreviewHandler() + { + Initialize(); + } + public override void DoPreview() { throw new NotImplementedException(); @@ -31,7 +36,7 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void PreviewHandlerBase_ShouldCallPreviewControlSetWindow_WhenSetWindowCalled() + public void PreviewHandlerBaseShouldCallPreviewControlSetWindowWhenSetWindowCalled() { // Arrange var mockPreviewControl = new Mock(); @@ -61,7 +66,7 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void PreviewHandlerBase_ShouldCallPreviewControlSetrect_WhenSetRectCalled() + public void PreviewHandlerBaseShouldCallPreviewControlSetrectWhenSetRectCalled() { // Arrange var mockPreviewControl = new Mock(); @@ -87,7 +92,7 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void PreviewHandlerBase_ShouldCallPreviewControlUnload_WhenUnloadCalled() + public void PreviewHandlerBaseShouldCallPreviewControlUnloadWhenUnloadCalled() { // Arrange var mockPreviewControl = new Mock(); @@ -103,7 +108,7 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void PreviewHandlerBase_ShouldCallPreviewControlSetBackgroundColor_WhenSetBackgroundColorCalled() + public void PreviewHandlerBaseShouldCallPreviewControlSetBackgroundColorWhenSetBackgroundColorCalled() { // Arrange var mockPreviewControl = new Mock(); @@ -120,7 +125,7 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void PreviewHandlerBase_ShouldCallPreviewControlSetTextColor_WhenSetTextColorCalled() + public void PreviewHandlerBaseShouldCallPreviewControlSetTextColorWhenSetTextColorCalled() { // Arrange var mockPreviewControl = new Mock(); @@ -137,7 +142,7 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void PreviewHandlerBase_ShouldCallPreviewControlSetFont_WhenSetFontCalled() + public void PreviewHandlerBaseShouldCallPreviewControlSetFontWhenSetFontCalled() { // Arrange Font actualFont = null; @@ -161,7 +166,7 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void PreviewHandlerBase_ShouldCallPreviewControlSetFocus_WhenSetFocusCalled() + public void PreviewHandlerBaseShouldCallPreviewControlSetFocusWhenSetFocusCalled() { // Arrange var mockPreviewControl = new Mock(); @@ -177,7 +182,7 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void PreviewHandlerBase_ShouldSetHandleOnQueryFocus_WhenPreviewControlsReturnValidHandle() + public void PreviewHandlerBaseShouldSetHandleOnQueryFocusWhenPreviewControlsReturnValidHandle() { // Arrange var hwnd = new IntPtr(5); @@ -197,7 +202,7 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void PreviewHandlerBase_ShouldThrowOnQueryFocus_WhenPreviewControlsReturnNotValidHandle() + public void PreviewHandlerBaseShouldThrowOnQueryFocusWhenPreviewControlsReturnNotValidHandle() { // Arrange var hwnd = IntPtr.Zero; @@ -225,7 +230,7 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void PreviewHandlerBase_ShouldDirectKeyStrokesToIPreviewHandlerFrame_IfIPreviewHandlerFrameIsSet() + public void PreviewHandlerBaseShouldDirectKeyStrokesToIPreviewHandlerFrameIfIPreviewHandlerFrameIsSet() { // Arrange var mockPreviewControl = new Mock(); @@ -246,7 +251,7 @@ namespace UnitTests_PreviewHandlerCommon [DataTestMethod] [DataRow(0U)] [DataRow(1U)] - public void PreviewHandlerBase_ShouldReturnIPreviewHandlerFrameResponse_IfIPreviewHandlerFrameIsSet(uint resultCode) + public void PreviewHandlerBaseShouldReturnIPreviewHandlerFrameResponseIfIPreviewHandlerFrameIsSet(uint resultCode) { // Arrange var mockPreviewControl = new Mock(); @@ -268,7 +273,7 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void PreviewHandlerBase_ShouldReturnS_FALSE_IfIPreviewHandlerFrameIsNotSet() + public void PreviewHandlerBaseShouldReturnUintFalseIfIPreviewHandlerFrameIsNotSet() { // Arrange var mockPreviewControl = new Mock(); @@ -286,12 +291,12 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void PreviewHandlerBase_ShouldReturnPreviewControlHandle_IfGetWindowCalled() + public void PreviewHandlerBaseShouldReturnPreviewControlHandleIfGetWindowCalled() { // Arrange var previewControlHandle = new IntPtr(5); var mockPreviewControl = new Mock(); - mockPreviewControl.Setup(x => x.GetHandle()) + mockPreviewControl.Setup(x => x.GetWindowHandle()) .Returns(previewControlHandle); previewHandlerControl = mockPreviewControl.Object; @@ -308,7 +313,7 @@ namespace UnitTests_PreviewHandlerCommon [DataTestMethod] [DataRow(true)] [DataRow(false)] - public void PreviewHandlerBase_ShouldThrowNotImplementedException_IfContextSensitiveHelpCalled(bool fEnterMode) + public void PreviewHandlerBaseShouldThrowNotImplementedExceptionIfContextSensitiveHelpCalled(bool fEnterMode) { // Arrange var mockPreviewControl = new Mock(); @@ -332,7 +337,7 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void PreviewHandlerBase_ShouldReturnSite_WhenGetSiteCalled() + public void PreviewHandlerBaseShouldReturnSiteWhenGetSiteCalled() { // Arrange var mockPreviewControl = new Mock(); @@ -341,19 +346,18 @@ namespace UnitTests_PreviewHandlerCommon var testPreviewHandler = new TestPreviewHandler(); var site = new Mock().Object; testPreviewHandler.SetSite(site); - object actualSite = null; var riid = Guid.Empty; // Act - testPreviewHandler.GetSite(ref riid, out actualSite); + testPreviewHandler.GetSite(ref riid, out object actualSite); // Assert Assert.AreEqual(actualSite, site); } - private LOGFONT GetLogFont() + private static LOGFONT GetLogFont() { - var logFont = new LOGFONT + return new LOGFONT { LfHeight = 12, LfWidth = 0, @@ -369,21 +373,17 @@ namespace UnitTests_PreviewHandlerCommon LfPitchAndFamily = Convert.ToByte(0), LfFaceName = "valid-font", }; - - return logFont; } - private RECT GetRectangle(int left, int top, int right, int bottom) + private static RECT GetRectangle(int left, int top, int right, int bottom) { - var rect = new RECT + return new RECT { Left = left, Top = top, Right = right, Bottom = bottom, }; - - return rect; } private static IPreviewHandlerControl GetMockPreviewHandlerControl() diff --git a/src/modules/previewpane/UnitTests-PreviewHandlerCommon/StreamBasedPreviewHandlerTests.cs b/src/modules/previewpane/UnitTests-PreviewHandlerCommon/StreamBasedPreviewHandlerTests.cs index 48e9c91f9e..583947be21 100644 --- a/src/modules/previewpane/UnitTests-PreviewHandlerCommon/StreamBasedPreviewHandlerTests.cs +++ b/src/modules/previewpane/UnitTests-PreviewHandlerCommon/StreamBasedPreviewHandlerTests.cs @@ -8,12 +8,12 @@ using Common; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -namespace UnitTests_PreviewHandlerCommon +namespace PreviewHandlerCommonUnitTests { [TestClass] public class StreamBasedPreviewHandlerTests { - public class TestStreamBasedPreviewHandler : StreamBasedPreviewHandler + internal class TestStreamBasedPreviewHandler : StreamBasedPreviewHandler { public override void DoPreview() { @@ -29,7 +29,7 @@ namespace UnitTests_PreviewHandlerCommon [DataTestMethod] [DataRow(0U)] [DataRow(1U)] - public void StreamBasedPreviewHandler_ShouldSetStream_WhenInitializeCalled(uint grfMode) + public void StreamBasedPreviewHandlerShouldSetStreamWhenInitializeCalled(uint grfMode) { // Arrange var streamBasedPreviewHandler = new TestStreamBasedPreviewHandler(); @@ -39,7 +39,7 @@ namespace UnitTests_PreviewHandlerCommon streamBasedPreviewHandler.Initialize(stream, grfMode); // Assert - Assert.AreEqual(streamBasedPreviewHandler.Stream, stream); + Assert.AreEqual(stream, streamBasedPreviewHandler.Stream); } } } diff --git a/src/modules/previewpane/UnitTests-PreviewHandlerCommon/StreamWrapperTests.cs b/src/modules/previewpane/UnitTests-PreviewHandlerCommon/StreamWrapperTests.cs index db7d13deba..e0cb28481e 100644 --- a/src/modules/previewpane/UnitTests-PreviewHandlerCommon/StreamWrapperTests.cs +++ b/src/modules/previewpane/UnitTests-PreviewHandlerCommon/StreamWrapperTests.cs @@ -11,13 +11,13 @@ using Common.Utilities; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; -namespace UnitTests_PreviewHandlerCommon +namespace PreviewHandlerCommonUnitTests { [TestClass] public class StreamWrapperTests { [TestMethod] - public void StreamWrapper_ShouldThrow_IfInitializeWithNullStream() + public void StreamWrapperShouldThrowIfInitializeWithNullStream() { // Arrange IStream stream = null; @@ -26,7 +26,10 @@ namespace UnitTests_PreviewHandlerCommon // Act try { - var streamWrapper = new StreamWrapper(stream); + using (var streamWrapper = new ReadonlyStream(stream)) + { + // do work + } } catch (ArgumentNullException ex) { @@ -38,107 +41,113 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void StreamWrapper_ShouldReturnCanReadTrue() + public void StreamWrapperShouldReturnCanReadTrue() { // Arrange var streamMock = new Mock(); // Act - var streamWrapper = new StreamWrapper(streamMock.Object); - - // Assert - Assert.AreEqual(streamWrapper.CanRead, true); + using (var streamWrapper = new ReadonlyStream(streamMock.Object)) + { + // Assert + Assert.AreEqual(true, streamWrapper.CanRead); + } } [TestMethod] - public void StreamWrapper_ShouldReturnCanSeekTrue() + public void StreamWrapperShouldReturnCanSeekTrue() { // Arrange var streamMock = new Mock(); // Act - var streamWrapper = new StreamWrapper(streamMock.Object); - - // Assert - Assert.AreEqual(streamWrapper.CanSeek, true); + using (var streamWrapper = new ReadonlyStream(streamMock.Object)) + { + // Assert + Assert.AreEqual(true, streamWrapper.CanSeek); + } } [TestMethod] - public void StreamWrapper_ShouldReturnCanWriteFalse() + public void StreamWrapperShouldReturnCanWriteFalse() { // Arrange var streamMock = new Mock(); // Act - var streamWrapper = new StreamWrapper(streamMock.Object); - - // Assert - Assert.AreEqual(streamWrapper.CanWrite, false); + using (var streamWrapper = new ReadonlyStream(streamMock.Object)) + { + // Assert + Assert.AreEqual(false, streamWrapper.CanWrite); + } } [TestMethod] - public void StreamWrapper_ShouldReturnValidLength() + public void StreamWrapperShouldReturnValidLength() { // Arrange long streamLength = 5; - var stremMock = new Mock(); + var streamMock = new Mock(); var stat = new System.Runtime.InteropServices.ComTypes.STATSTG { cbSize = streamLength, }; - stremMock - .Setup(x => x.Stat(out stat, It.IsAny())); - var streamWrapper = new StreamWrapper(stremMock.Object); + streamMock.Setup(x => x.Stat(out stat, It.IsAny())); - // Act - var actualLength = streamWrapper.Length; + using (var streamWrapper = new ReadonlyStream(streamMock.Object)) + { + // Act + var actualLength = streamWrapper.Length; - // Assert - Assert.AreEqual(actualLength, streamLength); + // Assert + Assert.AreEqual(streamLength, actualLength); + } } [TestMethod] - public void StreamWrapper_ShouldReturnValidPosition() + public void StreamWrapperShouldReturnValidPosition() { // Arrange int expectedDwOrigin = 1; // STREAM_SEEK_CUR long expectedOffset = 0; long currPosition = 5; - var stremMock = new Mock(); + var streamMock = new Mock(); - stremMock + streamMock .Setup(x => x.Seek(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((dlibMove, dwOrigin, plibNewPosition) => { Marshal.WriteInt64(plibNewPosition, currPosition); }); - var streamWrapper = new StreamWrapper(stremMock.Object); - // Act - var actualPosition = streamWrapper.Position; + using (var streamWrapper = new ReadonlyStream(streamMock.Object)) + { + // Act + var actualPosition = streamWrapper.Position; - // Assert - Assert.AreEqual(actualPosition, currPosition); - stremMock.Verify(_ => _.Seek(It.Is(offset => offset == expectedOffset), It.Is(dworigin => dworigin == expectedDwOrigin), It.IsAny()), Times.Once); + // Assert + Assert.AreEqual(currPosition, actualPosition); + streamMock.Verify(_ => _.Seek(It.Is(offset => offset == expectedOffset), It.Is(dworigin => dworigin == expectedDwOrigin), It.IsAny()), Times.Once); + } } [TestMethod] - public void StreamWrapper_ShouldCallIStreamSeek_WhenSetPosition() + public void StreamWrapperShouldCallIStreamSeekWhenSetPosition() { // Arrange long positionToSet = 5; int expectedDwOrigin = 0; // STREAM_SEEK_SET - var stremMock = new Mock(); + var streamMock = new Mock(); - var streamWrapper = new StreamWrapper(stremMock.Object) + using (var streamWrapper = new ReadonlyStream(streamMock.Object)) { // Act - Position = positionToSet, - }; + streamWrapper.Position = positionToSet; - // Assert - stremMock.Verify(_ => _.Seek(It.Is(offset => offset == positionToSet), It.Is(dworigin => dworigin == expectedDwOrigin), It.IsAny()), Times.Once); + // Assert + streamMock.Verify(_ => _.Seek(It.Is(offset => offset == positionToSet), It.Is(dworigin => dworigin == expectedDwOrigin), It.IsAny()), Times.Once); + } } [DataTestMethod] @@ -148,7 +157,7 @@ namespace UnitTests_PreviewHandlerCommon [DataRow(5L, SeekOrigin.Current)] [DataRow(0L, SeekOrigin.End)] [DataRow(5L, SeekOrigin.End)] - public void StreamWrapper_ShouldCallIStreamSeekWithValidArguments_WhenSeekCalled(long offset, SeekOrigin origin) + public void StreamWrapperShouldCallIStreamSeekWithValidArgumentsWhenSeekCalled(long offset, SeekOrigin origin) { // Arrange int expectedDwOrigin = 0; @@ -167,37 +176,39 @@ namespace UnitTests_PreviewHandlerCommon break; } - var stremMock = new Mock(); - var streamWrapper = new StreamWrapper(stremMock.Object); + var streamMock = new Mock(); + using (var streamWrapper = new ReadonlyStream(streamMock.Object)) + { + // Act + streamWrapper.Seek(offset, origin); - // Act - streamWrapper.Seek(offset, origin); - - // Assert - stremMock.Verify(_ => _.Seek(It.Is(actualOffset => actualOffset == offset), It.Is(actualDwOrigin => actualDwOrigin == expectedDwOrigin), It.IsAny()), Times.Once); + // Assert + streamMock.Verify(_ => _.Seek(It.Is(actualOffset => actualOffset == offset), It.Is(actualDwOrigin => actualDwOrigin == expectedDwOrigin), It.IsAny()), Times.Once); + } } [TestMethod] - public void StreamWrapper_ShouldReturnValidPosition_WhenSeekCalled() + public void StreamWrapperShouldReturnValidPositionWhenSeekCalled() { // Arrange long position = 5; - var stremMock = new Mock(); + var streamMock = new Mock(); - stremMock + streamMock .Setup(x => x.Seek(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((dlibMove, dwOrigin, plibNewPosition) => { Marshal.WriteInt64(plibNewPosition, position); }); - var streamWrapper = new StreamWrapper(stremMock.Object); + using (var streamWrapper = new ReadonlyStream(streamMock.Object)) + { + // Act + var actualPosition = streamWrapper.Seek(0, SeekOrigin.Begin); - // Act - var actualPosition = streamWrapper.Seek(0, SeekOrigin.Begin); - - // Assert - Assert.AreEqual(position, actualPosition); + // Assert + Assert.AreEqual(actualPosition, position); + } } [DataTestMethod] @@ -205,34 +216,35 @@ namespace UnitTests_PreviewHandlerCommon [DataRow(10, 0, -5)] [DataRow(10, 0, 11)] [DataRow(10, 5, 6)] - public void StreamWrapper_ShouldThrow_WhenReadCalledWithOutOfRangeArguments(int bufferLength, int offSet, int bytesToRead) + public void StreamWrapperShouldThrowWhenReadCalledWithOutOfRangeArguments(int bufferLength, int offSet, int bytesToRead) { // Arrange var buffer = new byte[bufferLength]; - var stremMock = new Mock(); + var streamMock = new Mock(); ArgumentOutOfRangeException exception = null; - var streamWrapper = new StreamWrapper(stremMock.Object); - - // Act - try + using (var streamWrapper = new ReadonlyStream(streamMock.Object)) { - streamWrapper.Read(buffer, offSet, bytesToRead); - } - catch (ArgumentOutOfRangeException ex) - { - exception = ex; - } + // Act + try + { + streamWrapper.Read(buffer, offSet, bytesToRead); + } + catch (ArgumentOutOfRangeException ex) + { + exception = ex; + } - // Assert - Assert.IsNotNull(exception); + // Assert + Assert.IsNotNull(exception); + } } [DataTestMethod] [DataRow(5, 0)] [DataRow(5, 5)] [DataRow(0, 5)] - public void StreamWrapper_ShouldSetValidBuffer_WhenReadCalled(int count, int offset) + public void StreamWrapperShouldSetValidBufferWhenReadCalled(int count, int offset) { // Arrange var inputBuffer = new byte[1024]; @@ -242,9 +254,9 @@ namespace UnitTests_PreviewHandlerCommon streamBytes[i] = (byte)i; } - var stremMock = new Mock(); + var streamMock = new Mock(); - stremMock + streamMock .Setup(x => x.Read(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((buffer, countToRead, bytesReadPtr) => { @@ -252,80 +264,87 @@ namespace UnitTests_PreviewHandlerCommon Marshal.WriteInt32(bytesReadPtr, count); }); - var streamWrapper = new StreamWrapper(stremMock.Object); + using (var streamWrapper = new ReadonlyStream(streamMock.Object)) + { + // Act + var bytesRead = streamWrapper.Read(inputBuffer, offset, count); - // Act - var bytesRead = streamWrapper.Read(inputBuffer, offset, count); - - // Assert - CollectionAssert.AreEqual(streamBytes, inputBuffer.Skip(offset).Take(count).ToArray()); - Assert.AreEqual(count, bytesRead); + // Assert + CollectionAssert.AreEqual(streamBytes, inputBuffer.Skip(offset).Take(count).ToArray()); + Assert.AreEqual(count, bytesRead); + } } [TestMethod] - public void StreamWrapper_ShouldThrowNotImplementedException_WhenFlushCalled() + public void StreamWrapperShouldThrowNotImplementedExceptionWhenFlushCalled() { // Arrange - var stremMock = new Mock(); - var streamWrapper = new StreamWrapper(stremMock.Object); - NotImplementedException exception = null; - - // Act - try + var streamMock = new Mock(); + using (var streamWrapper = new ReadonlyStream(streamMock.Object)) { - streamWrapper.Flush(); - } - catch (NotImplementedException ex) - { - exception = ex; - } + NotImplementedException exception = null; - // Assert - Assert.IsNotNull(exception); + // Act + try + { + streamWrapper.Flush(); + } + catch (NotImplementedException ex) + { + exception = ex; + } + + // Assert + Assert.IsNotNull(exception); + } } [TestMethod] - public void StreamWrapper_ShouldThrowNotImplementedException_WhenSetLengthCalled() + public void StreamWrapperShouldThrowNotImplementedExceptionWhenSetLengthCalled() { // Arrange - var stremMock = new Mock(); - var streamWrapper = new StreamWrapper(stremMock.Object); - NotImplementedException exception = null; - - // Act - try + var streamMock = new Mock(); + using (var streamWrapper = new ReadonlyStream(streamMock.Object)) { - streamWrapper.SetLength(5); - } - catch (NotImplementedException ex) - { - exception = ex; - } + NotImplementedException exception = null; - // Assert - Assert.IsNotNull(exception); + // Act + try + { + streamWrapper.SetLength(5); + } + catch (NotImplementedException ex) + { + exception = ex; + } + + // Assert + Assert.IsNotNull(exception); + } } [TestMethod] - public void StreamWrapper_ShouldThrowNotImplementedException_WhenWriteCalled() + public void StreamWrapperShouldThrowNotImplementedExceptionWhenWriteCalled() { // Arrange - var stremMock = new Mock(); - var streamWrapper = new StreamWrapper(stremMock.Object); - NotImplementedException exception = null; - - // Act - try + var streamMock = new Mock(); + using (var streamWrapper = new ReadonlyStream(streamMock.Object)) { - streamWrapper.Write(new byte[5], 0, 0); - } - catch (NotImplementedException ex) - { - exception = ex; - } + NotImplementedException exception = null; - // Assert - Assert.IsNotNull(exception); + // Act + try + { + streamWrapper.Write(new byte[5], 0, 0); + } + catch (NotImplementedException ex) + { + exception = ex; + } + + // Assert + Assert.IsNotNull(exception); + } } } } diff --git a/src/modules/previewpane/UnitTests-PreviewHandlerCommon/UnitTests-PreviewHandlerCommon.csproj b/src/modules/previewpane/UnitTests-PreviewHandlerCommon/UnitTests-PreviewHandlerCommon.csproj index 12db3f78ee..d14e164019 100644 --- a/src/modules/previewpane/UnitTests-PreviewHandlerCommon/UnitTests-PreviewHandlerCommon.csproj +++ b/src/modules/previewpane/UnitTests-PreviewHandlerCommon/UnitTests-PreviewHandlerCommon.csproj @@ -44,7 +44,7 @@ {748417CA-F17E-487F-9411-CAFB6D3F4877} Library Properties - UnitTests_PreviewHandlerCommon + PreviewHandlerCommonUnitTests UnitTests-PreviewHandlerCommon v4.7.2 512 @@ -82,7 +82,6 @@ true - true @@ -98,6 +97,7 @@ + @@ -114,6 +114,11 @@
+ + 3.3.0 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + 1.1.118 runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/modules/previewpane/UnitTests-PreviewHandlerCommon/WebBrowserExtUnitTests.cs b/src/modules/previewpane/UnitTests-PreviewHandlerCommon/WebBrowserExtUnitTests.cs index b10b411c95..5b2cc55e9f 100644 --- a/src/modules/previewpane/UnitTests-PreviewHandlerCommon/WebBrowserExtUnitTests.cs +++ b/src/modules/previewpane/UnitTests-PreviewHandlerCommon/WebBrowserExtUnitTests.cs @@ -6,7 +6,7 @@ using System.Reflection; using Microsoft.VisualStudio.TestTools.UnitTesting; using PreviewHandlerCommon; -namespace UnitTests_PreviewHandlerCommon +namespace PreviewHandlerCommonUnitTests { [TestClass] public class WebBrowserExtUnitTests : WebBrowserExt @@ -14,7 +14,7 @@ namespace UnitTests_PreviewHandlerCommon private const string DISPIDAMBIENTDLCONTROL = "[DISPID=-5512]"; [TestMethod] - public void InvokeMember_ShouldSetValidFlags_WhenCalledWithValidDispId() + public void InvokeMemberShouldSetValidFlagsWhenCalledWithValidDispId() { // Arrange var extendedSite = CreateWebBrowserSiteBase() as WebBrowserSiteExt; @@ -37,7 +37,7 @@ namespace UnitTests_PreviewHandlerCommon } [TestMethod] - public void InvokeMember_ShouldOnlySetValidFlags_WhenCalledWithValidDispId() + public void InvokeMemberShouldOnlySetValidFlagsWhenCalledWithValidDispId() { // Arrange var extendedSite = CreateWebBrowserSiteBase() as WebBrowserSiteExt; diff --git a/src/modules/previewpane/UnitTests-SvgPreviewHandler/SvgPreviewControlTests.cs b/src/modules/previewpane/UnitTests-SvgPreviewHandler/SvgPreviewControlTests.cs index a5d10fce72..73d24e3211 100644 --- a/src/modules/previewpane/UnitTests-SvgPreviewHandler/SvgPreviewControlTests.cs +++ b/src/modules/previewpane/UnitTests-SvgPreviewHandler/SvgPreviewControlTests.cs @@ -28,7 +28,7 @@ namespace SvgPreviewHandlerUnitTests svgPreviewControl.DoPreview(GetMockStream("")); // Assert - Assert.AreEqual(svgPreviewControl.Controls.Count, 1); + Assert.AreEqual(1, svgPreviewControl.Controls.Count); Assert.IsInstanceOfType(svgPreviewControl.Controls[0], typeof(WebBrowserExt)); } } @@ -57,7 +57,7 @@ namespace SvgPreviewHandlerUnitTests svgPreviewControl.DoPreview(GetMockStream("")); // Assert - Assert.AreEqual(((WebBrowser)svgPreviewControl.Controls[0]).IsWebBrowserContextMenuEnabled, false); + Assert.AreEqual(false, ((WebBrowser)svgPreviewControl.Controls[0]).IsWebBrowserContextMenuEnabled); } } @@ -71,7 +71,7 @@ namespace SvgPreviewHandlerUnitTests svgPreviewControl.DoPreview(GetMockStream("")); // Assert - Assert.AreEqual(((WebBrowser)svgPreviewControl.Controls[0]).Dock, DockStyle.Fill); + Assert.AreEqual(DockStyle.Fill, ((WebBrowser)svgPreviewControl.Controls[0]).Dock); } } @@ -85,7 +85,7 @@ namespace SvgPreviewHandlerUnitTests svgPreviewControl.DoPreview(GetMockStream("")); // Assert - Assert.AreEqual(((WebBrowser)svgPreviewControl.Controls[0]).ScriptErrorsSuppressed, true); + Assert.AreEqual(true, ((WebBrowser)svgPreviewControl.Controls[0]).ScriptErrorsSuppressed); } } @@ -99,7 +99,7 @@ namespace SvgPreviewHandlerUnitTests svgPreviewControl.DoPreview(GetMockStream("")); // Assert - Assert.AreEqual(((WebBrowser)svgPreviewControl.Controls[0]).ScrollBarsEnabled, true); + Assert.AreEqual(true, ((WebBrowser)svgPreviewControl.Controls[0]).ScrollBarsEnabled); } } @@ -113,7 +113,7 @@ namespace SvgPreviewHandlerUnitTests svgPreviewControl.DoPreview(GetMockStream("")); // Assert - Assert.AreEqual(((WebBrowser)svgPreviewControl.Controls[0]).AllowNavigation, false); + Assert.AreEqual(false, ((WebBrowser)svgPreviewControl.Controls[0]).AllowNavigation); } } @@ -134,13 +134,13 @@ namespace SvgPreviewHandlerUnitTests // Assert Assert.IsFalse(string.IsNullOrWhiteSpace(textBox.Text)); - Assert.AreEqual(svgPreviewControl.Controls.Count, 1); - Assert.AreEqual(textBox.Dock, DockStyle.Top); - Assert.AreEqual(textBox.BackColor, Color.LightYellow); + Assert.AreEqual(1, svgPreviewControl.Controls.Count); + Assert.AreEqual(DockStyle.Top, textBox.Dock); + Assert.AreEqual(Color.LightYellow, textBox.BackColor); Assert.IsTrue(textBox.Multiline); Assert.IsTrue(textBox.ReadOnly); - Assert.AreEqual(textBox.ScrollBars, RichTextBoxScrollBars.None); - Assert.AreEqual(textBox.BorderStyle, BorderStyle.None); + Assert.AreEqual(RichTextBoxScrollBars.None, textBox.ScrollBars); + Assert.AreEqual(BorderStyle.None, textBox.BorderStyle); } } @@ -165,8 +165,8 @@ namespace SvgPreviewHandlerUnitTests svgPreviewControl.Width += incrementParentControlWidth; // Assert - Assert.AreEqual(initialParentWidth, initialTextBoxWidth); - Assert.AreEqual(finalParentWidth, textBox.Width); + Assert.AreEqual(initialTextBoxWidth, initialParentWidth); + Assert.AreEqual(textBox.Width, finalParentWidth); } } @@ -187,7 +187,7 @@ namespace SvgPreviewHandlerUnitTests // Assert Assert.IsInstanceOfType(svgPreviewControl.Controls[0], typeof(RichTextBox)); Assert.IsInstanceOfType(svgPreviewControl.Controls[1], typeof(WebBrowserExt)); - Assert.AreEqual(svgPreviewControl.Controls.Count, 2); + Assert.AreEqual(2, svgPreviewControl.Controls.Count); } } @@ -208,7 +208,7 @@ namespace SvgPreviewHandlerUnitTests // Assert Assert.IsInstanceOfType(svgPreviewControl.Controls[0], typeof(WebBrowserExt)); - Assert.AreEqual(svgPreviewControl.Controls.Count, 1); + Assert.AreEqual(1, svgPreviewControl.Controls.Count); } } @@ -233,8 +233,8 @@ namespace SvgPreviewHandlerUnitTests svgPreviewControl.Width += incrementParentControlWidth; // Assert - Assert.AreEqual(initialParentWidth, initialTextBoxWidth); - Assert.AreEqual(finalParentWidth, textBox.Width); + Assert.AreEqual(initialTextBoxWidth, initialParentWidth); + Assert.AreEqual(textBox.Width, finalParentWidth); } } diff --git a/src/modules/previewpane/common/PreviewHandlerCommon.csproj b/src/modules/previewpane/common/PreviewHandlerCommon.csproj index f8adb7a9a3..eae65aae56 100644 --- a/src/modules/previewpane/common/PreviewHandlerCommon.csproj +++ b/src/modules/previewpane/common/PreviewHandlerCommon.csproj @@ -110,6 +110,7 @@ + @@ -129,7 +130,7 @@ - + @@ -137,6 +138,11 @@ + + 3.3.0 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + 1.1.118 runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/modules/previewpane/common/Utilities/StreamWrapper.cs b/src/modules/previewpane/common/Utilities/ReadonlyStream.cs similarity index 83% rename from src/modules/previewpane/common/Utilities/StreamWrapper.cs rename to src/modules/previewpane/common/Utilities/ReadonlyStream.cs index c1f8eb6acd..a9ae949181 100644 --- a/src/modules/previewpane/common/Utilities/StreamWrapper.cs +++ b/src/modules/previewpane/common/Utilities/ReadonlyStream.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; @@ -15,17 +16,17 @@ namespace Common.Utilities /// /// Implements only read from the stream functionality. /// - public class StreamWrapper : Stream + public class ReadonlyStream : Stream { - private IStream stream; + private IStream _stream; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// A pointer to an interface that represents the stream source. - public StreamWrapper(IStream stream) + public ReadonlyStream(IStream stream) { - this.stream = stream ?? throw new ArgumentNullException(nameof(stream)); + _stream = stream ?? throw new ArgumentNullException(nameof(stream)); } /// @@ -68,11 +69,11 @@ namespace Common.Utilities { get { - this.CheckDisposed(); + CheckDisposed(); System.Runtime.InteropServices.ComTypes.STATSTG stat; // Stat called with STATFLAG_NONAME. The pwcsName is not required more details https://docs.microsoft.com/en-us/windows/win32/api/wtypes/ne-wtypes-statflag - this.stream.Stat(out stat, 1); // STATFLAG_NONAME + _stream.Stat(out stat, 1); // STATFLAG_NONAME return stat.cbSize; } @@ -85,12 +86,12 @@ namespace Common.Utilities { get { - return this.Seek(0, SeekOrigin.Current); + return Seek(0, SeekOrigin.Current); } set { - this.Seek(value, SeekOrigin.Begin); + Seek(value, SeekOrigin.Begin); } } @@ -103,11 +104,16 @@ namespace Common.Utilities /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero if the end of the stream has been reached. public override int Read(byte[] buffer, int offset, int count) { - this.CheckDisposed(); + CheckDisposed(); - if (offset < 0 || count < 0 || offset + count > buffer.Length) + if (buffer == null) { - throw new ArgumentOutOfRangeException(); + throw new NullReferenceException("buffer is null"); + } + + if (offset < 0 || count < 0 || (offset + count) > buffer.Length) + { + throw new ArgumentOutOfRangeException(nameof(buffer), "Out of range for buffer"); } byte[] localBuffer = buffer; @@ -121,7 +127,7 @@ namespace Common.Utilities try { - this.stream.Read(localBuffer, count, bytesReadPtr); + _stream.Read(localBuffer, count, bytesReadPtr); int bytesRead = Marshal.ReadInt32(bytesReadPtr); if (offset > 0) @@ -145,7 +151,7 @@ namespace Common.Utilities /// The new position within the current stream. public override long Seek(long offset, SeekOrigin origin) { - this.CheckDisposed(); + CheckDisposed(); int dwOrigin; // Maps the SeekOrigin with dworigin more details: https://docs.microsoft.com/en-us/windows/win32/api/objidl/ne-objidl-stream_seek @@ -164,14 +170,14 @@ namespace Common.Utilities break; default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(origin)); } IntPtr posPtr = Marshal.AllocCoTaskMem(sizeof(long)); try { - this.stream.Seek(offset, dwOrigin, posPtr); + _stream.Seek(offset, dwOrigin, posPtr); return Marshal.ReadInt64(posPtr); } finally @@ -220,22 +226,24 @@ namespace Common.Utilities /// protected override void Dispose(bool disposing) { - if (this.stream != null) + base.Dispose(true); + + if (_stream != null) { - if (Marshal.IsComObject(this.stream)) + if (Marshal.IsComObject(_stream)) { - Marshal.ReleaseComObject(this.stream); + Marshal.ReleaseComObject(_stream); } - this.stream = null; + _stream = null; } } private void CheckDisposed() { - if (this.stream == null) + if (_stream == null) { - throw new ObjectDisposedException(nameof(StreamWrapper)); + throw new ObjectDisposedException(nameof(ReadonlyStream)); } } } diff --git a/src/modules/previewpane/common/cominterop/COLORREF.cs b/src/modules/previewpane/common/cominterop/COLORREF.cs index 65d1216973..0edf457f0f 100644 --- a/src/modules/previewpane/common/cominterop/COLORREF.cs +++ b/src/modules/previewpane/common/cominterop/COLORREF.cs @@ -11,12 +11,13 @@ namespace Common.ComInterlop /// The COLORREF value is used to specify an RGB color. /// [StructLayout(LayoutKind.Sequential)] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "Interop")] public struct COLORREF { /// - /// Stores an RGB color value in a 32 bit integer. + /// Gets or sets stores an RGB color value in a 32 bit integer. /// - public uint Dword; + public uint Dword { get; set; } /// /// Gets RGB value stored in in structure. @@ -26,9 +27,9 @@ namespace Common.ComInterlop get { return Color.FromArgb( - (int)(0x000000FFU & this.Dword), - (int)(0x0000FF00U & this.Dword) >> 8, - (int)(0x00FF0000U & this.Dword) >> 16); + (int)(0x000000FFU & Dword), + (int)(0x0000FF00U & Dword) >> 8, + (int)(0x00FF0000U & Dword) >> 16); } } } diff --git a/src/modules/previewpane/common/cominterop/IThumbnailProvider.cs b/src/modules/previewpane/common/cominterop/IThumbnailProvider.cs index 758d231c51..78a6a866ff 100644 --- a/src/modules/previewpane/common/cominterop/IThumbnailProvider.cs +++ b/src/modules/previewpane/common/cominterop/IThumbnailProvider.cs @@ -10,10 +10,11 @@ namespace Common.ComInterlop /// /// Specifies the alpha type of the image. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Interop")] public enum WTS_ALPHATYPE : int { /// - /// he bitmap is an unknown format. The Shell tries nonetheless to detect whether the image has an alpha channel. + /// The bitmap is an unknown format. The Shell tries nonetheless to detect whether the image has an alpha channel. /// WTSAT_UNKNOWN = 0, diff --git a/src/modules/previewpane/common/cominterop/LOGFONT.cs b/src/modules/previewpane/common/cominterop/LOGFONT.cs index e3b1e88d51..a20cedf11b 100644 --- a/src/modules/previewpane/common/cominterop/LOGFONT.cs +++ b/src/modules/previewpane/common/cominterop/LOGFONT.cs @@ -10,79 +10,86 @@ namespace Common.ComInterlop /// Defines the attributes of a font. /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "Interop")] public struct LOGFONT { /// - /// Value of type INT that specifies the height, in logical units, of the font's character cell or character. + /// Gets or sets value of type INT that specifies the height, in logical units, of the font's character cell or character. /// - public int LfHeight; + public int LfHeight { get; set; } /// - /// Value of type INT that specifies the width, in logical units, of characters in the font. + /// Gets or sets value of type INT that specifies the width, in logical units, of characters in the font. /// - public int LfWidth; + public int LfWidth { get; set; } /// - /// Value of type INT that contains the angle, in tenths of degrees, between the escapement vector and the x-axis of the device. The escapement + /// Gets or sets value of type INT that contains the angle, in tenths of degrees, between the escapement vector and the x-axis of the device. The escapement /// vector is parallel to the base line of a row of text. /// - public int LfEscapement; + public int LfEscapement { get; set; } /// - /// Value of type INT that specifies the angle, in tenths of degrees, between each character's base line and the x-axis of the device. + /// Gets or sets value of type INT that specifies the angle, in tenths of degrees, between each character's base line and the x-axis of the device. /// - public int LfOrientation; + public int LfOrientation { get; set; } /// - /// Value of type INT that specifies the weight of the font in the range from 0 through 1000. + /// Gets or sets value of type INT that specifies the weight of the font in the range from 0 through 1000. /// - public int LfWeight; + public int LfWeight { get; set; } /// - /// Value of type BYTE that specifies an italic font if set to TRUE. + /// Gets or sets value of type BYTE that specifies an italic font if set to TRUE. /// - public byte LfItalic; + public byte LfItalic { get; set; } /// - /// Value of type BYTE that specifies an underlined font if set to TRUE. + /// Gets or sets value of type BYTE that specifies an underlined font if set to TRUE. /// - public byte LfUnderline; + public byte LfUnderline { get; set; } /// - /// Value of type BYTE that specifies a strikeout font if set to TRUE. + /// Gets or sets value of type BYTE that specifies a strikeout font if set to TRUE. /// - public byte LfStrikeOut; + public byte LfStrikeOut { get; set; } /// - /// Value of type BYTE that specifies the character set. + /// Gets or sets value of type BYTE that specifies the character set. /// - public byte LfCharSet; + public byte LfCharSet { get; set; } /// - /// Value of type BYTE that specifies the output precision. The output precision defines how closely the output must match the requested + /// Gets or sets value of type BYTE that specifies the output precision. The output precision defines how closely the output must match the requested /// font's height, width, character orientation, escapement, pitch, and font type. /// - public byte LfOutPrecision; + public byte LfOutPrecision { get; set; } /// - /// Value of type BYTE that specifies the clipping precision. The clipping precision defines how to clip characters that are partially outside the clipping region. + /// Gets or sets value of type BYTE that specifies the clipping precision. The clipping precision defines how to clip characters that are partially outside the clipping region. /// - public byte LfClipPrecision; + public byte LfClipPrecision { get; set; } /// - /// Value of type BYTE that specifies the output quality. The output quality defines how carefully the GDI must attempt to match the logical-font attributes to those of an actual physical font. + /// Gets or sets value of type BYTE that specifies the output quality. The output quality defines how carefully the GDI must attempt to match the logical-font attributes to those of an actual physical font. /// - public byte LfQuality; + public byte LfQuality { get; set; } /// - /// Value of type BYTE that specifies the pitch and family of the font. + /// Gets or sets value of type BYTE that specifies the pitch and family of the font. /// - public byte LfPitchAndFamily; + public byte LfPitchAndFamily { get; set; } /// - /// Array of wide characters that contains a null-terminated string that specifies the typeface name of the font. The length of the string must not exceed 32 characters, including the NULL terminator. + /// Gets or sets array of wide characters that contains a null-terminated string that specifies the typeface name of the font. The length of the string must not exceed 32 characters, including the NULL terminator. /// + public string LfFaceName + { + get { return _lfFaceName; } + set { _lfFaceName = value; } + } + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] - public string LfFaceName; + private string _lfFaceName; } } diff --git a/src/modules/previewpane/common/cominterop/MSG.cs b/src/modules/previewpane/common/cominterop/MSG.cs index 58d5fedaa7..c1ec5ccca5 100644 --- a/src/modules/previewpane/common/cominterop/MSG.cs +++ b/src/modules/previewpane/common/cominterop/MSG.cs @@ -11,41 +11,42 @@ namespace Common.ComInterlop /// Contains message information from a thread's message queue. /// [StructLayout(LayoutKind.Sequential)] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "Interop")] public struct MSG { /// - /// A handle to the window whose window procedure receives the message. This member is NULL when the message is a thread message. + /// Gets or sets a handle to the window whose window procedure receives the message. This member is NULL when the message is a thread message. /// - public IntPtr Hwnd; + public IntPtr Hwnd { get; set; } /// - /// The message identifier. Applications can only use the low word; the high word is reserved by the system. + /// Gets or sets the message identifier. Applications can only use the low word; the high word is reserved by the system. /// - public int Message; + public int Message { get; set; } /// - /// Additional information about the message. The exact meaning depends on the value of the message member. + /// Gets or sets additional information about the message. The exact meaning depends on the value of the message member. /// - public IntPtr WParam; + public IntPtr WParam { get; set; } /// - /// Additional information about the message. The exact meaning depends on the value of the message member. + /// Gets or sets additional information about the message. The exact meaning depends on the value of the message member. /// - public IntPtr LParam; + public IntPtr LParam { get; set; } /// - /// The time at which the message was posted. + /// Gets or sets the time at which the message was posted. /// - public int Time; + public int Time { get; set; } /// - /// The x coordinate of cursor position, in screen coordinates, when the message was posted. + /// Gets or sets the x coordinate of cursor position, in screen coordinates, when the message was posted. /// - public int PtX; + public int PtX { get; set; } /// - /// The y coordinate of cursor position, in screen coordinates, when the message was posted. + /// Gets or sets the y coordinate of cursor position, in screen coordinates, when the message was posted. /// - public int PtY; + public int PtY { get; set; } } } diff --git a/src/modules/previewpane/common/cominterop/NativeMethods.cs b/src/modules/previewpane/common/cominterop/NativeMethods.cs new file mode 100644 index 0000000000..2a23adaed3 --- /dev/null +++ b/src/modules/previewpane/common/cominterop/NativeMethods.cs @@ -0,0 +1,37 @@ +// 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.Runtime.InteropServices; + +namespace PreviewHandlerCommon.ComInterop +{ + /// + /// Interop methods + /// + internal class NativeMethods + { + /// + /// Changes the parent window of the specified child window. + /// + /// A handle to the child window. + /// A handle to the new parent window. + /// If the function succeeds, the return value is a handle to the previous parent window and NULL in case of failure. + [DllImport("user32.dll")] + public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); + + /// + /// Retrieves the handle to the window that has the keyboard focus, if the window is attached to the calling thread's message queue. + /// + /// The return value is the handle to the window with the keyboard focus. If the calling thread's message queue does not have an associated window with the keyboard focus, the return value is NULL. + [DllImport("user32.dll", CharSet = CharSet.Auto)] + public static extern IntPtr GetFocus(); + + [DllImport("user32.dll", SetLastError = true)] + public static extern int GetWindowLong(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll", SetLastError = true)] + public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong); + } +} diff --git a/src/modules/previewpane/common/cominterop/RECT.cs b/src/modules/previewpane/common/cominterop/RECT.cs index 2d47937c39..25eca98b09 100644 --- a/src/modules/previewpane/common/cominterop/RECT.cs +++ b/src/modules/previewpane/common/cominterop/RECT.cs @@ -11,27 +11,28 @@ namespace Common.ComInterlop /// The RECT structure defines a rectangle by the coordinates of its upper-left and lower-right corners. /// [StructLayout(LayoutKind.Sequential)] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "Interop")] public struct RECT { /// - /// Specifies the x-coordinate of the upper-left corner of the rectangle. + /// Gets or sets specifies the x-coordinate of the upper-left corner of the rectangle. /// - public int Left; + public int Left { get; set; } /// - /// Specifies the y-coordinate of the upper-left corner of the rectangle. + /// Gets or sets specifies the y-coordinate of the upper-left corner of the rectangle. /// - public int Top; + public int Top { get; set; } /// - /// Specifies the x-coordinate of the lower-right corner of the rectangle. + /// Gets or sets specifies the x-coordinate of the lower-right corner of the rectangle. /// - public int Right; + public int Right { get; set; } /// - /// Specifies the y-coordinate of the lower-right corner of the rectangle. + /// Gets or sets specifies the y-coordinate of the lower-right corner of the rectangle. /// - public int Bottom; + public int Bottom { get; set; } /// /// Creates a structure with the edge locations specified in the struct. @@ -39,7 +40,7 @@ namespace Common.ComInterlop /// Return a . public Rectangle ToRectangle() { - return Rectangle.FromLTRB(this.Left, this.Top, this.Right, this.Bottom); + return Rectangle.FromLTRB(Left, Top, Right, Bottom); } } } diff --git a/src/modules/previewpane/common/controls/FormHandlerControl.cs b/src/modules/previewpane/common/controls/FormHandlerControl.cs index 901b42dc3f..b3696044fc 100644 --- a/src/modules/previewpane/common/controls/FormHandlerControl.cs +++ b/src/modules/previewpane/common/controls/FormHandlerControl.cs @@ -6,6 +6,7 @@ using System; using System.Drawing; using System.Runtime.InteropServices; using System.Windows.Forms; +using PreviewHandlerCommon.ComInterop; namespace Common { @@ -34,12 +35,13 @@ namespace Common // This is important, because the thread that instantiates the preview handler component and calls its constructor is a single-threaded apartment (STA) thread, but the thread that calls into the interface members later on is a multithreaded apartment (MTA) thread. Windows Forms controls are meant to run on STA threads. // More details: https://docs.microsoft.com/en-us/archive/msdn-magazine/2007/january/windows-vista-and-office-writing-your-own-preview-handlers. var forceCreation = this.Handle; + this.FormBorderStyle = FormBorderStyle.None; this.Visible = false; } /// - public IntPtr GetHandle() + public IntPtr GetWindowHandle() { return this.Handle; } @@ -50,7 +52,7 @@ namespace Common var getResult = IntPtr.Zero; this.InvokeOnControlThread(() => { - getResult = GetFocus(); + getResult = NativeMethods.GetFocus(); }); result = getResult; } @@ -140,28 +142,6 @@ namespace Common this.Invoke(func); } - /// - /// Changes the parent window of the specified child window. - /// - /// A handle to the child window. - /// A handle to the new parent window. - /// If the function succeeds, the return value is a handle to the previous parent window and NULL in case of failure. - [DllImport("user32.dll")] - private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); - - /// - /// Retrieves the handle to the window that has the keyboard focus, if the window is attached to the calling thread's message queue. - /// - /// The return value is the handle to the window with the keyboard focus. If the calling thread's message queue does not have an associated window with the keyboard focus, the return value is NULL. - [DllImport("user32.dll", CharSet = CharSet.Auto)] - private static extern IntPtr GetFocus(); - - [DllImport("user32.dll", SetLastError = true)] - private static extern int GetWindowLong(IntPtr hWnd, int nIndex); - - [DllImport("user32.dll", SetLastError = true)] - private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong); - /// /// Update the Form Control window with the passed rectangle. /// @@ -171,14 +151,14 @@ namespace Common this.InvokeOnControlThread(() => { // We must set the WS_CHILD style to change the form to a control within the Explorer preview pane - int windowStyle = GetWindowLong(this.Handle, gwlStyle); + int windowStyle = NativeMethods.GetWindowLong(Handle, gwlStyle); if ((windowStyle & wsChild) == 0) { - SetWindowLong(this.Handle, gwlStyle, windowStyle | wsChild); + _ = NativeMethods.SetWindowLong(Handle, gwlStyle, windowStyle | wsChild); } - SetParent(this.Handle, this.parentHwnd); - this.Bounds = windowBounds; + NativeMethods.SetParent(Handle, parentHwnd); + Bounds = windowBounds; }); } } diff --git a/src/modules/previewpane/common/controls/IPreviewHandlerControl.cs b/src/modules/previewpane/common/controls/IPreviewHandlerControl.cs index 20271da6da..eb91c28207 100644 --- a/src/modules/previewpane/common/controls/IPreviewHandlerControl.cs +++ b/src/modules/previewpane/common/controls/IPreviewHandlerControl.cs @@ -47,7 +47,7 @@ namespace Common /// Gets the HWND of the control window. /// /// Pointer to the window handle. - IntPtr GetHandle(); + IntPtr GetWindowHandle(); /// /// Hide the preview and free any resource used for the preview. diff --git a/src/modules/previewpane/common/controls/WebBrowserDownloadControlFlags.cs b/src/modules/previewpane/common/controls/WebBrowserDownloadControlFlags.cs index 6023950f8f..bf4743a0e9 100644 --- a/src/modules/previewpane/common/controls/WebBrowserDownloadControlFlags.cs +++ b/src/modules/previewpane/common/controls/WebBrowserDownloadControlFlags.cs @@ -9,6 +9,7 @@ using System; /// Values of flags are defined in mshtmdid.h in distributed Windows Sdk. /// [Flags] +[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Interop, keeping stuff in sync")] public enum WebBrowserDownloadControlFlags : int { /// diff --git a/src/modules/previewpane/common/controls/WebBrowserExt.cs b/src/modules/previewpane/common/controls/WebBrowserExt.cs index c62eb6e389..5dcd44772a 100644 --- a/src/modules/previewpane/common/controls/WebBrowserExt.cs +++ b/src/modules/previewpane/common/controls/WebBrowserExt.cs @@ -51,7 +51,8 @@ namespace PreviewHandlerCommon public object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters) { object result; - if (name.Equals(DISPIDAMBIENTDLCONTROL)) + + if (name != null && name.Equals(DISPIDAMBIENTDLCONTROL, StringComparison.CurrentCulture)) { result = Convert.ToInt32( WebBrowserDownloadControlFlags.DLIMAGES | @@ -65,11 +66,11 @@ namespace PreviewHandlerCommon WebBrowserDownloadControlFlags.NO_DLACTIVEXCTLS | WebBrowserDownloadControlFlags.NO_RUNACTIVEXCTLS | WebBrowserDownloadControlFlags.NO_BEHAVIORS | - WebBrowserDownloadControlFlags.SILENT); + WebBrowserDownloadControlFlags.SILENT, CultureInfo.CurrentCulture); } else { - result = this.GetType().InvokeMember(name, invokeAttr, binder, target, args, modifiers, culture, namedParameters); + result = GetType().InvokeMember(name, invokeAttr, binder, target, args, modifiers, culture, namedParameters); } return result; diff --git a/src/modules/previewpane/common/examplehandler/CustomControlTest.cs b/src/modules/previewpane/common/examplehandler/CustomControlTest.cs index d9e6e68c25..52fc860e54 100644 --- a/src/modules/previewpane/common/examplehandler/CustomControlTest.cs +++ b/src/modules/previewpane/common/examplehandler/CustomControlTest.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.IO; using System.Windows.Forms; @@ -24,7 +25,7 @@ namespace Common WebBrowser browser = new WebBrowser(); browser.DocumentText = "Test"; - browser.Navigate(filePath); + browser.Navigate(new Uri(filePath)); browser.Dock = DockStyle.Fill; browser.IsWebBrowserContextMenuEnabled = false; this.Controls.Add(browser); diff --git a/src/modules/previewpane/common/examplehandler/TestCustomHandler.cs b/src/modules/previewpane/common/examplehandler/TestCustomHandler.cs index f5d783020d..2e6828daae 100644 --- a/src/modules/previewpane/common/examplehandler/TestCustomHandler.cs +++ b/src/modules/previewpane/common/examplehandler/TestCustomHandler.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.Runtime.InteropServices; namespace Common @@ -12,21 +13,50 @@ namespace Common [Guid("22a1a8e8-e929-4732-90ce-91eaff38b614")] [ClassInterface(ClassInterfaceType.None)] [ComVisible(true)] - public class TestCustomHandler : FileBasedPreviewHandler + public class TestCustomHandler : FileBasedPreviewHandler, IDisposable { - private CustomControlTest previewHandlerControl; + private CustomControlTest _previewHandlerControl; + private bool disposedValue; /// public override void DoPreview() { - this.previewHandlerControl.DoPreview(this.FilePath); + _previewHandlerControl.DoPreview(FilePath); } /// protected override IPreviewHandlerControl CreatePreviewHandlerControl() { - this.previewHandlerControl = new CustomControlTest(); - return this.previewHandlerControl; + _previewHandlerControl = new CustomControlTest(); + + return _previewHandlerControl; + } + + /// + /// Disposes objects + /// + /// Is Disposing + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + _previewHandlerControl.Dispose(); + } + + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + disposedValue = true; + } + } + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); } } } diff --git a/src/modules/previewpane/common/handlers/PreviewHandlerBase.cs b/src/modules/previewpane/common/handlers/PreviewHandlerBase.cs index 6d47cc348d..3eaff72502 100644 --- a/src/modules/previewpane/common/handlers/PreviewHandlerBase.cs +++ b/src/modules/previewpane/common/handlers/PreviewHandlerBase.cs @@ -46,7 +46,14 @@ namespace Common /// public PreviewHandlerBase() { - this.previewControl = this.CreatePreviewHandlerControl(); + } + + /// + /// Initializes a new instance of the class. + /// + public void Initialize() + { + previewControl = CreatePreviewHandlerControl(); } /// @@ -107,7 +114,7 @@ namespace Common /// public void GetWindow(out IntPtr phwnd) { - phwnd = this.previewControl.GetHandle(); + phwnd = this.previewControl.GetWindowHandle(); } /// diff --git a/src/modules/previewpane/powerpreview/Resources.resx b/src/modules/previewpane/powerpreview/Resources.resx index 070fc17cb7..070462134c 100644 --- a/src/modules/previewpane/powerpreview/Resources.resx +++ b/src/modules/previewpane/powerpreview/Resources.resx @@ -156,4 +156,10 @@ Render SVG images + + Some of the File Explorer modules could not be registered/unregistered as per your settings. Please restart PowerToys as admin for the changes to take place. + + + Failed to modify File Explorer modules + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/loc/cs/src/modules/previewpane/powerpreview/Resources.resx.lcl b/src/modules/previewpane/powerpreview/loc/cs/src/modules/previewpane/powerpreview/Resources.resx.lcl new file mode 100644 index 0000000000..05258b2cb4 --- /dev/null +++ b/src/modules/previewpane/powerpreview/loc/cs/src/modules/previewpane/powerpreview/Resources.resx.lcl @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/loc/de/src/modules/previewpane/powerpreview/Resources.resx.lcl b/src/modules/previewpane/powerpreview/loc/de/src/modules/previewpane/powerpreview/Resources.resx.lcl new file mode 100644 index 0000000000..01e93f2336 --- /dev/null +++ b/src/modules/previewpane/powerpreview/loc/de/src/modules/previewpane/powerpreview/Resources.resx.lcl @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/loc/es/src/modules/previewpane/powerpreview/Resources.resx.lcl b/src/modules/previewpane/powerpreview/loc/es/src/modules/previewpane/powerpreview/Resources.resx.lcl new file mode 100644 index 0000000000..0d02e9fd42 --- /dev/null +++ b/src/modules/previewpane/powerpreview/loc/es/src/modules/previewpane/powerpreview/Resources.resx.lcl @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/loc/fr/src/modules/previewpane/powerpreview/Resources.resx.lcl b/src/modules/previewpane/powerpreview/loc/fr/src/modules/previewpane/powerpreview/Resources.resx.lcl new file mode 100644 index 0000000000..59629e0334 --- /dev/null +++ b/src/modules/previewpane/powerpreview/loc/fr/src/modules/previewpane/powerpreview/Resources.resx.lcl @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/loc/hu/src/modules/previewpane/powerpreview/Resources.resx.lcl b/src/modules/previewpane/powerpreview/loc/hu/src/modules/previewpane/powerpreview/Resources.resx.lcl new file mode 100644 index 0000000000..78db1b4b8a --- /dev/null +++ b/src/modules/previewpane/powerpreview/loc/hu/src/modules/previewpane/powerpreview/Resources.resx.lcl @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/loc/it/src/modules/previewpane/powerpreview/Resources.resx.lcl b/src/modules/previewpane/powerpreview/loc/it/src/modules/previewpane/powerpreview/Resources.resx.lcl new file mode 100644 index 0000000000..2d7386acad --- /dev/null +++ b/src/modules/previewpane/powerpreview/loc/it/src/modules/previewpane/powerpreview/Resources.resx.lcl @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/loc/ja/src/modules/previewpane/powerpreview/Resources.resx.lcl b/src/modules/previewpane/powerpreview/loc/ja/src/modules/previewpane/powerpreview/Resources.resx.lcl new file mode 100644 index 0000000000..340ffcd258 --- /dev/null +++ b/src/modules/previewpane/powerpreview/loc/ja/src/modules/previewpane/powerpreview/Resources.resx.lcl @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/loc/ko/src/modules/previewpane/powerpreview/Resources.resx.lcl b/src/modules/previewpane/powerpreview/loc/ko/src/modules/previewpane/powerpreview/Resources.resx.lcl new file mode 100644 index 0000000000..aa873a69a1 --- /dev/null +++ b/src/modules/previewpane/powerpreview/loc/ko/src/modules/previewpane/powerpreview/Resources.resx.lcl @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/loc/nl/src/modules/previewpane/powerpreview/Resources.resx.lcl b/src/modules/previewpane/powerpreview/loc/nl/src/modules/previewpane/powerpreview/Resources.resx.lcl new file mode 100644 index 0000000000..2730b8931f --- /dev/null +++ b/src/modules/previewpane/powerpreview/loc/nl/src/modules/previewpane/powerpreview/Resources.resx.lcl @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/loc/pl/src/modules/previewpane/powerpreview/Resources.resx.lcl b/src/modules/previewpane/powerpreview/loc/pl/src/modules/previewpane/powerpreview/Resources.resx.lcl new file mode 100644 index 0000000000..a94aecada0 --- /dev/null +++ b/src/modules/previewpane/powerpreview/loc/pl/src/modules/previewpane/powerpreview/Resources.resx.lcl @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/loc/pt-BR/src/modules/previewpane/powerpreview/Resources.resx.lcl b/src/modules/previewpane/powerpreview/loc/pt-BR/src/modules/previewpane/powerpreview/Resources.resx.lcl new file mode 100644 index 0000000000..bb57b7d0c4 --- /dev/null +++ b/src/modules/previewpane/powerpreview/loc/pt-BR/src/modules/previewpane/powerpreview/Resources.resx.lcl @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/loc/pt-PT/src/modules/previewpane/powerpreview/Resources.resx.lcl b/src/modules/previewpane/powerpreview/loc/pt-PT/src/modules/previewpane/powerpreview/Resources.resx.lcl new file mode 100644 index 0000000000..2440653d45 --- /dev/null +++ b/src/modules/previewpane/powerpreview/loc/pt-PT/src/modules/previewpane/powerpreview/Resources.resx.lcl @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/loc/ru/src/modules/previewpane/powerpreview/Resources.resx.lcl b/src/modules/previewpane/powerpreview/loc/ru/src/modules/previewpane/powerpreview/Resources.resx.lcl new file mode 100644 index 0000000000..3f4edd4ea6 --- /dev/null +++ b/src/modules/previewpane/powerpreview/loc/ru/src/modules/previewpane/powerpreview/Resources.resx.lcl @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/loc/sv/src/modules/previewpane/powerpreview/Resources.resx.lcl b/src/modules/previewpane/powerpreview/loc/sv/src/modules/previewpane/powerpreview/Resources.resx.lcl new file mode 100644 index 0000000000..a5500b8202 --- /dev/null +++ b/src/modules/previewpane/powerpreview/loc/sv/src/modules/previewpane/powerpreview/Resources.resx.lcl @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/loc/tr/src/modules/previewpane/powerpreview/Resources.resx.lcl b/src/modules/previewpane/powerpreview/loc/tr/src/modules/previewpane/powerpreview/Resources.resx.lcl new file mode 100644 index 0000000000..4778fe4ebc --- /dev/null +++ b/src/modules/previewpane/powerpreview/loc/tr/src/modules/previewpane/powerpreview/Resources.resx.lcl @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/loc/zh-Hans/src/modules/previewpane/powerpreview/Resources.resx.lcl b/src/modules/previewpane/powerpreview/loc/zh-Hans/src/modules/previewpane/powerpreview/Resources.resx.lcl new file mode 100644 index 0000000000..44703e6ab9 --- /dev/null +++ b/src/modules/previewpane/powerpreview/loc/zh-Hans/src/modules/previewpane/powerpreview/Resources.resx.lcl @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/loc/zh-Hant/src/modules/previewpane/powerpreview/Resources.resx.lcl b/src/modules/previewpane/powerpreview/loc/zh-Hant/src/modules/previewpane/powerpreview/Resources.resx.lcl new file mode 100644 index 0000000000..15c5a40313 --- /dev/null +++ b/src/modules/previewpane/powerpreview/loc/zh-Hant/src/modules/previewpane/powerpreview/Resources.resx.lcl @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/powerpreview.cpp b/src/modules/previewpane/powerpreview/powerpreview.cpp index 61d2a6b072..7c00ee4202 100644 --- a/src/modules/previewpane/powerpreview/powerpreview.cpp +++ b/src/modules/previewpane/powerpreview/powerpreview.cpp @@ -5,48 +5,69 @@ #include "trace.h" #include "settings.h" #include "Generated Files/resource.h" +#include + +// Constructor +PowerPreviewModule::PowerPreviewModule() : + m_moduleName(GET_RESOURCE_STRING(IDS_MODULE_NAME)), + app_key(powerpreviewConstants::ModuleKey), + m_fileExplorerModules( + { // SVG Preview Handler settings object. + new PreviewHandlerSettings( + true, + L"svg-previewer-toggle-setting", + GET_RESOURCE_STRING(IDS_PREVPANE_SVG_SETTINGS_DESCRIPTION), + L"{ddee2b8a-6807-48a6-bb20-2338174ff779}", + L"Svg Preview Handler", + new RegistryWrapper()), + + // MarkDown Preview Handler Settings Object. + new PreviewHandlerSettings( + true, + L"md-previewer-toggle-setting", + GET_RESOURCE_STRING(IDS_PREVPANE_MD_SETTINGS_DESCRIPTION), + L"{45769bcc-e8fd-42d0-947e-02beef77a1f5}", + L"Markdown Preview Handler", + new RegistryWrapper()), + //SVG Thumbnail Provider settings object. + new ThumbnailProviderSettings( + true, + L"svg-thumbnail-toggle-setting", + GET_RESOURCE_STRING(IDS_SVG_THUMBNAIL_PROVIDER_SETTINGS_DESCRIPTION), + L"{36B27788-A8BB-4698-A756-DF9F11F64F84}", + L"Svg Thumbnail Provider", + new RegistryWrapper(), + L".svg\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}") }) +{ + // Initialize the toggle states for each module + init_settings(); + + // If the user is on the new settings interface, File Explorer might be disabled if they updated from old to new settings, so initialize the registry state in the constructor as PowerPreviewModule::enable/disable will not be called on startup + if (UseNewSettings()) + { + update_registry_to_match_toggles(); + } +} // Destroy the powertoy and free memory. void PowerPreviewModule::destroy() { Trace::Destroyed(); - for (auto previewHandler : this->m_previewHandlers) - { - if (previewHandler != NULL) - { - // Disable all the active preview handlers. - if (this->m_enabled && previewHandler->GetToggleSettingState()) - { - previewHandler->DisablePreview(); - } - - delete previewHandler; - } - } - - for (auto thumbnailProvider : this->m_thumbnailProviders) - { - if (thumbnailProvider != NULL) - { - // Disable all the active thumbnail providers. - if (this->m_enabled && thumbnailProvider->GetToggleSettingState()) - { - thumbnailProvider->DisablePreview(); - } - - delete thumbnailProvider; - } - } - delete this; } -// Return the display name of the powertoy, this will be cached. +// Return the localized display name of the powertoy const wchar_t* PowerPreviewModule::get_name() { return m_moduleName.c_str(); } +// Return the non localized key of the powertoy, this will be cached by the runner +const wchar_t* PowerPreviewModule::get_key() +{ + return app_key.c_str(); +} + // Return JSON with the configuration options. bool PowerPreviewModule::get_config(_Out_ wchar_t* buffer, _Out_ int* buffer_size) { @@ -66,20 +87,12 @@ bool PowerPreviewModule::get_config(_Out_ wchar_t* buffer, _Out_ int* buffer_siz GET_RESOURCE_STRING(IDS_PRVPANE_FILE_PREV_STTNGS_GROUP_DESC), GET_RESOURCE_STRING(IDS_PRVPANE_FILE_PREV_STTNGS_GROUP_TEXT)); - for (auto previewHandler : this->m_previewHandlers) + for (auto fileExplorerModule : this->m_fileExplorerModules) { settings.add_bool_toggle( - previewHandler->GetToggleSettingName(), - previewHandler->GetToggleSettingDescription(), - previewHandler->GetToggleSettingState()); - } - - for (auto thumbnailProvider : this->m_thumbnailProviders) - { - settings.add_bool_toggle( - thumbnailProvider->GetToggleSettingName(), - thumbnailProvider->GetToggleSettingDescription(), - thumbnailProvider->GetToggleSettingState()); + fileExplorerModule->GetToggleSettingName(), + fileExplorerModule->GetToggleSettingDescription(), + fileExplorerModule->GetToggleSettingState()); } return settings.serialize_to_buffer(buffer, buffer_size); @@ -90,16 +103,19 @@ void PowerPreviewModule::set_config(const wchar_t* config) { try { - PowerToysSettings::PowerToyValues settings = PowerToysSettings::PowerToyValues::from_json_string(config); + PowerToysSettings::PowerToyValues settings = PowerToysSettings::PowerToyValues::from_json_string(config, get_key()); - for (auto previewHandler : this->m_previewHandlers) + bool updateSuccess = true; + bool isElevated = is_process_elevated(false); + for (auto fileExplorerModule : this->m_fileExplorerModules) { - previewHandler->UpdateState(settings, this->m_enabled); + // If the user is using the new settings interface, as it does not have a toggle to modify enabled consider File Explorer to always be enabled + updateSuccess = updateSuccess && fileExplorerModule->UpdateState(settings, this->m_enabled || UseNewSettings(), isElevated); } - for (auto thumbnailProvider : this->m_thumbnailProviders) + if (!updateSuccess) { - thumbnailProvider->UpdateState(settings, this->m_enabled); + show_update_warning_message(); } settings.save_to_settings_file(); @@ -113,30 +129,10 @@ void PowerPreviewModule::set_config(const wchar_t* config) // Enable preview handlers. void PowerPreviewModule::enable() { - for (auto previewHandler : this->m_previewHandlers) + // Should only be done for old settings as it is already done for new settings in the constructor. + if (!UseNewSettings()) { - if (previewHandler->GetToggleSettingState()) - { - // Enable all the previews with initial state set as true. - previewHandler->EnablePreview(); - } - else - { - previewHandler->DisablePreview(); - } - } - - for (auto thumbnailProvider : this->m_thumbnailProviders) - { - if (thumbnailProvider->GetToggleSettingState()) - { - // Enable all the thumbnail providers with initial state set as true. - thumbnailProvider->EnableThumbnailProvider(); - } - else - { - thumbnailProvider->DisableThumbnailProvider(); - } + update_registry_to_match_toggles(); } if (!this->m_enabled) @@ -150,15 +146,12 @@ void PowerPreviewModule::enable() // Disable active preview handlers. void PowerPreviewModule::disable() { - for (auto previewHandler : this->m_previewHandlers) - { - previewHandler->DisablePreview(); - } - - for (auto thumbnailProvider : this->m_thumbnailProviders) - { - thumbnailProvider->DisableThumbnailProvider(); - } + elevation_check_wrapper([this]() { + for (auto fileExplorerModule : this->m_fileExplorerModules) + { + fileExplorerModule->Disable(); + } + }); if (this->m_enabled) { @@ -181,21 +174,84 @@ void PowerPreviewModule::init_settings() { // Load and parse the settings file for this PowerToy. PowerToysSettings::PowerToyValues settings = - PowerToysSettings::PowerToyValues::load_from_settings_file(PowerPreviewModule::get_name()); + PowerToysSettings::PowerToyValues::load_from_settings_file(PowerPreviewModule::get_key()); // Load settings states. - for (auto previewHandler : this->m_previewHandlers) + for (auto fileExplorerModule : this->m_fileExplorerModules) { - previewHandler->LoadState(settings); - } - - for (auto thumbnailProvider : this->m_thumbnailProviders) - { - thumbnailProvider->LoadState(settings); + fileExplorerModule->LoadState(settings); } } catch (std::exception const& e) { Trace::InitSetErrorLoadingFile(e.what()); } -} \ No newline at end of file +} + +// Function to check if the registry states need to be updated +bool PowerPreviewModule::is_registry_update_required() +{ + for (auto fileExplorerModule : this->m_fileExplorerModules) + { + if (fileExplorerModule->GetToggleSettingState() != fileExplorerModule->CheckRegistryState()) + { + return true; + } + } + + return false; +} + +// Function to warn the user that PowerToys needs to run as administrator for changes to take effect +void PowerPreviewModule::show_update_warning_message() +{ + // Show warning message if update is required + MessageBoxW(NULL, + GET_RESOURCE_STRING(IDS_FILEEXPLORER_ADMIN_RESTART_WARNING_DESCRIPTION).c_str(), + GET_RESOURCE_STRING(IDS_FILEEXPLORER_ADMIN_RESTART_WARNING_TITLE).c_str(), + MB_OK | MB_ICONWARNING); +} + +// Function that checks if a registry method is required and if so checks if the process is elevated and accordingly executes the method or shows a warning +void PowerPreviewModule::registry_and_elevation_check_wrapper(std::function method) +{ + // Check if a registry update is required + if (is_registry_update_required()) + { + elevation_check_wrapper(method); + } +} + +// Function that checks if the process is elevated and accordingly executes the method or shows a warning +void PowerPreviewModule::elevation_check_wrapper(std::function method) +{ + // Check if the process is elevated in order to have permissions to modify HKLM registry + if (is_process_elevated(false)) + { + method(); + } + // Show a warning if it doesn't have permissions + else + { + show_update_warning_message(); + } +} + +// Function that updates the registry state to match the toggle states +void PowerPreviewModule::update_registry_to_match_toggles() +{ + registry_and_elevation_check_wrapper([this]() { + for (auto fileExplorerModule : this->m_fileExplorerModules) + { + if (fileExplorerModule->GetToggleSettingState()) + { + // Enable all the modules with initial state set as true. + fileExplorerModule->Enable(); + } + else + { + fileExplorerModule->Disable(); + } + } + }); +} diff --git a/src/modules/previewpane/powerpreview/powerpreview.h b/src/modules/previewpane/powerpreview/powerpreview.h index e0920712bc..b88387e758 100644 --- a/src/modules/previewpane/powerpreview/powerpreview.h +++ b/src/modules/previewpane/powerpreview/powerpreview.h @@ -3,7 +3,10 @@ #include #include "trace.h" #include "settings.h" +#include "thumbnail_provider.h" +#include "preview_handler.h" #include "registry_wrapper.h" +#include using namespace PowerPreviewSettings; @@ -16,45 +19,31 @@ private: // The PowerToy state. bool m_enabled = false; std::wstring m_moduleName; - std::vector m_previewHandlers; - std::vector m_thumbnailProviders; + //contains the non localized key of the powertoy + std::wstring app_key; + std::vector m_fileExplorerModules; + + // Function to check if the registry states need to be updated + bool is_registry_update_required(); + + // Function to warn the user that PowerToys needs to run as administrator for changes to take effect + void show_update_warning_message(); + + // Function that checks if a registry method is required and if so checks if the process is elevated and accordingly executes the method or shows a warning + void registry_and_elevation_check_wrapper(std::function method); + + // Function that checks if the process is elevated and accordingly executes the method or shows a warning + void elevation_check_wrapper(std::function method); + + // Function that updates the registry state to match the toggle states + void update_registry_to_match_toggles(); public: - PowerPreviewModule() : - m_moduleName(GET_RESOURCE_STRING(IDS_MODULE_NAME)), - m_previewHandlers( - { // SVG Preview Handler settings object. - new FileExplorerPreviewSettings( - true, - L"svg-previewer-toggle-setting", - GET_RESOURCE_STRING(IDS_PREVPANE_SVG_SETTINGS_DESCRIPTION), - L"{ddee2b8a-6807-48a6-bb20-2338174ff779}", - L"SVG Preview Handler", - new RegistryWrapper()), - - // MarkDown Preview Handler Settings Object. - new FileExplorerPreviewSettings( - true, - L"md-previewer-toggle-setting", - GET_RESOURCE_STRING(IDS_PREVPANE_MD_SETTINGS_DESCRIPTION), - L"{45769bcc-e8fd-42d0-947e-02beef77a1f5}", - L"Markdown Preview Handler", - new RegistryWrapper()) }), - m_thumbnailProviders( - { // TODO: MOVE THIS SVG Thumbnail Provider settings object. - new FileExplorerPreviewSettings( - true, - L"svg-thumbnail-toggle-setting", - GET_RESOURCE_STRING(IDS_SVG_THUMBNAIL_PROVIDER_SETTINGS_DESCRIPTION), - L"{36B27788-A8BB-4698-A756-DF9F11F64F84}", - L"SVG Thumbnail Provider", - new RegistryWrapper()) }) - { - init_settings(); - }; + PowerPreviewModule(); virtual void destroy(); virtual const wchar_t* get_name(); + virtual const wchar_t* get_key(); virtual bool get_config(_Out_ wchar_t* buffer, _Out_ int* buffer_size); virtual void set_config(const wchar_t* config); virtual void enable(); diff --git a/src/modules/previewpane/powerpreview/powerpreview.vcxproj b/src/modules/previewpane/powerpreview/powerpreview.vcxproj index ce130a6fb3..b8aaea835b 100644 --- a/src/modules/previewpane/powerpreview/powerpreview.vcxproj +++ b/src/modules/previewpane/powerpreview/powerpreview.vcxproj @@ -85,7 +85,7 @@ true true NDEBUG;EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - ..\..\..\common\Telemetry;..\..\..\common;..\..\common\inc;..\common\Telemetry;..\;..\..\;..\..\..\deps\cpprestsdk\include;%(AdditionalIncludeDirectories) + ..\;..\..\..\common;..\..\..\common\telemetry;..\..\;..\..\..\;..\..\..\..\deps\cpprestsdk\include;%(AdditionalIncludeDirectories) MultiThreaded stdcpplatest @@ -109,11 +109,14 @@ + + + @@ -123,8 +126,10 @@ + + diff --git a/src/modules/previewpane/powerpreview/powerpreview.vcxproj.filters b/src/modules/previewpane/powerpreview/powerpreview.vcxproj.filters index 584f6254d1..d1bff4109d 100644 --- a/src/modules/previewpane/powerpreview/powerpreview.vcxproj.filters +++ b/src/modules/previewpane/powerpreview/powerpreview.vcxproj.filters @@ -19,6 +19,12 @@ Source Files + + Source Files + + + Source Files + @@ -43,6 +49,12 @@ Header Files + + Header Files + + + Header Files + diff --git a/src/modules/previewpane/powerpreview/powerpreviewConstants.h b/src/modules/previewpane/powerpreview/powerpreviewConstants.h new file mode 100644 index 0000000000..3f1c2b58a7 --- /dev/null +++ b/src/modules/previewpane/powerpreview/powerpreviewConstants.h @@ -0,0 +1,8 @@ +#pragma once +#include + +namespace powerpreviewConstants +{ + // Name of the powertoy module. + inline const std::wstring ModuleKey = L"File Explorer"; +} \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/preview_handler.cpp b/src/modules/previewpane/powerpreview/preview_handler.cpp new file mode 100644 index 0000000000..04f98f0736 --- /dev/null +++ b/src/modules/previewpane/powerpreview/preview_handler.cpp @@ -0,0 +1,53 @@ +#include "pch.h" +#include "preview_handler.h" + +namespace PowerPreviewSettings +{ + const LPCWSTR PreviewHandlerSettings::preview_handlers_subkey = L"Software\\Microsoft\\Windows\\CurrentVersion\\PreviewHandlers"; + + // Function to enable the preview handler in registry + LONG PreviewHandlerSettings::Enable() + { + // Add registry value to enable preview. + return this->m_registryWrapper->SetRegistryValue(HKEY_LOCAL_MACHINE, preview_handlers_subkey, this->GetCLSID(), REG_SZ, (LPBYTE)this->GetRegistryValueData().c_str(), (DWORD)(this->GetRegistryValueData().length() * sizeof(wchar_t))); + } + + // Function to disable the preview handler in registry + LONG PreviewHandlerSettings::Disable() + { + // Delete the registry key to disable preview. + return this->m_registryWrapper->DeleteRegistryValue(HKEY_LOCAL_MACHINE, preview_handlers_subkey, this->GetCLSID()); + } + + // Function to check if the preview handler is enabled in registry + bool PreviewHandlerSettings::CheckRegistryState() + { + DWORD dataType; + DWORD byteCount = 255; + wchar_t regValue[255] = { 0 }; + + LONG errorCode = this->m_registryWrapper->GetRegistryValue(HKEY_LOCAL_MACHINE, preview_handlers_subkey, this->GetCLSID(), &dataType, regValue, &byteCount); + + // Registry value was found + if (errorCode == ERROR_SUCCESS) + { + // Check if the value type is string + if (dataType == REG_SZ) + { + // Check if the current registry value matches the expected value + if (wcscmp(regValue, this->GetRegistryValueData().c_str()) == 0) + { + return true; + } + } + } + + return false; + } + + // Function to retrieve the registry subkey + LPCWSTR PreviewHandlerSettings::GetSubkey() + { + return preview_handlers_subkey; + } +} diff --git a/src/modules/previewpane/powerpreview/preview_handler.h b/src/modules/previewpane/powerpreview/preview_handler.h new file mode 100644 index 0000000000..c2d79d9b84 --- /dev/null +++ b/src/modules/previewpane/powerpreview/preview_handler.h @@ -0,0 +1,31 @@ +#pragma once +#include "settings.h" + +namespace PowerPreviewSettings +{ + class PreviewHandlerSettings : + public FileExplorerPreviewSettings + { + private: + // Relative(HKLM/HKCU) sub key path of Preview Handlers list in registry. Registry key for Preview Handlers is generally HKLM\Software\Microsoft\Windows\CurrentVersion\PreviewHandlers, and the value name with CLSID of the handler in it is set to the name of the handler + static const LPCWSTR preview_handlers_subkey; + + public: + PreviewHandlerSettings(bool toggleSettingEnabled, const std::wstring& toggleSettingName, const std::wstring& toggleSettingDescription, LPCWSTR clsid, const std::wstring& registryValueData, RegistryWrapperIface* registryWrapper) : + FileExplorerPreviewSettings(toggleSettingEnabled, toggleSettingName, toggleSettingDescription, clsid, registryValueData, registryWrapper) + { + } + + // Function to enable the preview handler in registry + LONG Enable(); + + // Function to disable the preview handler in registry + LONG Disable(); + + // Function to check if the preview handler is enabled in registry + bool CheckRegistryState(); + + // Function to retrieve the registry subkey + static LPCWSTR GetSubkey(); + }; +} diff --git a/src/modules/previewpane/powerpreview/registry_wrapper.cpp b/src/modules/previewpane/powerpreview/registry_wrapper.cpp index 18ea8dd800..0a247f5f6c 100644 --- a/src/modules/previewpane/powerpreview/registry_wrapper.cpp +++ b/src/modules/previewpane/powerpreview/registry_wrapper.cpp @@ -23,7 +23,7 @@ namespace PowerPreviewSettings return err; } - LONG RegistryWrapper::GetRegistryValue(HKEY keyScope, LPCWSTR subKey, LPCWSTR valueName, DWORD dwType, LPDWORD pdwType, PVOID pvData, LPDWORD pcbData) + LONG RegistryWrapper::GetRegistryValue(HKEY keyScope, LPCWSTR subKey, LPCWSTR valueName, LPDWORD pdwType, PVOID pvData, LPDWORD pcbData) { HKEY OpenResult; LONG err = RegOpenKeyExW(keyScope, subKey, 0, KEY_READ, &OpenResult); @@ -61,4 +61,3 @@ namespace PowerPreviewSettings return err; } } - \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/registry_wrapper.h b/src/modules/previewpane/powerpreview/registry_wrapper.h index 906040bce8..40fcb8350e 100644 --- a/src/modules/previewpane/powerpreview/registry_wrapper.h +++ b/src/modules/previewpane/powerpreview/registry_wrapper.h @@ -8,6 +8,6 @@ namespace PowerPreviewSettings public: virtual LONG SetRegistryValue(HKEY keyScope, LPCWSTR subKey, LPCWSTR valueName, DWORD dwType, CONST BYTE* data, DWORD cbData); virtual LONG DeleteRegistryValue(HKEY keyScope, LPCWSTR subKey, LPCWSTR valueName); - virtual LONG GetRegistryValue(HKEY keyScope, LPCWSTR subKey, LPCWSTR valueName, DWORD dwType, LPDWORD pdwType, PVOID pvData, LPDWORD pcbData); + virtual LONG GetRegistryValue(HKEY keyScope, LPCWSTR subKey, LPCWSTR valueName, LPDWORD pdwType, PVOID pvData, LPDWORD pcbData); }; } \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/registry_wrapper_interface.h b/src/modules/previewpane/powerpreview/registry_wrapper_interface.h index 2b1485271d..767d0e8080 100644 --- a/src/modules/previewpane/powerpreview/registry_wrapper_interface.h +++ b/src/modules/previewpane/powerpreview/registry_wrapper_interface.h @@ -10,5 +10,5 @@ public: virtual LONG DeleteRegistryValue(HKEY keyScope, LPCWSTR subKey, LPCWSTR valueName) = 0; // Reads a registry value. - virtual LONG GetRegistryValue(HKEY keyScope, LPCWSTR subKey, LPCWSTR valueName, DWORD dwType, LPDWORD pdwType, PVOID pvData, LPDWORD pcbData) = 0; + virtual LONG GetRegistryValue(HKEY keyScope, LPCWSTR subKey, LPCWSTR valueName, LPDWORD pdwType, PVOID pvData, LPDWORD pcbData) = 0; }; \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/settings.cpp b/src/modules/previewpane/powerpreview/settings.cpp index 93ffd501a2..aa017f3492 100644 --- a/src/modules/previewpane/powerpreview/settings.cpp +++ b/src/modules/previewpane/powerpreview/settings.cpp @@ -11,12 +11,6 @@ namespace PowerPreviewSettings { extern "C" IMAGE_DOS_HEADER __ImageBase; - // Relative(HKLM/HKCU) sub key path of Preview Handlers list in registry. - static LPCWSTR preview_handlers_subkey = L"Software\\Microsoft\\Windows\\CurrentVersion\\PreviewHandlers"; - - // Relative HKCR sub key path of SVG thumbnail provider in registry - static LPCWSTR svg_thumbnail_provider_subkey = L".svg\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}"; - // Base Settings Class Implementation FileExplorerPreviewSettings::FileExplorerPreviewSettings(bool toggleSettingEnabled, const std::wstring& toggleSettingName, const std::wstring& toggleSettingDescription, LPCWSTR clsid, const std::wstring& registryValueData, RegistryWrapperIface* registryWrapper) : m_toggleSettingEnabled(toggleSettingEnabled), @@ -28,7 +22,7 @@ namespace PowerPreviewSettings { } - FileExplorerPreviewSettings::~FileExplorerPreviewSettings() + FileExplorerPreviewSettings::~FileExplorerPreviewSettings() { if (this->m_registryWrapper != NULL) { @@ -66,7 +60,7 @@ namespace PowerPreviewSettings return this->m_registryValueData; } - // Load initial state of the Preview Handler. If no inital state present initialize setting with default value. + // Load initial state of the file explorer module. If no inital state present initialize setting with default value. void FileExplorerPreviewSettings::LoadState(PowerToysSettings::PowerToyValues& settings) { auto toggle = settings.get_bool_value(this->GetToggleSettingName()); @@ -77,8 +71,8 @@ namespace PowerPreviewSettings } } - // Manage change in state of Preview Handler settings. - void FileExplorerPreviewSettings::UpdateState(PowerToysSettings::PowerToyValues& settings, bool enabled) + // Manage change in state of file explorer module settings. + bool FileExplorerPreviewSettings::UpdateState(PowerToysSettings::PowerToyValues& settings, bool enabled, bool isElevated) { auto toggle = settings.get_bool_value(this->GetToggleSettingName()); if (toggle) @@ -89,27 +83,40 @@ namespace PowerPreviewSettings { this->UpdateToggleSettingState(newState); - // If global setting is enable. Add or remove the preview handler otherwise just change the UI and save the updated config. + // If global setting is enable. Add or remove the file explorer module otherwise just change the UI and save the updated config. if (enabled) { - LONG err; - if (lastState) + // Check if the registry state does not match the new state, registry needs to be modified + if (this->CheckRegistryState() != newState) { - err = this->DisablePreview(); - } - else - { - err = this->EnablePreview(); - } + if (isElevated) + { + LONG err; + if (lastState) + { + err = this->Disable(); + } + else + { + err = this->Enable(); + } - if (err == ERROR_SUCCESS) - { - Trace::PowerPreviewSettingsUpdated(this->GetToggleSettingName().c_str(), lastState, newState, enabled); - } - else - { - Trace::PowerPreviewSettingsUpdateFailed(this->GetToggleSettingName().c_str(), lastState, newState, enabled); + if (err == ERROR_SUCCESS) + { + Trace::PowerPreviewSettingsUpdated(this->GetToggleSettingName().c_str(), lastState, newState, enabled); + } + else + { + Trace::PowerPreviewSettingsUpdateFailed(this->GetToggleSettingName().c_str(), lastState, newState, enabled); + } + } + // If process is not elevated, return false as it is not possible to update the registry + else + { + return false; + } } + // If it matches the new state, no update to registry is required } else { @@ -117,29 +124,7 @@ namespace PowerPreviewSettings } } } - } - LONG FileExplorerPreviewSettings::EnablePreview() - { - // Add registry value to enable preview. - return this->m_registryWrapper->SetRegistryValue(HKEY_CURRENT_USER, preview_handlers_subkey, this->GetCLSID(), REG_SZ, (LPBYTE)this->GetRegistryValueData().c_str(), (DWORD)(this->GetRegistryValueData().length() * sizeof(wchar_t))); - } - - LONG FileExplorerPreviewSettings::DisablePreview() - { - // Delete the registry key to disable preview. - return this->m_registryWrapper->DeleteRegistryValue(HKEY_CURRENT_USER, preview_handlers_subkey, this->GetCLSID()); - } - - LONG FileExplorerPreviewSettings::EnableThumbnailProvider() - { - // Add registry value to enable thumbnail provider. - return this->m_registryWrapper->SetRegistryValue(HKEY_CURRENT_USER, svg_thumbnail_provider_subkey, this->GetCLSID(), REG_SZ, (LPBYTE)this->GetRegistryValueData().c_str(), (DWORD)(this->GetRegistryValueData().length() * sizeof(wchar_t))); - } - - LONG FileExplorerPreviewSettings::DisableThumbnailProvider() - { - // Delete the registry key to disable thumbnail provider. - return this->m_registryWrapper->DeleteRegistryValue(HKEY_CURRENT_USER, svg_thumbnail_provider_subkey, this->GetCLSID()); + return true; } } diff --git a/src/modules/previewpane/powerpreview/settings.h b/src/modules/previewpane/powerpreview/settings.h index 3b52256021..431c93feb1 100644 --- a/src/modules/previewpane/powerpreview/settings.h +++ b/src/modules/previewpane/powerpreview/settings.h @@ -15,12 +15,14 @@ namespace PowerPreviewSettings std::wstring m_toggleSettingName; std::wstring m_toggleSettingDescription; std::wstring m_registryValueData; - RegistryWrapperIface * m_registryWrapper; LPCWSTR m_clsid; + protected: + RegistryWrapperIface* m_registryWrapper; + public: FileExplorerPreviewSettings(bool toggleSettingEnabled, const std::wstring& toggleSettingName, const std::wstring& toggleSettingDescription, LPCWSTR clsid, const std::wstring& registryValueData, RegistryWrapperIface* registryWrapper); - ~ FileExplorerPreviewSettings(); + ~FileExplorerPreviewSettings(); virtual bool GetToggleSettingState() const; virtual void UpdateToggleSettingState(bool state); @@ -29,10 +31,9 @@ namespace PowerPreviewSettings virtual LPCWSTR GetCLSID() const; virtual std::wstring GetRegistryValueData() const; virtual void LoadState(PowerToysSettings::PowerToyValues& settings); - virtual void UpdateState(PowerToysSettings::PowerToyValues& settings, bool enabled); - virtual LONG EnablePreview(); - virtual LONG DisablePreview(); - virtual LONG EnableThumbnailProvider(); - virtual LONG DisableThumbnailProvider(); + virtual bool UpdateState(PowerToysSettings::PowerToyValues& settings, bool enabled, bool isElevated); + virtual LONG Enable() = 0; + virtual LONG Disable() = 0; + virtual bool CheckRegistryState() = 0; }; } diff --git a/src/modules/previewpane/powerpreview/thumbnail_provider.cpp b/src/modules/previewpane/powerpreview/thumbnail_provider.cpp new file mode 100644 index 0000000000..e7540ed8b2 --- /dev/null +++ b/src/modules/previewpane/powerpreview/thumbnail_provider.cpp @@ -0,0 +1,51 @@ +#include "pch.h" +#include "thumbnail_provider.h" + +namespace PowerPreviewSettings +{ + // Function to enable the thumbnail provider in registry + LONG ThumbnailProviderSettings::Enable() + { + // Add registry value to enable thumbnail provider. + return this->m_registryWrapper->SetRegistryValue(HKEY_CLASSES_ROOT, thumbnail_provider_subkey, nullptr, REG_SZ, (LPBYTE)this->GetCLSID(), (DWORD)(wcslen(this->GetCLSID()) * sizeof(wchar_t))); + } + + // Function to disable the thumbnail provider in registry + LONG ThumbnailProviderSettings::Disable() + { + // Delete the registry key to disable thumbnail provider. + return this->m_registryWrapper->DeleteRegistryValue(HKEY_CLASSES_ROOT, thumbnail_provider_subkey, nullptr); + } + + // Function to check if the thumbnail provider is enabled in registry + bool ThumbnailProviderSettings::CheckRegistryState() + { + DWORD dataType; + DWORD byteCount = 255; + wchar_t regValue[255] = { 0 }; + + LONG errorCode = this->m_registryWrapper->GetRegistryValue(HKEY_CLASSES_ROOT, thumbnail_provider_subkey, nullptr, &dataType, regValue, &byteCount); + + // Registry value was found + if (errorCode == ERROR_SUCCESS) + { + // Check if the value type is string + if (dataType == REG_SZ) + { + // Check if the current registry value matches the expected value + if (wcscmp(regValue, this->GetCLSID()) == 0) + { + return true; + } + } + } + + return false; + } + + // Function to retrieve the registry subkey + LPCWSTR ThumbnailProviderSettings::GetSubkey() + { + return thumbnail_provider_subkey; + } +} diff --git a/src/modules/previewpane/powerpreview/thumbnail_provider.h b/src/modules/previewpane/powerpreview/thumbnail_provider.h new file mode 100644 index 0000000000..03eba9e306 --- /dev/null +++ b/src/modules/previewpane/powerpreview/thumbnail_provider.h @@ -0,0 +1,31 @@ +#pragma once +#include "settings.h" + +namespace PowerPreviewSettings +{ + class ThumbnailProviderSettings : + public FileExplorerPreviewSettings + { + private: + // Relative HKCR sub key path of thumbnail provider in registry. Registry key for Thumbnail Providers is generally HKCR\fileExtension\{E357FCCD-A995-4576-B01F-234630154E96}, and the default value in it is set to the CLSID of the provider + LPCWSTR thumbnail_provider_subkey; + + public: + ThumbnailProviderSettings(bool toggleSettingEnabled, const std::wstring& toggleSettingName, const std::wstring& toggleSettingDescription, LPCWSTR clsid, const std::wstring& registryValueData, RegistryWrapperIface* registryWrapper, LPCWSTR subkey) : + FileExplorerPreviewSettings(toggleSettingEnabled, toggleSettingName, toggleSettingDescription, clsid, registryValueData, registryWrapper), thumbnail_provider_subkey(subkey) + { + } + + // Function to enable the thumbnail provider in registry + LONG Enable(); + + // Function to disable the thumbnail provider in registry + LONG Disable(); + + // Function to check if the thumbnail provider is enabled in registry + bool CheckRegistryState(); + + // Function to retrieve the registry subkey + LPCWSTR GetSubkey(); + }; +} diff --git a/src/modules/previewpane/powerpreviewTest/FileExplorerPreviewSettingsTest.cpp b/src/modules/previewpane/powerpreviewTest/FileExplorerPreviewSettingsTest.cpp index d2cbd27351..fe66476dcd 100644 --- a/src/modules/previewpane/powerpreviewTest/FileExplorerPreviewSettingsTest.cpp +++ b/src/modules/previewpane/powerpreviewTest/FileExplorerPreviewSettingsTest.cpp @@ -5,12 +5,14 @@ #include #include #include +#include +#include using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace PowerToysSettings; using namespace PowerPreviewSettings; -namespace PreviewHandlerSettingsTest +namespace FileExplorerPreviewSettingsTest { extern "C" IMAGE_DOS_HEADER __ImageBase; @@ -22,10 +24,14 @@ namespace PreviewHandlerSettingsTest HKEY Scope = NULL; LPCWSTR SubKey; LPCWSTR ValueName; + wchar_t ValueData[255] = { 0 }; }; class RegistryMock : public RegistryWrapperIface { + private: + wchar_t mockData[255] = { 0 }; + public: FunctionProperties SetRegistryMockProperties; FunctionProperties DeleteRegistryMockProperties; @@ -37,6 +43,7 @@ namespace PreviewHandlerSettingsTest SetRegistryMockProperties.Scope = keyScope; SetRegistryMockProperties.SubKey = subKey; SetRegistryMockProperties.ValueName = valueName; + wcscpy_s(SetRegistryMockProperties.ValueData, cbData, (WCHAR*)data); return SetRegistryMockProperties.ReturnValue; } @@ -49,27 +56,33 @@ namespace PreviewHandlerSettingsTest return DeleteRegistryMockProperties.ReturnValue; } - LONG GetRegistryValue(HKEY keyScope, LPCWSTR subKey, LPCWSTR valueName, DWORD dwType, LPDWORD pdwType, PVOID pvData, LPDWORD pcbData) + LONG GetRegistryValue(HKEY keyScope, LPCWSTR subKey, LPCWSTR valueName, LPDWORD pdwType, PVOID pvData, LPDWORD pcbData) { GetRegistryMockProperties.NumOfCalls++; GetRegistryMockProperties.Scope = keyScope; GetRegistryMockProperties.SubKey = subKey; GetRegistryMockProperties.ValueName = valueName; + *pdwType = REG_SZ; + wcscpy_s((LPWSTR)pvData, 255, mockData); return GetRegistryMockProperties.ReturnValue; } + + void SetMockData(std::wstring data) + { + wcscpy_s(mockData, data.c_str()); + } }; - TEST_CLASS(BaseSettingsTest) - { - public: - + TEST_CLASS (BaseSettingsTest) + { + public: TEST_METHOD (LoadState_ShouldLoadValidState_IfInitalStateIsPresent) { // Arrange bool defaultState = true; RegistryMock* mockRegistryWrapper = new RegistryMock(); - FileExplorerPreviewSettings previewSettings = GetSettingsObject(defaultState, mockRegistryWrapper); - auto settings = PowerToyValues::from_json_string(GetJSONSettings(previewSettings.GetToggleSettingName(), L"false")); + PreviewHandlerSettings previewSettings = GetPreviewHandlerSettingsObject(defaultState, mockRegistryWrapper); + auto settings = PowerToyValues::from_json_string(GetJSONSettings(previewSettings.GetToggleSettingName(), L"false"), L"FileExplorerPreviewTests"); // Act previewSettings.LoadState(settings); @@ -83,8 +96,8 @@ namespace PreviewHandlerSettingsTest // Arrange bool defaultState = true; RegistryMock* mockRegistryWrapper = new RegistryMock(); - FileExplorerPreviewSettings previewSettings = GetSettingsObject(defaultState, mockRegistryWrapper); - auto settings = PowerToyValues::from_json_string(L"{\"name\":\"Module Name\"}"); + PreviewHandlerSettings previewSettings = GetPreviewHandlerSettingsObject(defaultState, mockRegistryWrapper); + auto settings = PowerToyValues::from_json_string(L"{\"name\":\"Module Name\"}", L"FileExplorerPreviewTests"); // Act previewSettings.LoadState(settings); @@ -93,50 +106,223 @@ namespace PreviewHandlerSettingsTest Assert::AreEqual(previewSettings.GetToggleSettingState(), defaultState); } - TEST_METHOD (UpdateState_ShouldDisablePreview_IfPreviewsAreEnabledAndNewSettingsStateIsFalse) + TEST_METHOD (PreviewHandlerSettingsUpdateState_ShouldDisableInRegistry_IfPreviewsAreEnabledAndNewSettingsStateIsFalseAndPowerToysIsElevatedAndRegistryContainsThePreview) { // Arrange bool enabled = true; + bool elevated = true; RegistryMock* mockRegistryWrapper = new RegistryMock(); - FileExplorerPreviewSettings previewSettings = GetSettingsObject(true, mockRegistryWrapper); - auto settings = PowerToyValues::from_json_string(GetJSONSettings(previewSettings.GetToggleSettingName(), L"false")); + PreviewHandlerSettings previewSettings = GetPreviewHandlerSettingsObject(true, mockRegistryWrapper); + auto settings = PowerToyValues::from_json_string(GetJSONSettings(previewSettings.GetToggleSettingName(), L"false"), L"FileExplorerPreviewTests"); previewSettings.UpdateToggleSettingState(true); + // Add expected data in registry + mockRegistryWrapper->SetMockData(previewSettings.GetRegistryValueData()); // Act - previewSettings.UpdateState(settings, enabled); + previewSettings.UpdateState(settings, enabled, elevated); // Assert Assert::IsFalse(previewSettings.GetToggleSettingState()); Assert::AreEqual(mockRegistryWrapper->DeleteRegistryMockProperties.NumOfCalls, 1); + Assert::AreEqual(mockRegistryWrapper->GetRegistryMockProperties.NumOfCalls, 1); } - TEST_METHOD (UpdateState_ShouldEnablePreview_IfPreviewsAreEnabledAndNewSettingsStateIsTrue) + TEST_METHOD (ThumbnailProviderSettingsUpdateState_ShouldDisableInRegistry_IfPreviewsAreEnabledAndNewSettingsStateIsFalseAndPowerToysIsElevatedAndRegistryContainsThePreview) { // Arrange bool enabled = true; + bool elevated = true; RegistryMock* mockRegistryWrapper = new RegistryMock(); - FileExplorerPreviewSettings previewSettings = GetSettingsObject(true, mockRegistryWrapper); - auto settings = PowerToyValues::from_json_string(GetJSONSettings(previewSettings.GetToggleSettingName(), L"true")); + ThumbnailProviderSettings thumbnailSettings = GetThumbnailProviderSettingsObject(true, mockRegistryWrapper); + auto settings = PowerToyValues::from_json_string(GetJSONSettings(thumbnailSettings.GetToggleSettingName(), L"false"), L"FileExplorerPreviewTests"); + thumbnailSettings.UpdateToggleSettingState(true); + // Add expected data in registry + mockRegistryWrapper->SetMockData(thumbnailSettings.GetCLSID()); + + // Act + thumbnailSettings.UpdateState(settings, enabled, elevated); + + // Assert + Assert::IsFalse(thumbnailSettings.GetToggleSettingState()); + Assert::AreEqual(mockRegistryWrapper->DeleteRegistryMockProperties.NumOfCalls, 1); + Assert::AreEqual(mockRegistryWrapper->GetRegistryMockProperties.NumOfCalls, 1); + } + + TEST_METHOD (UpdateState_ShouldNotDisableInRegistry_IfPreviewsAreEnabledAndNewSettingsStateIsFalseAndPowerToysIsElevatedAndRegistryDoesNotContainThePreview) + { + // Arrange + bool enabled = true; + bool elevated = true; + RegistryMock* mockRegistryWrapper = new RegistryMock(); + PreviewHandlerSettings previewSettings = GetPreviewHandlerSettingsObject(true, mockRegistryWrapper); + auto settings = PowerToyValues::from_json_string(GetJSONSettings(previewSettings.GetToggleSettingName(), L"false"), L"FileExplorerPreviewTests"); + previewSettings.UpdateToggleSettingState(true); + + // Act + previewSettings.UpdateState(settings, enabled, elevated); + + // Assert + Assert::IsFalse(previewSettings.GetToggleSettingState()); + Assert::AreEqual(mockRegistryWrapper->DeleteRegistryMockProperties.NumOfCalls, 0); + Assert::AreEqual(mockRegistryWrapper->GetRegistryMockProperties.NumOfCalls, 1); + } + + TEST_METHOD (UpdateState_ShouldNotDisableInRegistry_IfPreviewsAreEnabledAndNewSettingsStateIsFalseAndPowerToysIsNotElevated) + { + // Arrange + bool enabled = true; + bool elevated = false; + RegistryMock* mockRegistryWrapper = new RegistryMock(); + PreviewHandlerSettings previewSettings = GetPreviewHandlerSettingsObject(true, mockRegistryWrapper); + auto settings = PowerToyValues::from_json_string(GetJSONSettings(previewSettings.GetToggleSettingName(), L"false"), L"FileExplorerPreviewTests"); + previewSettings.UpdateToggleSettingState(true); + + // Act + previewSettings.UpdateState(settings, enabled, elevated); + + // Assert + Assert::IsFalse(previewSettings.GetToggleSettingState()); + Assert::AreEqual(mockRegistryWrapper->DeleteRegistryMockProperties.NumOfCalls, 0); + Assert::AreEqual(mockRegistryWrapper->GetRegistryMockProperties.NumOfCalls, 1); + } + + TEST_METHOD (UpdateState_ShouldEnableInRegistry_IfPreviewsAreEnabledAndNewSettingsStateIsTrueAndPowerToysIsElevatedAndRegistryDoesNotContainThePreview) + { + // Arrange + bool enabled = true; + bool elevated = true; + RegistryMock* mockRegistryWrapper = new RegistryMock(); + PreviewHandlerSettings previewSettings = GetPreviewHandlerSettingsObject(true, mockRegistryWrapper); + auto settings = PowerToyValues::from_json_string(GetJSONSettings(previewSettings.GetToggleSettingName(), L"true"), L"FileExplorerPreviewTests"); previewSettings.UpdateToggleSettingState(false); // Act - previewSettings.UpdateState(settings, enabled); + previewSettings.UpdateState(settings, enabled, elevated); // Assert - Assert::IsTrue(previewSettings.GetToggleSettingState()); Assert::AreEqual(mockRegistryWrapper->SetRegistryMockProperties.NumOfCalls, 1); + Assert::AreEqual(mockRegistryWrapper->GetRegistryMockProperties.NumOfCalls, 1); } - TEST_METHOD (UpdateState_ShouldOnlyUpdateToggleSettingState_IfPreviewsAreDisabled) + TEST_METHOD (PreviewHandlerSettingsUpdateState_ShouldNotEnableInRegistry_IfPreviewsAreEnabledAndNewSettingsStateIsTrueAndPowerToysIsElevatedAndRegistryContainsThePreview) + { + // Arrange + bool enabled = true; + bool elevated = true; + RegistryMock* mockRegistryWrapper = new RegistryMock(); + PreviewHandlerSettings previewSettings = GetPreviewHandlerSettingsObject(true, mockRegistryWrapper); + auto settings = PowerToyValues::from_json_string(GetJSONSettings(previewSettings.GetToggleSettingName(), L"true"), L"FileExplorerPreviewTests"); + previewSettings.UpdateToggleSettingState(false); + // Add expected data in registry + mockRegistryWrapper->SetMockData(previewSettings.GetRegistryValueData()); + + // Act + previewSettings.UpdateState(settings, enabled, elevated); + + // Assert + Assert::AreEqual(mockRegistryWrapper->SetRegistryMockProperties.NumOfCalls, 0); + Assert::AreEqual(mockRegistryWrapper->GetRegistryMockProperties.NumOfCalls, 1); + } + + TEST_METHOD (ThumbnailProviderSettingsUpdateState_ShouldNotEnableInRegistry_IfPreviewsAreEnabledAndNewSettingsStateIsTrueAndPowerToysIsElevatedAndRegistryContainsThePreview) + { + // Arrange + bool enabled = true; + bool elevated = true; + RegistryMock* mockRegistryWrapper = new RegistryMock(); + ThumbnailProviderSettings thumbnailSettings = GetThumbnailProviderSettingsObject(true, mockRegistryWrapper); + auto settings = PowerToyValues::from_json_string(GetJSONSettings(thumbnailSettings.GetToggleSettingName(), L"true"), L"FileExplorerPreviewTests"); + thumbnailSettings.UpdateToggleSettingState(false); + // Add expected data in registry + mockRegistryWrapper->SetMockData(thumbnailSettings.GetCLSID()); + + // Act + thumbnailSettings.UpdateState(settings, enabled, elevated); + + // Assert + Assert::AreEqual(mockRegistryWrapper->SetRegistryMockProperties.NumOfCalls, 0); + Assert::AreEqual(mockRegistryWrapper->GetRegistryMockProperties.NumOfCalls, 1); + } + + TEST_METHOD (UpdateState_ShouldNotEnableInRegistry_IfPreviewsAreEnabledAndNewSettingsStateIsTrueAndPowerToysIsNotElevated) + { + // Arrange + bool enabled = true; + bool elevated = false; + RegistryMock* mockRegistryWrapper = new RegistryMock(); + PreviewHandlerSettings previewSettings = GetPreviewHandlerSettingsObject(true, mockRegistryWrapper); + auto settings = PowerToyValues::from_json_string(GetJSONSettings(previewSettings.GetToggleSettingName(), L"true"), L"FileExplorerPreviewTests"); + previewSettings.UpdateToggleSettingState(false); + + // Act + previewSettings.UpdateState(settings, enabled, elevated); + + // Assert + Assert::AreEqual(mockRegistryWrapper->SetRegistryMockProperties.NumOfCalls, 0); + Assert::AreEqual(mockRegistryWrapper->GetRegistryMockProperties.NumOfCalls, 1); + } + + TEST_METHOD (UpdateState_ShouldUpdateToggleSettingState_IfPreviewsAreEnabledAndPowerToysIsElevated) { // Arrange bool enabled = false; + bool elevated = true; RegistryMock* mockRegistryWrapper = new RegistryMock(); - FileExplorerPreviewSettings previewSettings = GetSettingsObject(true, mockRegistryWrapper); - auto settings = PowerToyValues::from_json_string(GetJSONSettings(previewSettings.GetToggleSettingName(), L"false")); + PreviewHandlerSettings previewSettings = GetPreviewHandlerSettingsObject(true, mockRegistryWrapper); + auto settings = PowerToyValues::from_json_string(GetJSONSettings(previewSettings.GetToggleSettingName(), L"false"), L"FileExplorerPreviewTests"); // Act - previewSettings.UpdateState(settings, enabled); + previewSettings.UpdateState(settings, enabled, elevated); + + // Assert + Assert::IsFalse(previewSettings.GetToggleSettingState()); + } + + TEST_METHOD (UpdateState_ShouldUpdateToggleSettingState_IfPreviewsAreEnabledAndPowerToysIsNotElevated) + { + // Arrange + bool enabled = false; + bool elevated = false; + RegistryMock* mockRegistryWrapper = new RegistryMock(); + PreviewHandlerSettings previewSettings = GetPreviewHandlerSettingsObject(true, mockRegistryWrapper); + auto settings = PowerToyValues::from_json_string(GetJSONSettings(previewSettings.GetToggleSettingName(), L"false"), L"FileExplorerPreviewTests"); + + // Act + previewSettings.UpdateState(settings, enabled, elevated); + + // Assert + Assert::IsFalse(previewSettings.GetToggleSettingState()); + } + + TEST_METHOD (UpdateState_ShouldOnlyUpdateToggleSettingState_IfPreviewsAreDisabledAndPowerToysIsElevated) + { + // Arrange + bool enabled = false; + bool elevated = true; + RegistryMock* mockRegistryWrapper = new RegistryMock(); + PreviewHandlerSettings previewSettings = GetPreviewHandlerSettingsObject(true, mockRegistryWrapper); + auto settings = PowerToyValues::from_json_string(GetJSONSettings(previewSettings.GetToggleSettingName(), L"false"), L"FileExplorerPreviewTests"); + + // Act + previewSettings.UpdateState(settings, enabled, elevated); + + // Assert + Assert::IsFalse(previewSettings.GetToggleSettingState()); + Assert::AreEqual(mockRegistryWrapper->SetRegistryMockProperties.NumOfCalls, 0); + Assert::AreEqual(mockRegistryWrapper->DeleteRegistryMockProperties.NumOfCalls, 0); + } + + TEST_METHOD (UpdateState_ShouldOnlyUpdateToggleSettingState_IfPreviewsAreDisabledAndPowerToysIsNotElevated) + { + // Arrange + bool enabled = false; + bool elevated = false; + RegistryMock* mockRegistryWrapper = new RegistryMock(); + PreviewHandlerSettings previewSettings = GetPreviewHandlerSettingsObject(true, mockRegistryWrapper); + auto settings = PowerToyValues::from_json_string(GetJSONSettings(previewSettings.GetToggleSettingName(), L"false"), L"FileExplorerPreviewTests"); + + // Act + previewSettings.UpdateState(settings, enabled, elevated); // Assert Assert::IsFalse(previewSettings.GetToggleSettingState()); @@ -148,7 +334,7 @@ namespace PreviewHandlerSettingsTest { // Arrange bool updatedState = false; - FileExplorerPreviewSettings previewSettings = GetSettingsObject(true, new RegistryMock()); + PreviewHandlerSettings previewSettings = GetPreviewHandlerSettingsObject(true, new RegistryMock()); // Act previewSettings.UpdateToggleSettingState(updatedState); @@ -157,52 +343,98 @@ namespace PreviewHandlerSettingsTest Assert::AreEqual(previewSettings.GetToggleSettingState(), updatedState); } - TEST_METHOD(EnablePreview_ShouldCallSetRegistryValueWithValidArguments_WhenCalled) + TEST_METHOD (PreviewHandlerSettingsEnable_ShouldCallSetRegistryValueWithValidArguments_WhenCalled) { // Arrange RegistryMock* mockRegistryWrapper = new RegistryMock(); - FileExplorerPreviewSettings previewSettings = GetSettingsObject(true, mockRegistryWrapper); + PreviewHandlerSettings previewSettings = GetPreviewHandlerSettingsObject(true, mockRegistryWrapper); // Act - previewSettings.EnablePreview(); + previewSettings.Enable(); // Assert Assert::AreEqual(mockRegistryWrapper->SetRegistryMockProperties.NumOfCalls, 1); - Assert::AreEqual(mockRegistryWrapper->SetRegistryMockProperties.SubKey, preview_handlers_subkey); + Assert::AreEqual(mockRegistryWrapper->SetRegistryMockProperties.SubKey, PreviewHandlerSettings::GetSubkey()); Assert::AreEqual(mockRegistryWrapper->SetRegistryMockProperties.ValueName, previewSettings.GetCLSID()); - Assert::AreEqual((ULONG_PTR)(mockRegistryWrapper->SetRegistryMockProperties.Scope), (ULONG_PTR)(HKEY_CURRENT_USER)); + Assert::AreEqual(mockRegistryWrapper->SetRegistryMockProperties.ValueData, previewSettings.GetRegistryValueData().c_str()); + Assert::AreEqual((ULONG_PTR)(mockRegistryWrapper->SetRegistryMockProperties.Scope), (ULONG_PTR)(HKEY_LOCAL_MACHINE)); } - TEST_METHOD(DisablePreview_ShouldCallDeleteRegistryValueWithValidArguments_WhenCalled) + TEST_METHOD (PreviewHandlerDisable_ShouldCallDeleteRegistryValueWithValidArguments_WhenCalled) { // Arrange RegistryMock* mockRegistryWrapper = new RegistryMock(); - FileExplorerPreviewSettings previewSettings = GetSettingsObject(true, mockRegistryWrapper); + PreviewHandlerSettings previewSettings = GetPreviewHandlerSettingsObject(true, mockRegistryWrapper); // Act - previewSettings.DisablePreview(); + previewSettings.Disable(); // Assert Assert::AreEqual(mockRegistryWrapper->DeleteRegistryMockProperties.NumOfCalls, 1); - Assert::AreEqual(mockRegistryWrapper->DeleteRegistryMockProperties.SubKey, preview_handlers_subkey); + Assert::AreEqual(mockRegistryWrapper->DeleteRegistryMockProperties.SubKey, PreviewHandlerSettings::GetSubkey()); Assert::AreEqual(mockRegistryWrapper->DeleteRegistryMockProperties.ValueName, previewSettings.GetCLSID()); - Assert::AreEqual((ULONG_PTR)(mockRegistryWrapper->DeleteRegistryMockProperties.Scope), (ULONG_PTR)(HKEY_CURRENT_USER)); + Assert::AreEqual((ULONG_PTR)(mockRegistryWrapper->DeleteRegistryMockProperties.Scope), (ULONG_PTR)(HKEY_LOCAL_MACHINE)); } - FileExplorerPreviewSettings GetSettingsObject(bool defaultState, RegistryWrapperIface* registryMock) - { - return FileExplorerPreviewSettings( + TEST_METHOD (ThumbnailProviderSettingsEnable_ShouldCallSetRegistryValueWithValidArguments_WhenCalled) + { + // Arrange + RegistryMock* mockRegistryWrapper = new RegistryMock(); + ThumbnailProviderSettings thumbnailSettings = GetThumbnailProviderSettingsObject(true, mockRegistryWrapper); + + // Act + thumbnailSettings.Enable(); + + // Assert + Assert::AreEqual(mockRegistryWrapper->SetRegistryMockProperties.NumOfCalls, 1); + Assert::AreEqual(mockRegistryWrapper->SetRegistryMockProperties.SubKey, thumbnailSettings.GetSubkey()); + Assert::AreEqual(mockRegistryWrapper->SetRegistryMockProperties.ValueName, nullptr); + Assert::AreEqual(mockRegistryWrapper->SetRegistryMockProperties.ValueData, thumbnailSettings.GetCLSID()); + Assert::AreEqual((ULONG_PTR)(mockRegistryWrapper->SetRegistryMockProperties.Scope), (ULONG_PTR)(HKEY_CLASSES_ROOT)); + } + + TEST_METHOD (ThumbnailProviderSettingsDisable_ShouldCallDeleteRegistryValueWithValidArguments_WhenCalled) + { + // Arrange + RegistryMock* mockRegistryWrapper = new RegistryMock(); + ThumbnailProviderSettings thumbnailSettings = GetThumbnailProviderSettingsObject(true, mockRegistryWrapper); + + // Act + thumbnailSettings.Disable(); + + // Assert + Assert::AreEqual(mockRegistryWrapper->DeleteRegistryMockProperties.NumOfCalls, 1); + Assert::AreEqual(mockRegistryWrapper->DeleteRegistryMockProperties.SubKey, thumbnailSettings.GetSubkey()); + Assert::AreEqual(mockRegistryWrapper->DeleteRegistryMockProperties.ValueName, nullptr); + Assert::AreEqual((ULONG_PTR)(mockRegistryWrapper->DeleteRegistryMockProperties.Scope), (ULONG_PTR)(HKEY_CLASSES_ROOT)); + } + + PreviewHandlerSettings GetPreviewHandlerSettingsObject(bool defaultState, RegistryWrapperIface* registryMock) + { + return PreviewHandlerSettings( defaultState, L"valid-name", L"valid-description", L"valid-guid", L"valid-handler", registryMock); - } + } - std::wstring GetJSONSettings(const std::wstring &_settingsNameId, const std::wstring &_value) const - { - return L"{\"name\":\"Module Name\",\"properties\" : {\"" + _settingsNameId + L"\":{\"value\":" + _value + L"}},\"version\" : \"1.0\" }"; - } - }; + ThumbnailProviderSettings GetThumbnailProviderSettingsObject(bool defaultState, RegistryWrapperIface* registryMock) + { + return ThumbnailProviderSettings( + defaultState, + L"valid-name", + L"valid-description", + L"valid-guid", + L"valid-handler", + registryMock, + L"valid-subkey"); + } + + std::wstring GetJSONSettings(const std::wstring& _settingsNameId, const std::wstring& _value) const + { + return L"{\"name\":\"Module Name\",\"properties\" : {\"" + _settingsNameId + L"\":{\"value\":" + _value + L"}},\"version\" : \"1.0\" }"; + } + }; } diff --git a/src/modules/shortcut_guide/ShortcutGuideConstants.h b/src/modules/shortcut_guide/ShortcutGuideConstants.h new file mode 100644 index 0000000000..3d0e434abf --- /dev/null +++ b/src/modules/shortcut_guide/ShortcutGuideConstants.h @@ -0,0 +1,8 @@ +#pragma once +#include + +namespace ShortcutGuideConstants +{ + // Name of the powertoy module. + inline const std::wstring ModuleKey = L"Shortcut Guide"; +} \ No newline at end of file diff --git a/src/modules/shortcut_guide/loc/cs/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/shortcut_guide/loc/cs/src/modules/shortcut_guide/Resources.resx.lcl new file mode 100644 index 0000000000..72cf2dec99 --- /dev/null +++ b/src/modules/shortcut_guide/loc/cs/src/modules/shortcut_guide/Resources.resx.lcl @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/shortcut_guide/loc/de/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/shortcut_guide/loc/de/src/modules/shortcut_guide/Resources.resx.lcl new file mode 100644 index 0000000000..6d463fe8fd --- /dev/null +++ b/src/modules/shortcut_guide/loc/de/src/modules/shortcut_guide/Resources.resx.lcl @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/shortcut_guide/loc/es/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/shortcut_guide/loc/es/src/modules/shortcut_guide/Resources.resx.lcl new file mode 100644 index 0000000000..6e82ee35c9 --- /dev/null +++ b/src/modules/shortcut_guide/loc/es/src/modules/shortcut_guide/Resources.resx.lcl @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/shortcut_guide/loc/fr/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/shortcut_guide/loc/fr/src/modules/shortcut_guide/Resources.resx.lcl new file mode 100644 index 0000000000..e603b95328 --- /dev/null +++ b/src/modules/shortcut_guide/loc/fr/src/modules/shortcut_guide/Resources.resx.lcl @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/shortcut_guide/loc/hu/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/shortcut_guide/loc/hu/src/modules/shortcut_guide/Resources.resx.lcl new file mode 100644 index 0000000000..2a4f6cc6ca --- /dev/null +++ b/src/modules/shortcut_guide/loc/hu/src/modules/shortcut_guide/Resources.resx.lcl @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/shortcut_guide/loc/it/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/shortcut_guide/loc/it/src/modules/shortcut_guide/Resources.resx.lcl new file mode 100644 index 0000000000..771c487437 --- /dev/null +++ b/src/modules/shortcut_guide/loc/it/src/modules/shortcut_guide/Resources.resx.lcl @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/shortcut_guide/loc/ja/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/shortcut_guide/loc/ja/src/modules/shortcut_guide/Resources.resx.lcl new file mode 100644 index 0000000000..327133a4fc --- /dev/null +++ b/src/modules/shortcut_guide/loc/ja/src/modules/shortcut_guide/Resources.resx.lcl @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/shortcut_guide/loc/ko/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/shortcut_guide/loc/ko/src/modules/shortcut_guide/Resources.resx.lcl new file mode 100644 index 0000000000..9a06fd32ed --- /dev/null +++ b/src/modules/shortcut_guide/loc/ko/src/modules/shortcut_guide/Resources.resx.lcl @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/shortcut_guide/loc/nl/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/shortcut_guide/loc/nl/src/modules/shortcut_guide/Resources.resx.lcl new file mode 100644 index 0000000000..28db3238b4 --- /dev/null +++ b/src/modules/shortcut_guide/loc/nl/src/modules/shortcut_guide/Resources.resx.lcl @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/shortcut_guide/loc/pl/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/shortcut_guide/loc/pl/src/modules/shortcut_guide/Resources.resx.lcl new file mode 100644 index 0000000000..59e4186646 --- /dev/null +++ b/src/modules/shortcut_guide/loc/pl/src/modules/shortcut_guide/Resources.resx.lcl @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/shortcut_guide/loc/pt-BR/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/shortcut_guide/loc/pt-BR/src/modules/shortcut_guide/Resources.resx.lcl new file mode 100644 index 0000000000..d1363c60fc --- /dev/null +++ b/src/modules/shortcut_guide/loc/pt-BR/src/modules/shortcut_guide/Resources.resx.lcl @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/shortcut_guide/loc/pt-PT/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/shortcut_guide/loc/pt-PT/src/modules/shortcut_guide/Resources.resx.lcl new file mode 100644 index 0000000000..b322a6633f --- /dev/null +++ b/src/modules/shortcut_guide/loc/pt-PT/src/modules/shortcut_guide/Resources.resx.lcl @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/shortcut_guide/loc/ru/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/shortcut_guide/loc/ru/src/modules/shortcut_guide/Resources.resx.lcl new file mode 100644 index 0000000000..f9c2e7e96f --- /dev/null +++ b/src/modules/shortcut_guide/loc/ru/src/modules/shortcut_guide/Resources.resx.lcl @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/shortcut_guide/loc/sv/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/shortcut_guide/loc/sv/src/modules/shortcut_guide/Resources.resx.lcl new file mode 100644 index 0000000000..710d0f1c28 --- /dev/null +++ b/src/modules/shortcut_guide/loc/sv/src/modules/shortcut_guide/Resources.resx.lcl @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/shortcut_guide/loc/tr/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/shortcut_guide/loc/tr/src/modules/shortcut_guide/Resources.resx.lcl new file mode 100644 index 0000000000..9e8291e7cc --- /dev/null +++ b/src/modules/shortcut_guide/loc/tr/src/modules/shortcut_guide/Resources.resx.lcl @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/shortcut_guide/loc/zh-Hans/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/shortcut_guide/loc/zh-Hans/src/modules/shortcut_guide/Resources.resx.lcl new file mode 100644 index 0000000000..8c4853c91b --- /dev/null +++ b/src/modules/shortcut_guide/loc/zh-Hans/src/modules/shortcut_guide/Resources.resx.lcl @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/shortcut_guide/loc/zh-Hant/src/modules/shortcut_guide/Resources.resx.lcl b/src/modules/shortcut_guide/loc/zh-Hant/src/modules/shortcut_guide/Resources.resx.lcl new file mode 100644 index 0000000000..ab54354e69 --- /dev/null +++ b/src/modules/shortcut_guide/loc/zh-Hant/src/modules/shortcut_guide/Resources.resx.lcl @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/shortcut_guide/shortcut_guide.cpp b/src/modules/shortcut_guide/shortcut_guide.cpp index 60c9041f06..d51cfa0090 100644 --- a/src/modules/shortcut_guide/shortcut_guide.cpp +++ b/src/modules/shortcut_guide/shortcut_guide.cpp @@ -7,6 +7,7 @@ #include #include #include +#include extern "C" IMAGE_DOS_HEADER __ImageBase; @@ -94,14 +95,22 @@ namespace OverlayWindow::OverlayWindow() { app_name = GET_RESOURCE_STRING(IDS_SHORTCUT_GUIDE); + app_key = ShortcutGuideConstants::ModuleKey; init_settings(); } +// Return the localized display name of the powertoy const wchar_t* OverlayWindow::get_name() { return app_name.c_str(); } +// Return the non localized key of the powertoy, this will be cached by the runner +const wchar_t* OverlayWindow::get_key() +{ + return app_key.c_str(); +} + bool OverlayWindow::get_config(wchar_t* buffer, int* buffer_size) { HINSTANCE hinstance = reinterpret_cast(&__ImageBase); @@ -142,7 +151,7 @@ void OverlayWindow::set_config(const wchar_t* config) { // save configuration PowerToysSettings::PowerToyValues _values = - PowerToysSettings::PowerToyValues::from_json_string(config); + PowerToysSettings::PowerToyValues::from_json_string(config, get_key()); _values.save_to_settings_file(); Trace::SettingsChanged(pressTime.value, overlayOpacity.value, theme.value); @@ -331,7 +340,7 @@ void OverlayWindow::init_settings() try { PowerToysSettings::PowerToyValues settings = - PowerToysSettings::PowerToyValues::load_from_settings_file(OverlayWindow::get_name()); + PowerToysSettings::PowerToyValues::load_from_settings_file(OverlayWindow::get_key()); if (const auto val = settings.get_int_value(pressTime.name)) { pressTime.value = *val; diff --git a/src/modules/shortcut_guide/shortcut_guide.h b/src/modules/shortcut_guide/shortcut_guide.h index 1af0b06b51..b34c8353cc 100644 --- a/src/modules/shortcut_guide/shortcut_guide.h +++ b/src/modules/shortcut_guide/shortcut_guide.h @@ -17,6 +17,7 @@ public: OverlayWindow(); virtual const wchar_t* get_name() override; + virtual const wchar_t* get_key() override; virtual bool get_config(wchar_t* buffer, int* buffer_size) override; virtual void set_config(const wchar_t* config) override; @@ -37,6 +38,8 @@ public: private: std::wstring app_name; + //contains the non localized key of the powertoy + std::wstring app_key; std::unique_ptr target_state; std::unique_ptr winkey_popup; bool _enabled = false; diff --git a/src/modules/shortcut_guide/shortcut_guide.vcxproj b/src/modules/shortcut_guide/shortcut_guide.vcxproj index c606c3f689..39047b7830 100644 --- a/src/modules/shortcut_guide/shortcut_guide.vcxproj +++ b/src/modules/shortcut_guide/shortcut_guide.vcxproj @@ -109,6 +109,7 @@ + diff --git a/src/runner/centralized_kb_hook.cpp b/src/runner/centralized_kb_hook.cpp index a9c2409b63..f0945afc28 100644 --- a/src/runner/centralized_kb_hook.cpp +++ b/src/runner/centralized_kb_hook.cpp @@ -1,6 +1,7 @@ #include "pch.h" #include "centralized_kb_hook.h" -#include "common/common.h" +#include +#include namespace CentralizedKeyboardHook { diff --git a/src/runner/main.cpp b/src/runner/main.cpp index cbc2b69a85..90241bffad 100644 --- a/src/runner/main.cpp +++ b/src/runner/main.cpp @@ -136,7 +136,7 @@ int runner(bool isProcessElevated) try { auto module = load_powertoy(moduleSubdir); - modules().emplace(module->get_name(), std::move(module)); + modules().emplace(module->get_key(), std::move(module)); } catch (...) { diff --git a/src/runner/powertoy_module.cpp b/src/runner/powertoy_module.cpp index ef2c45c853..220911b954 100644 --- a/src/runner/powertoy_module.cpp +++ b/src/runner/powertoy_module.cpp @@ -49,7 +49,7 @@ PowertoyModule::PowertoyModule(PowertoyModuleIface* module, HMODULE handle) : void PowertoyModule::update_hotkeys() { - CentralizedKeyboardHook::ClearModuleHotkeys(module->get_name()); + CentralizedKeyboardHook::ClearModuleHotkeys(module->get_key()); size_t hotkeyCount = module->get_hotkeys(nullptr, 0); std::vector hotkeys(hotkeyCount); @@ -59,7 +59,7 @@ void PowertoyModule::update_hotkeys() for (size_t i = 0; i < hotkeyCount; i++) { - CentralizedKeyboardHook::SetHotkeyAction(module->get_name(), hotkeys[i], [modulePtr, i] { + CentralizedKeyboardHook::SetHotkeyAction(module->get_key(), hotkeys[i], [modulePtr, i] { return modulePtr->on_hotkey(i); }); } diff --git a/src/runner/settings_window.cpp b/src/runner/settings_window.cpp index 71fb26b205..a6d0786672 100644 --- a/src/runner/settings_window.cpp +++ b/src/runner/settings_window.cpp @@ -100,9 +100,9 @@ std::optional dispatch_json_action_to_module(const json::JsonObjec const auto element = powertoy_element.Value().Stringify(); modules().at(name)->call_custom_action(element.c_str()); } - - return result; } + + return result; } void send_json_config_to_module(const std::wstring& module_key, const std::wstring& settings) diff --git a/src/tests/win-app-driver/FancyZonesTests/EditorGridZoneResizeTests.cs b/src/tests/win-app-driver/FancyZonesTests/EditorGridZoneResizeTests.cs index bb0cffbd9b..5a39a28423 100644 --- a/src/tests/win-app-driver/FancyZonesTests/EditorGridZoneResizeTests.cs +++ b/src/tests/win-app-driver/FancyZonesTests/EditorGridZoneResizeTests.cs @@ -176,10 +176,10 @@ namespace PowerToysTests Assert.AreEqual(4, zones.Count); //check splitted zone - Assert.AreEqual(zones[0].Rect.Top, defaultSpacing); + Assert.AreEqual(defaultSpacing, zones[0].Rect.Top); Assert.IsTrue(Math.Abs(zones[0].Rect.Bottom - splitPos + defaultSpacing / 2) <= 2); Assert.IsTrue(Math.Abs(zones[1].Rect.Top - splitPos - defaultSpacing / 2) <= 2); - Assert.AreEqual(zones[1].Rect.Bottom, Screen.PrimaryScreen.Bounds.Bottom - defaultSpacing); + Assert.AreEqual(Screen.PrimaryScreen.Bounds.Bottom - defaultSpacing, zones[1].Rect.Bottom); } [TestMethod] @@ -201,10 +201,10 @@ namespace PowerToysTests zones = gridEditor.FindElementsByClassName("GridZone"); Assert.AreEqual(4, zones.Count); - Assert.AreEqual(zones[0].Rect.Top, defaultSpacing); + Assert.AreEqual(defaultSpacing, zones[0].Rect.Top); Assert.IsTrue(Math.Abs(zones[0].Rect.Bottom - firstSplitPos + defaultSpacing / 2) <= 2); Assert.IsTrue(Math.Abs(zones[1].Rect.Top - firstSplitPos - defaultSpacing / 2) <= 2); - Assert.AreEqual(zones[3].Rect.Bottom, Screen.PrimaryScreen.Bounds.Bottom - defaultSpacing); + Assert.AreEqual(Screen.PrimaryScreen.Bounds.Bottom - defaultSpacing, zones[3].Rect.Bottom); //create second split int secondSplitPos = zones[3].Rect.Y + zones[3].Rect.Height / 2; @@ -216,14 +216,14 @@ namespace PowerToysTests Assert.AreEqual(5, zones.Count); //check first split on same position - Assert.AreEqual(zones[0].Rect.Top, defaultSpacing); + Assert.AreEqual(defaultSpacing, zones[0].Rect.Top); Assert.IsTrue(Math.Abs(zones[0].Rect.Bottom - firstSplitPos + defaultSpacing / 2) <= 2); //check second split - Assert.AreEqual(zones[3].Rect.Top, expectedTop); + Assert.AreEqual(expectedTop, zones[3].Rect.Top); Assert.IsTrue(Math.Abs(zones[3].Rect.Bottom - secondSplitPos + defaultSpacing / 2) <= 2); Assert.IsTrue(Math.Abs(zones[4].Rect.Top - secondSplitPos - defaultSpacing / 2) <= 2); - Assert.AreEqual(zones[4].Rect.Bottom, Screen.PrimaryScreen.Bounds.Bottom - defaultSpacing); + Assert.AreEqual(Screen.PrimaryScreen.Bounds.Bottom - defaultSpacing, zones[4].Rect.Bottom); } [TestMethod] diff --git a/tools/build/convert-resx-to-rc.ps1 b/tools/build/convert-resx-to-rc.ps1 index 071b80907f..6a6fab526b 100644 --- a/tools/build/convert-resx-to-rc.ps1 +++ b/tools/build/convert-resx-to-rc.ps1 @@ -110,8 +110,8 @@ Foreach-Object { $content = $line -split "=", 2 $culture = [System.Globalization.CultureInfo]::GetCultureInfo('en-US') - # Each resource is named as IDS_ResxResourceName, in uppercase - $lineInRCFormat = "IDS_" + $content[0].ToUpper($culture) + " L`"" + $content[1] + "`"" + # Each resource is named as IDS_ResxResourceName, in uppercase. Escape occurrences of double quotes in the string + $lineInRCFormat = "IDS_" + $content[0].ToUpper($culture) + " L`"" + $content[1].Replace("`"", "`"`"") + "`"" $newLinesForRCFile = $newLinesForRCFile + "`r`n " + $lineInRCFormat # Resource header file needs to be updated only for one language diff --git a/tools/localization/move_uwp_resources.ps1 b/tools/localization/move_uwp_resources.ps1 new file mode 100644 index 0000000000..350f856fca --- /dev/null +++ b/tools/localization/move_uwp_resources.ps1 @@ -0,0 +1,63 @@ +# List of resource folders +$input_resource_folder_list = @("src\core\Microsoft.PowerToys.Settings.UI\Strings\") +$output_resource_folder_list = @("src\core\Microsoft.PowerToys.Settings.UI\Strings\") + +# Hash table to get the folder language code from the code used in the file name +$languageHashTable = @{ "en" = "en-us"; + "cs" = "cs-cz"; + "de" = "de-de"; + "es" = "es-es"; + "fr" = "fr-fr"; + "hu" = "hu-hu"; + "it" = "it-it"; + "ja" = "ja-jp"; + "ko" = "ko-kr"; + "nl" = "nl-nl"; + "pl" = "pl-pl"; + "pt-BR" = "pt-br"; + "pt-PT" = "pt-pt"; + "ru" = "ru-ru"; + "sv" = "sv-se"; + "tr" = "tr-tr"; + "zh-Hans" = "zh-cn"; + "zh-Hant" = "zh-tw" + } + +# Iterate over all folders +for ($i=0; $i -lt $input_resource_folder_list.length; $i++) { + Get-ChildItem $input_resource_folder_list[$i] -Filter Resources.*.resw | + Foreach-Object { + # Get language code from file name + $lang = "en" + $tokens = $_.Name -split "\." + if ($tokens.Count -eq 3) { + $lang = $tokens[1] + } + $langPath = $languageHashTable[$lang] + + # Skip for en-us as it already exists in correct folder + if ($lang -eq "en") { + continue + } + + # Create language folder if it doesn't exist + $output_path = $output_resource_folder_list[$i] + $langPath + if (!(Test-Path -Path $output_path)) + { + $paramNewItem = @{ + Path = $output_path + ItemType = 'Directory' + Force = $true + } + + New-Item @paramNewItem + } + + # UWP projects expect the file to be in the path Strings\langCode\Resources.resw where langCode is the hyphenated language code + $input_file = $input_resource_folder_list[$i] + $_.Name + $output_file = $output_path + "\" + "Resources.resw" + + Move-Item -Path $input_file -Destination $output_file + } +} + diff --git a/tools/project_template/ModuleTemplate/dllmain.cpp b/tools/project_template/ModuleTemplate/dllmain.cpp index 7d9c47286c..7d6db190f6 100644 --- a/tools/project_template/ModuleTemplate/dllmain.cpp +++ b/tools/project_template/ModuleTemplate/dllmain.cpp @@ -66,12 +66,18 @@ public: delete this; } - // Return the display name of the powertoy, this will be cached by the runner + // Return the localized display name of the powertoy virtual const wchar_t* get_name() override { return MODULE_NAME; } + // Return the non localized key of the powertoy, this will be cached by the runner + virtual const wchar_t* get_key() override + { + return MODULE_NAME; + } + // Return JSON with the configuration options. virtual bool get_config(wchar_t* buffer, int* buffer_size) override { @@ -158,7 +164,7 @@ public: { // Parse the input JSON string. PowerToysSettings::PowerToyValues values = - PowerToysSettings::PowerToyValues::from_json_string(config); + PowerToysSettings::PowerToyValues::from_json_string(config, get_key()); // Update a bool property. //if (auto v = values.get_bool_value(L"bool_toggle_1")) { @@ -218,7 +224,7 @@ void $safeprojectname$::init_settings() { // Load and parse the settings file for this PowerToy. PowerToysSettings::PowerToyValues settings = - PowerToysSettings::PowerToyValues::load_from_settings_file($safeprojectname$::get_name()); + PowerToysSettings::PowerToyValues::load_from_settings_file($safeprojectname$::get_key()); // Load a bool property. //if (auto v = settings.get_bool_value(L"bool_toggle_1")) {