diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 08087a4fb..36e0737e0 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -39,6 +39,7 @@ body: - Image Resizer - Keyboard Manager - MD Preview + - Mouse Utilities - PDF Preview - PDF Thumbnail - PowerRename @@ -47,6 +48,7 @@ body: - SVG Preview - SVG Thumbnail - Settings + - Video Conference Mute - Welcome / PowerToys Tour window - System tray interaction - Installer diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index d362237c6..28fc2643c 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,5 @@ -blank_issues_enabled: true +blank_issues_enabled: false contact_links: - - name: "\U0001F4F7 Video Conference Mute Issue" - url: https://github.com/microsoft/PowerToys/issues/6246 - about: Report Bug for the Video Conference Mute utility - name: "\U0001F6A8 Microsoft Security Response Center (MSRC)" url: https://msrc.microsoft.com/create-report about: Report security bugs @@ -10,6 +7,6 @@ contact_links: url: https://github.com/microsoft/PowerToys/wiki about: Documentation for users of PowerToys utilities - name: "\U0001F4DA PowerToys dev documentation" - url: https://github.com/microsoft/PowerToys/tree/master/doc/devdocs + url: https://github.com/microsoft/PowerToys/tree/main/doc/devdocs about: Documentation for people interested in developing and contributing for PowerToys diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 09f024255..883ff3807 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -631,6 +631,7 @@ Farbraum FARPROC fdw feimage +FFAA ffcd FFDDDDDD fff @@ -698,6 +699,7 @@ gdi gdiplus GDISCALED generatesqlfromuserquery +GETDESKWALLPAPER GETDISPINFO GETDLGCODE GETDPISCALEDSIZE @@ -732,6 +734,7 @@ Hardlines HARDWAREINPUT hashcode Hashset +Hashtable HASHVAL hbitmap hbmp @@ -760,8 +763,10 @@ hglobal hhk HHmmss HHOOK +hhx HICON HIDEWINDOW +highlighter HIMAGELIST himl hinst @@ -785,6 +790,7 @@ hmonitor HOLDENTER HOLDESC homljgmgpmcbpjbnjpfijnhipfkiclkd +homepage HOOKPROC hostname hotkeycontrol @@ -1659,6 +1665,7 @@ prgms pri PRINTCLIENT printf +pritudev prm proactively PROCESSKEY @@ -2079,6 +2086,7 @@ Switchbetweenvirtualdesktops SWP swprintf SWRESTORE +swscanf SYMED SYMOPT SYNCMFT @@ -2330,7 +2338,7 @@ VTABLE Vtbl wav WBounds -Wca +wca wcautil WCE wcex @@ -2435,6 +2443,7 @@ workaround Workflow workspaces wostream +wostringstream wox wparam wpf diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b948638ca..020ad0c4f 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -16,8 +16,8 @@ - [ ] **Docs:** Added/ updated - [ ] **Binaries:** Any new files are added to WXS / YML - [ ] No new binaries - - [ ] [YML for signing](https://github.com/microsoft/PowerToys/blob/master/.pipelines/pipeline.user.windows.yml#L68) for new binaries - - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/master/installer/PowerToysSetup/Product.wxs) for new binaries + - [ ] [YML for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/pipeline.user.windows.yml#L68) for new binaries + - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries ## Contributor License Agreement (CLA) A CLA must be signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/PowerToys) and sign the CLA. diff --git a/.pipelines/ci/ci.yml b/.pipelines/ci/ci.yml index ad8a707a2..fd7b18829 100644 --- a/.pipelines/ci/ci.yml +++ b/.pipelines/ci/ci.yml @@ -2,7 +2,7 @@ trigger: batch: true branches: include: - - master + - main - stable paths: exclude: @@ -13,7 +13,7 @@ trigger: pr: branches: include: - - master + - main - stable # 0.0.yyMM.dd## diff --git a/.pipelines/pipeline.user.windows.yml b/.pipelines/pipeline.user.windows.yml index 22f6cf723..b727524f2 100644 --- a/.pipelines/pipeline.user.windows.yml +++ b/.pipelines/pipeline.user.windows.yml @@ -171,6 +171,7 @@ build: - 'modules\launcher\Wox.Infrastructure.dll' - 'modules\launcher\Wox.Plugin.dll' - 'modules\MouseUtils\FindMyMouse.dll' + - 'modules\MouseUtils\MouseHighlighter.dll' - 'modules\PowerRename\PowerRenameExt.dll' - 'modules\PowerRename\PowerRenameUILib.dll' - 'modules\PowerRename\PowerRename.exe' diff --git a/.pipelines/restore-dependencies.ps1 b/.pipelines/restore-dependencies.ps1 index 4e30a5509..22c25a1a8 100644 --- a/.pipelines/restore-dependencies.ps1 +++ b/.pipelines/restore-dependencies.ps1 @@ -1,6 +1,6 @@ # not using this but keeping around in case we need it in the future. # good use case here could be to set up a new machine, we just point people at it. -# https://github.com/microsoft/PowerToys/tree/master/doc/devdocs#prerequisites-for-compiling-powertoys +# https://github.com/microsoft/PowerToys/tree/main/doc/devdocs#prerequisites-for-compiling-powertoys # improvements if this script is used to replace the snippet # Add in a param for passive versus quiet. Could be a IsSettingUpDevComputer true/false flag diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0dc0cc42a..6c22522bb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,7 +49,7 @@ Once the team has approved an issue/spec approach to solving, development can pr ## Development -Follow the [development guidelines](https://github.com/microsoft/PowerToys/blob/master/doc/devdocs/readme.md). +Follow the [development guidelines](https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/readme.md). ### Naming of features and functionality diff --git a/PowerToys.sln b/PowerToys.sln index e185f09f7..43ebe00f8 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -262,6 +262,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643-4663-475E-B329-03F0C9918D48}" ProjectSection(SolutionItems) = preProject src\common\utils\appMutex.h = src\common\utils\appMutex.h + src\common\utils\color.h = src\common\utils\color.h src\common\utils\com_object_factory.h = src\common\utils\com_object_factory.h src\common\utils\elevation.h = src\common\utils\elevation.h src\common\utils\EventLocker.h = src\common\utils\EventLocker.h @@ -371,6 +372,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MouseUtils", "MouseUtils", EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FindMyMouse", "src\modules\MouseUtils\FindMyMouse\FindMyMouse.vcxproj", "{E94FD11C-0591-456F-899F-EFC0CA548336}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MouseHighlighter", "src\modules\MouseUtils\MouseHighlighter\MouseHighlighter.vcxproj", "{782A61BE-9D85-4081-B35C-1CCC9DCC1E88}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -986,6 +989,12 @@ Global {E94FD11C-0591-456F-899F-EFC0CA548336}.Release|x64.ActiveCfg = Release|x64 {E94FD11C-0591-456F-899F-EFC0CA548336}.Release|x64.Build.0 = Release|x64 {E94FD11C-0591-456F-899F-EFC0CA548336}.Release|x86.ActiveCfg = Release|x64 + {782A61BE-9D85-4081-B35C-1CCC9DCC1E88}.Debug|x64.ActiveCfg = Debug|x64 + {782A61BE-9D85-4081-B35C-1CCC9DCC1E88}.Debug|x64.Build.0 = Debug|x64 + {782A61BE-9D85-4081-B35C-1CCC9DCC1E88}.Debug|x86.ActiveCfg = Debug|x64 + {782A61BE-9D85-4081-B35C-1CCC9DCC1E88}.Release|x64.ActiveCfg = Release|x64 + {782A61BE-9D85-4081-B35C-1CCC9DCC1E88}.Release|x64.Build.0 = Release|x64 + {782A61BE-9D85-4081-B35C-1CCC9DCC1E88}.Release|x86.ActiveCfg = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1105,6 +1114,7 @@ Global {4642D596-723F-4BFC-894C-46811219AC4A} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} {322566EF-20DC-43A6-B9F8-616AF942579A} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {E94FD11C-0591-456F-899F-EFC0CA548336} = {322566EF-20DC-43A6-B9F8-616AF942579A} + {782A61BE-9D85-4081-B35C-1CCC9DCC1E88} = {322566EF-20DC-43A6-B9F8-616AF942579A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/README.md b/README.md index 1f4708bc0..2f1333a9e 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ ## Build status -| Architecture | Master | Stable | Installer | -|--------------|--------|--------|-----------| -| x64 | [![Build Status for Master](https://dev.azure.com/ms/PowerToys/_apis/build/status/microsoft.PowerToys?branchName=master)](https://dev.azure.com/ms/PowerToys/_build/latest?definitionId=219&branchName=master) | [![Build Status for Stable](https://dev.azure.com/ms/PowerToys/_apis/build/status/microsoft.PowerToys?branchName=stable)](https://dev.azure.com/ms/PowerToys/_build/latest?definitionId=219&branchName=stable) | [![Build Status for Installer](https://github-private.visualstudio.com/microsoft/_apis/build/status/CDPX/powertoys/powertoys-Windows-Official-master-Test?branchName=master)](https://github-private.visualstudio.com/microsoft/_build/latest?definitionId=61&branchName=master) | +| Architecture | Main | Stable | Installer | +|--------------|------|--------|-----------| +| x64 | [![Build Status for Main](https://dev.azure.com/ms/PowerToys/_apis/build/status/microsoft.PowerToys?branchName=main)](https://dev.azure.com/ms/PowerToys/_build/latest?definitionId=219&branchName=main) | [![Build Status for Stable](https://dev.azure.com/ms/PowerToys/_apis/build/status/microsoft.PowerToys?branchName=stable)](https://dev.azure.com/ms/PowerToys/_build/latest?definitionId=219&branchName=stable) | [![Build Status for Installer](https://github-private.visualstudio.com/microsoft/_apis/build/status/CDPX/powertoys/powertoys-Windows-Official-master-Test?branchName=main)](https://github-private.visualstudio.com/microsoft/_build/latest?definitionId=61&branchName=main) | ## About @@ -16,32 +16,29 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline | | Current utilities: | | |--------------|--------------------|--------------| -| [Awake](https://aka.ms/PowerToysOverview_Awake) | [Color Picker](https://aka.ms/PowerToysOverview_ColorPicker) | [FancyZones](https://aka.ms/PowerToysOverview_FancyZones) | -| [File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) | [Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | -| [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | [Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) | -| [Video Conference Mute (Experimental)](https://aka.ms/PowerToysOverview_VideoConference) | [Mouse utilities](https://aka.ms/PowerToysOverview_MouseUtilities) | | +| [PowerToys Awake](https://aka.ms/PowerToysOverview_Awake) | [Color Picker](https://aka.ms/PowerToysOverview_ColorPicker) | [FancyZones](https://aka.ms/PowerToysOverview_FancyZones) | +| [File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) | [Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | +| [Mouse utilities](https://aka.ms/PowerToysOverview_MouseUtilities) | [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | +| [Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) | [Video Conference Mute](https://aka.ms/PowerToysOverview_VideoConference) | | ## Installing and running Microsoft PowerToys ### Requirements -- ⚠️ PowerToys (v0.37.0 and newer) requires Windows 10 v1903 (18362) or newer. - -- Have [.NET Core 3.1.15 Desktop Runtime](https://dotnet.microsoft.com/download/dotnet/thank-you/runtime-desktop-3.1.15-windows-x64-installer). The installer should handle this but we want to directly make people aware. +- Windows 11 or Windows 10 v1903 (18362) or newer. +- [.NET Core 3.1.20 Desktop Runtime](https://dotnet.microsoft.com/download/dotnet/thank-you/runtime-desktop-3.1.20-windows-x64-installer) or a newer 3.1.x runtime. The installer will handle this if not present. ### Via GitHub with EXE [Recommended] #### Stable version -Install from the [Microsoft Store's PowerToys page][microsoft-store-link] or use [Microsoft PowerToys GitHub releases page][github-release-link]. - -- For GitHub, click on `Assets` to show the files available in the release and then click on `PowerToysSetup-0.47.1-x64.exe` to download the PowerToys installer. -- For Microsoft Store, you must be using the [new Microsoft Store](https://blogs.windows.com/windowsExperience/2021/06/24/building-a-new-open-microsoft-store-on-windows-11/) which will be available for both Windows 11 and Windows 10. + [Microsoft PowerToys GitHub releases page][github-release-link], click on `Assets` at the bottom to show the files available in the release and then click on `PowerToysSetup-0.49.0-x64.exe` to download the PowerToys installer. This is our preferred method. -#### Experimental version -To install the Video Conference mute, please use the [v0.46 experimental version of PowerToys][github-prerelease-link] to try out this version. It includes all improvements from v0.45 in addition to the Video conference utility. Click on `Assets` to show the files available in the release and then download the .exe installer. +### Via Microsoft Store + +Install from the [Microsoft Store's PowerToys page][microsoft-store-link]. You must be using the [new Microsoft Store](https://blogs.windows.com/windowsExperience/2021/06/24/building-a-new-open-microsoft-store-on-windows-11/) which will be available for both Windows 11 and Windows 10. ### Via WinGet (Preview) Download PowerToys from [WinGet](https://github.com/microsoft/winget-cli#installing-the-client). To install PowerToys, run the following command from the command line / PowerShell: @@ -78,97 +75,74 @@ For guidance on developing for PowerToys, please read the [developer docs](/doc/ Our [prioritized roadmap][roadmap] of features and utilities that the core team is focusing on. -### 0.47 - September 2021 Update +### 0.49 - October 2021 Update -Our goals for the [v0.47 release cycle](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen+is%3Aissue+project%3Amicrosoft%2FPowerToys%2F24) primarily centered around stability updates and optimizations, installer updates, general bug fixes, and accessibility improvements. +The [v0.49 release cycle](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen+is%3Aissue+project%3Amicrosoft%2FPowerToys%2F25) introduces exciting new updates primarily centered around modernizing PowerRename's UI, adding a brand new mouse utility, and merging Video Conference Mute into the stable releases! -Notably, based on the community feedback received, PowerToys has re-introduced the highly-requested ability to activate Shortcut Guide via holding the Win key. PowerToys also now allows various commands in PowerToys Run to be used in either the universal English phrasing or system-localized translation. The great feedback the community provides is invaluable in helping PowerToys continually grow and improve as a product. +PowerRename's new UI brings a refreshed experience that reflects the modern UI theming of Windows 11, along with helpful regular expression guidance and file formatting tips. -An experimental version of PowerToys ([v0.48.1](https://github.com/microsoft/PowerToys/releases/tag/v0.48.1)) is also available, introducing improvements to our Video Conference Mute utility! All updates from the v0.47.1 release apply in v0.48.1. +With the new mouse utility, PowerToys introduces functionality to quickly find your mouse position by double pressing the left ctrl key. This is ideal for large, high-resolution displays and low-vision users, with additional features and enhancements planned for future releases. Special thanks to [Raymond Chen](https://github.com/oldnewthing) for providing the base code PowerToys used to develop this feature. To learn more, check out our [Mouse Utilities documentation](https://aka.ms/PowerToysOverview_MouseUtilities) on Microsoft Docs! -#### Highlights from v0.47 +As Video Conference Mute becomes available in the stable releases, there are still known quirks that we are actively working to address. These bugs are [tracked on our GitHub](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen+is%3Aissue+label%3A%22Product-Video+Conference+Mute%22), and we welcome any and all feedback as we work to isolate and resolve the cause. + +Color Picker's HEX format will no longer have the `#` character. This addresses issues with various color inputs that only accept six characters cutting off the last value. We apologize for any inconvenience this causes as we understand it impacts users who may prefer having `#` included. However, we believe this is the best solution while the custom string functionality ([#8305](https://github.com/microsoft/PowerToys/issues/8305)) is in development. + +Additional work in this release include stability updates and optimizations, installer updates, general bug fixes, and accessibility improvements. + +#### Highlights from v0.49 **General** +- **Change of Behavior:** Color Picker's HEX format will no longer have the `#` character. Custom string functionality for color picker ([#8305](https://github.com/microsoft/PowerToys/issues/8305)) is in development and will allow someone to use this again. +- Find My Mouse utility added! Utilize the functionality to quickly locate the cursor on your displays! Learn more on our [Mouse Utility docs](https://aka.ms/PowerToysOverview_MouseUtilities). +- Accessibility and minor UI improvements to the settings page. Thanks [@niels9001](https://github.com/niels9001)! +- Added deep links to the Settings menus for various utilities within their respective editors. Thanks [@niels9001](https://github.com/niels9001)! +- Settings improvements to improve clarity for various options. Thanks [@niels9001](https://github.com/niels9001)! +- Improved settings window to adjust size and position as needed when multi-monitor conditions change. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! -- Fixed issue with new updates changing the PowerToys install location. -- Fixed settings with NumberBox elements overlapping the delete button. -- Fixed issue with the bug report tool not generating .zip files. -- Updated the shortcut configuration experience in Settings. Thanks @niels9001! -- Fixed inconsistent width of sidebar icons. Thanks @niels9001! -- Fixed sidebar UI not scaling for longer text strings in certain localizations. Thanks @niels9001! -- Fixed issue with settings not displaying invalid keystroke assignments. Thanks @niels9001! -- Added user defined shortcuts when set to the "Welcome to PowerToys" instead of the default shortcuts. - +**PowerToys Awake** +- Screen reader improvements for accessibility. -### Color Picker +**Color Picker** +- Color Picker's HEX format was changed to remove the `#` character. Thanks [@niels9001](https://github.com/niels9001)! +- Accessibility improvements for screen reader and UI to distinguish colors from the border when matching. Thanks [@niels9001](https://github.com/niels9001)! -- Accessibility issues addressed. Thanks @niels9001! -- Added CIELAB and CIEXYZ color formats. Thanks @RubenFricke! -- Fixed bug where changing RGB values manually doesn't automatically update the color displayed. Thanks @martinchrzan! +**FancyZones** +- Fixed Color Picker and OOBE windows from being snapped by FancyZones. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! +- Fixed regression with layouts not being changed via shortcuts. +- Fixed crashing issue with FancyZones editor. +- Fixed zone layouts resetting after screen locking. +- Accessibility improvements for screen reader in editor. -### FancyZones +**Keyboard Manager** +- Fixed crashing issue when the editor is opened at high zoom on 4k monitors. -- Fixed regression where restarting computer resets user defined layouts to the default selection. -- Fixed issues with Grid layout editor not showing the "Save" and "Cancel" buttons. -- Fixed accessibility issue where users could not add or merge zones using the keyboard. -- Added a flyout describe the prerequisites for the "Allow zones to span across monitors" option. -- Fixed various crashing bugs. +**PowerRename** +- New UI update! We hope you enjoy the modern experience and take advantage of new tool-tips to describe common regular expressions and text/file formatting. Thanks to [@niels9001](https://github.com/niels9001) for all the support on this redesign! -### File Explorer add-ons +**PowerToys Run** +- Windows Terminal Plugin added. Open shells through Windows Terminal via `_` activation command by default. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! +- Added environment variables to Folder plugin search. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! +- Fixed certain schemas that were overwritten with HTTPS. Thanks [@franky920920](https://github.com/franky920920)! +- Fixed issue with program plugin getting caught in infinite loops as certain file paths are recursively searched. -- Added PDF preview and thumbnail provider for Windows Explorer. Thanks @rdeveen! - -### Image Resizer - -- Added default values for newly added sizes. Thanks @htcfreek! -- Fixed regression where spaces in the filename format settings couldn't be registered. -- Corrected scaling issues with Image Resizer Window. Thanks @niels9001! -- Fixed issue where PowerToys crashes when json settings are not formatted properly. Thanks @davidegiacometti! - -### Keyboard Manager - -- Fixed crash when adding a shortcut. -- Fixed issue with Re-mappings window not displaying. -- Fixed issue when remapping a shortcut to Alt+Tab breaks the Alt+Tab navigation with arrow keys. - -### PowerToys Run - -- Improvements on subtitle layout for Settings plugin. Thanks @htcfreek! -- Added path filters for Settings plugin via `>` character. Thanks @htcfreek! -- Translation improvements for Settings plugin. Thanks @htcfreek! -- Enabled translation for Settings Plugin. Thanks @htcfreek! -- Fixed issue with PowerToys Run not being in focus when launched. -- Fixed crash on empty/deleted environment variables when updating variables after a change. Thanks @htcfreek! -- Corrected Registry Plugin query results. -- Fixed crash in Registry plugin queries. -- Fixed crash when Windows shuts down. -- Added better description in the global results settings for plugins. Thanks @niels9001! -- Added a confirmation box before running system commands. Thanks @chrisharris333 and @davidegiacometti! -- Added option to use system localization our universal terminology for system commands. Thanks @davidegiacometti! - -### Shortcut Guide - -- Re-added the long Win key press to activate utility. - -### Video Conference Mute - -- Fixed an issue with the first hotkey input in the settings being focused when the page loads. Prevents unintentionally shortcut reassignment. Thanks @niels9001! +**Video Conference Mute** +- VCM added to stable releases of PowerToys! ## Community contributions We'd like to directly mention certain contributors (in alphabetical order) for their continued community support this month and helping directly make PowerToys a better piece of software. -[@Aaron-Junker](https://github.com/Aaron-Junker), [@chrisharris333](https://github.com/chrisharris333), [@davidegiacometti](https://github.com/davidegiacometti), [@dend](https://github.com/dend), [@franky920920](https://github.com/franky920920), [@htcfreek](https://github.com/htcfreek), [@Jay-o-Way](https://github.com/Jay-o-Way), [@jsoref](https://github.com/jsoref), [@martinchrzan](https://github.com/martinchrzan), [@niels9001](https://github.com/niels9001), [@rdeveen](https://github.com/rdeveen) and [@RubenFricke](https://github.com/RubenFricke) +[@Aaron-Junker](https://github.com/Aaron-Junker), [@davidegiacometti](https://github.com/davidegiacometti), [@franky920920](https://github.com/franky920920), [@htcfreek](https://github.com/htcfreek), [@Jay-o-Way](https://github.com/Jay-o-Way), [@martinchrzan](https://github.com/martinchrzan), [@niels9001](https://github.com/niels9001), [@pritudev](https://github.com/pritudev), and [@TobiasSekan](https://github.com/TobiasSekan) -#### What is being planned for v0.49 +#### What is being planned for v0.51 -For [v0.49][github-next-release-work], we are planning to work on: +For [v0.51][github-next-release-work], we are planning to work on: -- Execution on new utilities and enhancements +- Initial development of Always on Top utility to allow users to persist desired windows in the foreground of their displays +- We are working to heavily reduce / remove the UAC prompt over the next few releases on install. This is a big shift so it is spanning multiple releases so we can isolate issues if they do occur. Work is tracked in [#10126](https://github.com/microsoft/PowerToys/issues/10126) - UI/UX investigations to adopt WinUI and improve accessibility - Stability and bug fixes -- Upgrading PowerToys Run to .NET 5 - +- Update the PowerToys Build Pipeline to allow .NET 5 integration ## PowerToys Community @@ -194,5 +168,4 @@ The application logs basic telemetry. Our Telemetry Data page (Coming Soon) has [usingPowerToys-docs-link]: https://aka.ms/powertoys-docs -[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aopen+is%3Aissue+project%3Amicrosoft%2FPowerToys%2F25 -[github-prerelease-link]: https://github.com/microsoft/PowerToys/releases/tag/v0.46.0 +[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=is%3Aopen+is%3Aissue+project%3Amicrosoft%2FPowerToys%2F26 diff --git a/SUPPORT.md b/SUPPORT.md index 92b0f54aa..da76b0ac0 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -20,5 +20,5 @@ Support for PowerToys is limited to the resources listed above. [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 +[contributor]: https://github.com/microsoft/PowerToys/blob/main/CONTRIBUTING.md [usingPowerToys-docs-link]: https://aka.ms/powertoys-docs diff --git a/doc/devdocs/akaLinks.md b/doc/devdocs/akaLinks.md index 064035fd1..8dc69c338 100644 --- a/doc/devdocs/akaLinks.md +++ b/doc/devdocs/akaLinks.md @@ -4,21 +4,21 @@ |----------|----------| | getpowertoys | ms-windows-store://pdp/?productid=XP89DCGQ3K6VLD | | installpowertoys | https://github.com/microsoft/PowerToys/releases/latest | -| powertoys-license | https://github.com/microsoft/PowerToys/blob/master/LICENSE | +| powertoys-license | https://github.com/microsoft/PowerToys/blob/main/LICENSE | | powertoys | https://github.com/microsoft/PowerToys | | PowerToysAppCompat | https://github.com/microsoft/PowerToys/wiki/Application-Compatibility | | powerToysCannotRemapKeys | https://docs.microsoft.com/windows/powertoys/keyboard-manager#keys-that-cannot-be-remapped | | powerToysColorPickerImageSmall | https://github.com/microsoft/PowerToys/wiki/images/overview/ColorPicker_small.png | -| powerToysColorPickerSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/master/doc/images/overview/ColorPicker_large.png | +| powerToysColorPickerSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/main/doc/images/overview/ColorPicker_large.png | | powertoysDetectedElevatedHelp | https://docs.microsoft.com/windows/powertoys/administrator | | powertoys-docs | https://docs.microsoft.com/windows/powertoys/?WT.mc_id=twitter-0000-docsmsft | | powerToysFancyZoneImageSmall | https://github.com/microsoft/PowerToys/wiki/images/overview/FancyZones_small.png | -| powerToysFancyZoneSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/master/doc/images/overview/FancyZones_large.png | +| powerToysFancyZoneSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/main/doc/images/overview/FancyZones_large.png | | powerToysGiveFeedback | https://github.com/microsoft/PowerToys/issues | | powerToysImageResizerImageSmall | https://github.com/microsoft/PowerToys/wiki/images/overview/ImageResizer_small.png | -| powerToysImageResizerSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/master/doc/images/overview/ImageResizer_large.png | +| powerToysImageResizerSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/main/doc/images/overview/ImageResizer_large.png | | powerToysKBMImageSmall | https://github.com/microsoft/PowerToys/wiki/images/overview/KBM_small.png | -| powerToysKBMSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/master/doc/images/overview/KBM_large.png | +| powerToysKBMSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/main/doc/images/overview/KBM_large.png | | PowerToysOverview | https://docs.microsoft.com/windows/powertoys/ | | PowerToysOverview_ColorPicker | https://docs.microsoft.com/windows/powertoys/color-picker | | PowerToysOverview_FancyZones | https://docs.microsoft.com/windows/powertoys/fancyzones | @@ -31,17 +31,17 @@ | PowerToysOverview_ShortcutGuide | https://docs.microsoft.com/windows/powertoys/shortcut-guide | | PowerToysOverview_VideoConference | https://docs.microsoft.com/windows/powertoys/video-conference-mute | | powerToysPowerLauncherImageSmall | https://github.com/microsoft/PowerToys/wiki/images/overview/PowerLauncher_small.png | -| powerToysPowerLauncherSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/master/doc/images/overview/PowerLauncher_large.png | +| powerToysPowerLauncherSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/main/doc/images/overview/PowerLauncher_large.png | | powerToysPowerPreviewImageSmall | https://github.com/microsoft/PowerToys/wiki/images/overview/PowerPreview_small.png | -| powerToysPowerPreviewSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/master/doc/images/overview/PowerPreview_large.png | +| powerToysPowerPreviewSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/main/doc/images/overview/PowerPreview_large.png | | powerToysPowerRenameImageSmall | https://github.com/microsoft/PowerToys/wiki/images/overview/PowerRename_small.png | -| powerToysPowerRenameSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/master/doc/images/overview/PowerRename_large.png | +| powerToysPowerRenameSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/main/doc/images/overview/PowerRename_large.png | | powerToysPTImageSmall | https://github.com/microsoft/PowerToys/wiki/images/overview/PT_small.png | -| powerToysPTSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/master/doc/images/overview/PT_large.png | +| powerToysPTSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/main/doc/images/overview/PT_large.png | | powerToysReportBug | https://github.com/microsoft/PowerToys/issues/new?assignees=&labels=Issue-Bug%2CTriage-Needed&template=bug_report.yml&title= | | powerToysRequestFeature | https://github.com/microsoft/PowerToys/issues/new?assignees=&labels=&template=feature_request.md&title= | | powerToysShortcutGuideImageSmall | https://github.com/microsoft/PowerToys/wiki/images/overview/ShortcutGuide_small.png | -| powerToysShortcutGuideSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/master/doc/images/overview/ShortcutGuide_large.png | +| powerToysShortcutGuideSettingImage | https://raw.githubusercontent.com/microsoft/PowerToys/main/doc/images/overview/ShortcutGuide_large.png | | powerToysVideoConferenceImageSmall | https://github.com/microsoft/PowerToys/wiki/images/overview/VideoConference_small.png | | powerToysVideoConferenceSettingImage | https://github.com/microsoft/PowerToys/wiki/images/overview/VideoConference_large.png | | powertoyswiki | https://github.com/microsoft/PowerToys/wiki | diff --git a/doc/devdocs/localization.md b/doc/devdocs/localization.md index 2b33e4ae7..3b90c05f0 100644 --- a/doc/devdocs/localization.md +++ b/doc/devdocs/localization.md @@ -12,11 +12,11 @@ 5. [Enabling localized MSI for a new project](#enabling-localized-msi-for-a-new-project) ## Localization on the pipeline (CDPX) -[The localization step](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/pipeline.user.windows.yml#L45-L52) is run on the pipeline before the solution is built. This step runs the [build-localization](https://github.com/microsoft/PowerToys/blob/master/.pipelines/build-localization.cmd) script, which generates resx files for all the projects with localization enabled using the `Localization.XLoc` package. +[The localization step](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/pipeline.user.windows.yml#L45-L52) is run on the pipeline before the solution is built. This step runs the [build-localization](https://github.com/microsoft/PowerToys/blob/main/.pipelines/build-localization.cmd) script, which generates resx files for all the projects with localization enabled using the `Localization.XLoc` package. -The [`Localization.XLoc`](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/build-localization.cmd#L24-L25) tool is run on the repo root, and it checks for all occurrences of `LocProject.json`. Each localized project has a `LocProject.json` file in the project root, which contains the location of the English resx file, list of languages for localization, and the output path where the localized resx files are to be copied to. In addition to this, some other parameters can be set, such as whether the language ID should be added as a folder in the file path or in the file name. When the CDPX pipeline is run, the localization team is notified of changes in the English resx files. For each project with localization enabled, a `loc` folder (see [this](https://github.com/microsoft/PowerToys/tree/master/src/modules/launcher/Microsoft.Launcher/loc) for example) is created in the same directory as the `LocProject.json` file. The folder contains language specific folders which in turn have a nested folder path equivalent to `OutputPath` in the `LocProject.json`. Each of these folders contain one `lcl` file. The `lcl` files contain the English resources along with their translation for that language. These are described in more detail [here](#lcl-files). Once the `.resx` files are generated, they will be used during the `Build PowerToys` step for localized versions of the modules. +The [`Localization.XLoc`](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/build-localization.cmd#L24-L25) tool is run on the repo root, and it checks for all occurrences of `LocProject.json`. Each localized project has a `LocProject.json` file in the project root, which contains the location of the English resx file, list of languages for localization, and the output path where the localized resx files are to be copied to. In addition to this, some other parameters can be set, such as whether the language ID should be added as a folder in the file path or in the file name. When the CDPX pipeline is run, the localization team is notified of changes in the English resx files. For each project with localization enabled, a `loc` folder (see [this](https://github.com/microsoft/PowerToys/tree/main/src/modules/launcher/Microsoft.Launcher/loc) for example) is created in the same directory as the `LocProject.json` file. The folder contains language specific folders which in turn have a nested folder path equivalent to `OutputPath` in the `LocProject.json`. Each of these folders contain one `lcl` file. The `lcl` files contain the English resources along with their translation for that language. These are described in more detail [here](#lcl-files). Once the `.resx` files are generated, they will be used during the `Build PowerToys` step for localized versions of the modules. -Since the localization script requires certain nuget packages, the [`restore-localization`](https://github.com/microsoft/PowerToys/blob/master/.pipelines/restore-localization.cmd) script is run before running `build-localization` to install all the required packages. This script must [run in the `restore` step](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/pipeline.user.windows.yml#L37-L39) of pipeline because [the host is network isolated](https://onebranch.visualstudio.com/Pipeline/_wiki/wikis/Pipeline.wiki/2066/Consuming-Packages-in-a-CDPx-Pipelinhttps://onebranch.visualstudio.com/Pipeline/_wiki/wikis/Pipeline.wiki/2066/Consuming-Packages-in-a-CDPx-Pipeline?anchor=overview) at the `build` step. The [Toolset package source](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/pipeline.user.windows.yml#L23) is used for this. +Since the localization script requires certain nuget packages, the [`restore-localization`](https://github.com/microsoft/PowerToys/blob/main/.pipelines/restore-localization.cmd) script is run before running `build-localization` to install all the required packages. This script must [run in the `restore` step](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/pipeline.user.windows.yml#L37-L39) of pipeline because [the host is network isolated](https://onebranch.visualstudio.com/Pipeline/_wiki/wikis/Pipeline.wiki/2066/Consuming-Packages-in-a-CDPx-Pipelinhttps://onebranch.visualstudio.com/Pipeline/_wiki/wikis/Pipeline.wiki/2066/Consuming-Packages-in-a-CDPx-Pipeline?anchor=overview) at the `build` step. The [Toolset package source](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/pipeline.user.windows.yml#L23) is used for this. The process and variables that can be tweaked on the pipeline are described in more detail [here](https://onebranch.visualstudio.com/Pipeline/_wiki/wikis/Pipeline.wiki/290/Localization). @@ -31,7 +31,7 @@ UWP differs from this as it expects the resources to have the same Resources.res For example, `path\en-us\Resources.resw` for English and `path\fr-fr\Resources.resw` for French. -Since the pipeline generates it in this format, [a script is run](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/build-localization.cmd#L29-L31) to move these resw files to the correct format expected by all UWP projects. Currently the only UWP project is [Microsoft.PowerToys.Settings.UI](https://github.com/microsoft/PowerToys/tree/master/src/core/Microsoft.PowerToys.Settings.UI). The script used for moving the resources can be [found here](https://github.com/microsoft/PowerToys/blob/master/tools/localization/move_uwp_resources.ps1). The equivalent full language IDs for each shortened language ID obtained from the pipeline has been hardcoded in the script. +Since the pipeline generates it in this format, [a script is run](https://github.com/microsoft/PowerToys/blob/86d77103e9c69686c297490acb04775d43ef8b76/.pipelines/build-localization.cmd#L29-L31) to move these resw files to the correct format expected by all UWP projects. Currently the only UWP project is [Microsoft.PowerToys.Settings.UI](https://github.com/microsoft/PowerToys/tree/main/src/core/Microsoft.PowerToys.Settings.UI). The script used for moving the resources can be [found here](https://github.com/microsoft/PowerToys/blob/main/tools/localization/move_uwp_resources.ps1). The equivalent full language IDs for each shortened language ID obtained from the pipeline has been hardcoded in the script. ## Enabling localization on a new project To enable localization on a new project, the first step is to create a file `LocProject.json` in the project root. @@ -58,7 +58,7 @@ The rest of the steps depend on the project type and are covered in the sections ### C++ C++ projects do not support `resx` files, and instead use `rc` files along with `resource.h` files. The CDPX pipeline however doesn't support localizing `rc` files and the other alternative they support is directly translating the resources from the binary which makes it harder to maintain resources. To avoid this, a custom script has been added which expects a resx file and converts the entries to an rc file with a string table and adds resource declarations to a resource.h file so that the resources can be compiled with the C++ project. -If you already have a .rc file, copy the string table to a separate txt file and run the [convert-stringtable-to-resx.ps1](https://github.com/microsoft/PowerToys/blob/master/tools/build/convert-stringtable-to-resx.ps1) script on it. This script is not very robust to input, and requires the data in a specific format, where `IDS_ResName L"ResourceValue"` and any number of spaces can be present in between. The script converts this file to the format expected by [`resgen`](https://docs.microsoft.com/en-us/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert), which will convert it to resx. The resource names are changed from all uppercase to title case, and the `IDS_` prefix is removed. Escape characters might have to be manually replaced, for example .rc files would have escaped double quotes as `""`, so this should be replaced with just `"` before converting to the resx files. +If you already have a .rc file, copy the string table to a separate txt file and run the [convert-stringtable-to-resx.ps1](https://github.com/microsoft/PowerToys/blob/main/tools/build/convert-stringtable-to-resx.ps1) script on it. This script is not very robust to input, and requires the data in a specific format, where `IDS_ResName L"ResourceValue"` and any number of spaces can be present in between. The script converts this file to the format expected by [`resgen`](https://docs.microsoft.com/en-us/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert), which will convert it to resx. The resource names are changed from all uppercase to title case, and the `IDS_` prefix is removed. Escape characters might have to be manually replaced, for example .rc files would have escaped double quotes as `""`, so this should be replaced with just `"` before converting to the resx files. After generating the resx file, rename the existing rc and h files to ProjName.base.rc and resource.base.h. In the rc file remove the string table which is to be localized and in the .h file remove all `#define`s corresponding to localized resources. In the vcxproj of the C++ project, add the following build event: ``` @@ -67,7 +67,7 @@ After generating the resx file, rename the existing rc and h files to ProjName.b ``` -This event runs a script which generates a resource.h and ProjName.rc in the `Generated Files` folder using the strings in all the resx files along with the existing information in resource.base.h and ProjName.base.rc. The script can be found [here](https://github.com/microsoft/PowerToys/blob/master/tools/build/convert-resx-to-rc.ps1). The script uses [`resgen`](https://docs.microsoft.com/en-us/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert) to convert the resx file to a string table expected in the .rc file format. When the resources are added to the rc file the `IDS_` prefix is added and resource names are in upper case (as it was originally). Any occurrences of `"` in the string resource is escaped as `""` to prevent build errors. The string tables are added to the rc file in the following format: +This event runs a script which generates a resource.h and ProjName.rc in the `Generated Files` folder using the strings in all the resx files along with the existing information in resource.base.h and ProjName.base.rc. The script can be found [here](https://github.com/microsoft/PowerToys/blob/main/tools/build/convert-resx-to-rc.ps1). The script uses [`resgen`](https://docs.microsoft.com/en-us/dotnet/framework/tools/resgen-exe-resource-file-generator#Convert) to convert the resx file to a string table expected in the .rc file format. When the resources are added to the rc file the `IDS_` prefix is added and resource names are in upper case (as it was originally). Any occurrences of `"` in the string resource is escaped as `""` to prevent build errors. The string tables are added to the rc file in the following format: ``` #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US @@ -84,7 +84,7 @@ Since there is no API to identify the `AFX_TARG_*`, `LANG_*` or `SUBLANG_*` valu ``` -Some rc/resource.h files might be used in multiple projects (for example, KBM). To ensure the projects build for these cases, the build event can be added to the entire directory so that the rc files are generated before any project is built. See [Directory.Build.targets](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/Directory.Build.targets) for an example. +Some rc/resource.h files might be used in multiple projects (for example, KBM). To ensure the projects build for these cases, the build event can be added to the entire directory so that the rc files are generated before any project is built. See [Directory.Build.targets](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/Directory.Build.targets) for an example. Check [this PR](https://github.com/microsoft/PowerToys/pull/6104) for an example for making these changes for a C++ project. diff --git a/doc/devdocs/modules/keyboardmanager/keyboardeventhandlers.md b/doc/devdocs/modules/keyboardmanager/keyboardeventhandlers.md index 8908f450e..e6bfc0bbf 100644 --- a/doc/devdocs/modules/keyboardmanager/keyboardeventhandlers.md +++ b/doc/devdocs/modules/keyboardmanager/keyboardeventhandlers.md @@ -66,10 +66,10 @@ This file contains documentation for all the methods involved in key/shortcut re [This method](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/KeyboardEventHandlers.cpp#L126-L176) was added to support a feature for converting the behavior of a key from behaving like a toggle (like Caps Lock/Num Lock) to a modifier (like Ctrl), such that when you hold Caps Lock it would behave as if Caps Lock was active, and when it was not pressed Caps Lock would be off. For Caps Lock this would be similar to behaving like Shift, but for Num Lock there is no existing key which can substitute for this. This was added while testing out remapping for the KBM PoC, but wasn't added as a feature since it wasn't a priority. ## Tests -In order to test the remapping logic, a mocked keyboard input handler had to be created because otherwise the tests would process and send actual key events. For this the [`InputInterface`](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/common/InputInterface.h) was made, and in production code the methods are implemented using [`SendInput`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput) and [`GetAsyncKeyState`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getasynckeystate). In addition to this, [`GetCurrentApplication`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/Helpers.cpp#L226-L268) had to be mocked so that app-specific remapping can be tested. +In order to test the remapping logic, a mocked keyboard input handler had to be created because otherwise the tests would process and send actual key events. For this the [`InputInterface`](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/common/InputInterface.h) was made, and in production code the methods are implemented using [`SendInput`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput) and [`GetAsyncKeyState`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getasynckeystate). In addition to this, [`GetCurrentApplication`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/Helpers.cpp#L226-L268) had to be mocked so that app-specific remapping can be tested. ### MockedInput -The [`MockedInput`](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/test/MockedInput.h) class uses a 256 size `bool` vector to store the key state for each key code. Identifying the foreground process is mocked by simply setting and getting a string value for the name of the current process. +The [`MockedInput`](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/test/MockedInput.h) class uses a 256 size `bool` vector to store the key state for each key code. Identifying the foreground process is mocked by simply setting and getting a string value for the name of the current process. [To mock the `SendInput` method](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/test/MockedInput.cpp#L10-L110), the steps for processing the input are as follows. This implementation is based on public documentation for SendInput and the behavior of key messages and keyboard hooks: - Iterate over all the inputs in the INPUT array argument @@ -81,4 +81,4 @@ The [`MockedInput`](https://github.com/microsoft/PowerToys/blob/master/src/modul - For modifiers the behavior is slightly different as if the key state of the L/R version is modified, it should also modify the common version, and if a common version is released, it should release both the L and R versions. ### Tests for single key remaps and shortcut remaps -Using the MockedInput handler, all the expected (and known) key scenarios that can occur for while pressing a [remapped key](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/test/SingleKeyRemappingTests.cpp) or [remapped shortcut](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/test/OSLevelShortcutRemappingTests.cpp) are tested. The foreground app behavior which is specific to app-specific shortcuts is tested [here](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/test/AppSpecificShortcutRemappingTests.cpp). +Using the MockedInput handler, all the expected (and known) key scenarios that can occur for while pressing a [remapped key](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/test/SingleKeyRemappingTests.cpp) or [remapped shortcut](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/test/OSLevelShortcutRemappingTests.cpp) are tested. The foreground app behavior which is specific to app-specific shortcuts is tested [here](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/test/AppSpecificShortcutRemappingTests.cpp). diff --git a/doc/devdocs/modules/keyboardmanager/keyboardmanager.md b/doc/devdocs/modules/keyboardmanager/keyboardmanager.md index 3dd5304ef..8d0b27ff9 100644 --- a/doc/devdocs/modules/keyboardmanager/keyboardmanager.md +++ b/doc/devdocs/modules/keyboardmanager/keyboardmanager.md @@ -185,7 +185,7 @@ This method is used by [SharpKeys](https://github.com/randyrants/sharpkeys) and Using a driver approach has the benefit of not depending on precedence orders as KBM could always run before low level hooks, and it also has the benefit of differentiating between different keyboards, allowing [multi keyboard-specific remaps](https://github.com/microsoft/PowerToys/issues/1460). The disadvantages are however that any bug or crash could have system level consequences. [Interception](https://github.com/oblitum/Interception) is an open source driver that could be used for implementing this. The approach was deprioritized due to the potential side effects. ## Telemetry -Keyboard Manager emits the following telemetry events (implemented in [trace.h](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/common/trace.h) and [trace.cpp](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/common/trace.cpp)): +Keyboard Manager emits the following telemetry events (implemented in [trace.h](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/common/trace.h) and [trace.cpp](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/common/trace.cpp)): - **`KeyboardManager_EnableKeyboardManager`:** Logs a `boolean` value storing the KBM toggle state. It is logged whenever KBM is enabled or disabled (emitted [here](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/dllmain.cpp#L305-L316)). - **`KeyboardManager_KeyRemapCount`:** Logs the number of key to key and key to shortcut remaps (i.e. all the remaps on the Remap a key window). This gets logged on saving new settings in the Remap a key window (emitted [here](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/LoadingAndSavingRemappingHelper.cpp#L159-L163)). - **`KeyboardManager_OSLevelShortcutRemapCount`:** Logs the number of global shortcut to shortcut and shortcut to key remaps. This gets logged on saving new settings in the Remap a shortcut window (emitted [here](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/LoadingAndSavingRemappingHelper.cpp#L220)). diff --git a/doc/devdocs/modules/keyboardmanager/keyboardmanagercommon.md b/doc/devdocs/modules/keyboardmanager/keyboardmanagercommon.md index dbdfe930f..7a43fb07d 100644 --- a/doc/devdocs/modules/keyboardmanager/keyboardmanagercommon.md +++ b/doc/devdocs/modules/keyboardmanager/keyboardmanagercommon.md @@ -17,7 +17,7 @@ This project contains any code that is to be shared between the backend and UI p 1. [Foreground App Detection](#Foreground-App-Detection) ## KeyboardManagerState -[This class](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/common/KeyboardManagerState.cpp) stores all the data related to remappings and is also used in the sense of a View Model as it used to communicate common data that is shared between the KBM UI and the backend. They are accessed on the UI controls using static class members of `SingleKeyRemapControl` and `ShortcutControl`. +[This class](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/common/KeyboardManagerState.cpp) stores all the data related to remappings and is also used in the sense of a View Model as it used to communicate common data that is shared between the KBM UI and the backend. They are accessed on the UI controls using static class members of `SingleKeyRemapControl` and `ShortcutControl`. ### UI States [UI states](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/KeyboardManagerState.h#L27-L42) are used to keep track in which step of the UI flow is the user at, such as which Remap window they are on, or if they have one of the Type windows open. This is required because the hook needs to suppress input and update UI in some cases, and in some cases remappings have to be disabled altogether. @@ -37,12 +37,12 @@ The [`SaveConfigToFile`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a To prevent the UI thread and low level hook thread from concurrently accessing the remap tables we use an [`atomic bool` variable](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/KeyboardManagerState.h#L91-L92), which is set to `true` while the tables are getting updated. When this is `true` the hook will skip all remappings. Use of mutexes in the hook were removed to prevent re-entrant mutex bugs. ## KeyDelay -[This class](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/common/KeyDelay.cpp) implements a queue based approach for processing key events and based on the time difference between key down and key up events [executes separate methods for `ShortPress`, `LongPress` or `LongPressReleased`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/KeyDelay.h#L69-L72). The class is used for the hold Enter/Esc functionality required for making the Type window accessible and prevent keyboard traps (see [this](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/SingleKeyRemapControl.cpp#L273-L292) for an example of it's usage). The `KeyEvents` are added to the queue from the hook thread of KBM, and a separate [`DelayThread`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/KeyDelay.cpp#L142-L166) is used to process the key events by checking the `time` member in the key event. The thresholds for short vs long press and hold wait timeouts are `static` constants, but if the module is extended for other purposes these could be made into arguments. +[This class](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/common/KeyDelay.cpp) implements a queue based approach for processing key events and based on the time difference between key down and key up events [executes separate methods for `ShortPress`, `LongPress` or `LongPressReleased`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/KeyDelay.h#L69-L72). The class is used for the hold Enter/Esc functionality required for making the Type window accessible and prevent keyboard traps (see [this](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/SingleKeyRemapControl.cpp#L273-L292) for an example of it's usage). The `KeyEvents` are added to the queue from the hook thread of KBM, and a separate [`DelayThread`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/KeyDelay.cpp#L142-L166) is used to process the key events by checking the `time` member in the key event. The thresholds for short vs long press and hold wait timeouts are `static` constants, but if the module is extended for other purposes these could be made into arguments. **Note:** [Deletion of the `KeyDelay`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/KeyDelay.cpp#L4-L12) object should never be called from the `DelayThread` i.e. from within one of the 3 handlers, as it can re-enter the mutex and would lead to a deadlock. This can be avoided by either deleting it on a separate thread or as done in the KBM UI, on the dispatcher thread. See [this PR](https://github.com/microsoft/PowerToys/pull/6959#issue-496583547) for more details on this issue. ## Shortcut and RemapShortcut classes -The [`Shortcut` class](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/common/Shortcut.h) is a data structure for storing key combinations which are valid shortcuts and it contains several methods which are used for shortcut specific operations. [`RemapShortcut`](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/common/RemapShortcut.h) consists of a shortcut/key union (`std::variant`), along with other boolean flags which are required on the hook side for storing any relevant keyboard states mid-execution. +The [`Shortcut` class](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/common/Shortcut.h) is a data structure for storing key combinations which are valid shortcuts and it contains several methods which are used for shortcut specific operations. [`RemapShortcut`](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/common/RemapShortcut.h) consists of a shortcut/key union (`std::variant`), along with other boolean flags which are required on the hook side for storing any relevant keyboard states mid-execution. ### IsKeyboardStateClearExceptShortcut [This method](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/Shortcut.cpp#L665-L813) is used by the `HandleShortcutRemapEvent` to check if any other keys on the keyboard have been pressed apart from the keys in the shortcut. This is required because shortcut to shortcut remaps should not be applied if the shortcut is pressed with other keys. The method iterates over all the possible key codes, except any keys that are considered reserved, unassigned, OEM-specific or undefined, as well as mouse buttons (see list [here](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/Shortcut.cpp#L628-L663)). @@ -51,13 +51,13 @@ The [`Shortcut` class](https://github.com/microsoft/PowerToys/blob/master/src/mo [This method](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/Shortcut.cpp#L517-L614) uses `GetVirtualKeyState` (internally calls `GetAsyncKeyState` in production code), to check if all the modifiers of the current shortcut are being pressed. Since Win doesn't have a non-L/R key code we check this by checking both LWIN and RWIN. ### Tests -Tests for some methods in the `Shortcut` class can be found [here](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/test/ShortcutTests.cpp). +Tests for some methods in the `Shortcut` class can be found [here](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/test/ShortcutTests.cpp). ## Helpers -[This namespace](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/common/Helpers.cpp) has any methods which are used across either UI or the backend which aren't specific to either. Some of these methods have tests [here](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/test/SetKeyEventTests.cpp). +[This namespace](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/common/Helpers.cpp) has any methods which are used across either UI or the backend which aren't specific to either. Some of these methods have tests [here](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/test/SetKeyEventTests.cpp). ### Foreground App Detection [`GetCurrentApplication`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/Helpers.cpp#L226-L268) is used for detecting the foreground process for App-specific shortcuts. The logic is very similar to that used for FZ's app exception feature, involving `GetForegroundWindow` and `get_process_path`. The one additional case which has been added is for full-screen UWP apps, where the above method fails and returns `ApplicationFrameHost.exe`. The [`GetFullscreenUWPWindowHandle`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/common/Helpers.cpp#L210-L224) uses `GetGUIThreadInfo` API to find the window linked to the GUI thread. This logic is based on [this stackoverflow answer](https://stackoverflow.com/questions/39702704/connecting-uwp-apps-hosted-by-applicationframehost-to-their-real-processes/55353165#55353165). **Note:** The [`GetForegroundProcess` method](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/dll/Input.cpp#L17-L21) performs string allocation in a weird way because of exceptions that were occurring while running tests as a result of memory being allocated or deallocated across dll boundaries. Here's the comment from the PR where this was added -> To make app-specific logic test-able, a GetForegroundProcess was added to the input interface which internally calls GetCurrentApplication. This allows us to mock this method in the test project by just setting some process name as the foreground process for that function. When I set this to just return the string name, it would goes runtime errors on the test project in debug_heap with `__acrt_first_block == header`. Based on [this stackoverflow answer](https://stackoverflow.com/a/35311928), this would happen if allocation happens in one dll's code space and deallocation happens in another. One way to avoid this is to change both the projects to MD (multi threaded dll) instead of MT(multi threaded), however that results in many compile-time errors since all the PT projects are configured as MT. To solve this, the GetForegroundProcess was rewritten such that its argument is the output variable, and we allocate memory for that string within the AppSpecificHandler method rather than in that function. \ No newline at end of file +> To make app-specific logic test-able, a GetForegroundProcess was added to the input interface which internally calls GetCurrentApplication. This allows us to mock this method in the test project by just setting some process name as the foreground process for that function. When I set this to just return the string name, it would goes runtime errors on the test project in debug_heap with `__acrt_first_block == header`. Based on [this stackoverflow answer](https://stackoverflow.com/a/35311928), this would happen if allocation happens in one dll's code space and deallocation happens in another. One way to avoid this is to change both the projects to MD (multi threaded dll) instead of MT(multi threaded), however that results in many compile-time errors since all the PT projects are configured as MT. To solve this, the GetForegroundProcess was rewritten such that its argument is the output variable, and we allocate memory for that string within the AppSpecificHandler method rather than in that function. diff --git a/doc/devdocs/modules/keyboardmanager/keyboardmanagerui.md b/doc/devdocs/modules/keyboardmanager/keyboardmanagerui.md index 16ec0a9be..a6a9f4236 100644 --- a/doc/devdocs/modules/keyboardmanager/keyboardmanagerui.md +++ b/doc/devdocs/modules/keyboardmanager/keyboardmanagerui.md @@ -24,7 +24,7 @@ The KBM UI is implemented as a C++ XAML Island, but all the controls are impleme The windows are [created as C++ windows](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L128-L140) and the window sizes are set to default by [scaling them as per DPI](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L120-L126) using the `DPIAware::Convert` API from common lib. Since the UI is launched on a new thread, the window may not be in the foreground, so [we call `SetForegroundWindow`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L146-L150). -`DesktopWindowXamlSource` has to be declared and [it is initialized](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L159-L162) using the [`XamlBridge`](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/ui/XamlBridge.cpp), and [a second window handle](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L161-L162) is generated for the internal Xaml Island window. Most of the code was based on the [Xaml Island Sample](https://github.com/microsoft/Xaml-Islands-Samples/blob/master/Samples/Win32/SampleCppApp/XamlBridge.cpp). The `XamlBridge` class contains code which handles initializing the Xaml Island containers as well as handling special messages like keyboard navigation, and focus between islands and between the C++ window and the island. It also has methods for clearing the xaml islands and closing the window. +`DesktopWindowXamlSource` has to be declared and [it is initialized](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L159-L162) using the [`XamlBridge`](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/ui/XamlBridge.cpp), and [a second window handle](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L161-L162) is generated for the internal Xaml Island window. Most of the code was based on the [Xaml Island Sample](https://github.com/microsoft/Xaml-Islands-Samples/blob/master/Samples/Win32/SampleCppApp/XamlBridge.cpp). The `XamlBridge` class contains code which handles initializing the Xaml Island containers as well as handling special messages like keyboard navigation, and focus between islands and between the C++ window and the island. It also has methods for clearing the xaml islands and closing the window. Once the UI controls are created, the parent container is set as the content for the `DesktopWindowXamlSource` and the `XamlBridge.MessageLoop` is executed. Messages are processed by the C++ window handler like [`EditKeyboardWindowProc`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L364-L404). The general structure we use for this is, for any `WM_PAINT` or `WM_SIZE` message we resize the Xaml Island window. For `WM_GETMINMAXINFO` we set minimum widths so that the window cannot be resized beyond a minimum height and width. This is done to prevent the WinUI elements from overlapping and getting cropped. If it is neither of these cases we send the message to the [`XamlBridge.MessageHandler`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/XamlBridge.cpp#L291-L301) which handles Destroy, Activation and Focus. If `WM_NCDESTROY` is received when the `XamlBridge` is `nullptr`, the window thread is terminated. @@ -41,7 +41,7 @@ To access the brushes available on C# Xaml, it has to be done with the `Resource `primaryButton.Background(Windows::UI::Xaml::Application::Current().Resources().Lookup(box_value(L"SystemControlBackgroundBaseMediumLowBrush")).as());` ## UI Structure -The KBM UI consists of a [`Grid` with several columns](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L200-L218). Rows are added dynamically when [the add button is pressed](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L305-L309). [A vector of vector of unique pointers to `SingleKeyRemapControl`/`ShortcutControl`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L248-L249) is created so that references to the UI components and their data are not lost until the window is closed. [`SingleKeyRemapControl`](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/ui/SingleKeyRemapControl.cpp) is the UI class for each row of the Remap keys table, and [`ShortcutControl`](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/ui/ShortcutControl.cpp) is the UI class for each row of the Remap shortcuts table. [`KeyDropDownControl`](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp) is used for handling the ComboBox operations. Each of these two classes [have vectors of unique pointers to the `KeyDropDownControl` objects](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/ShortcutControl.h#L44-L45) so that references to the objects are active until the control is deleted. +The KBM UI consists of a [`Grid` with several columns](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L200-L218). Rows are added dynamically when [the add button is pressed](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L305-L309). [A vector of vector of unique pointers to `SingleKeyRemapControl`/`ShortcutControl`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L248-L249) is created so that references to the UI components and their data are not lost until the window is closed. [`SingleKeyRemapControl`](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/ui/SingleKeyRemapControl.cpp) is the UI class for each row of the Remap keys table, and [`ShortcutControl`](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/ui/ShortcutControl.cpp) is the UI class for each row of the Remap shortcuts table. [`KeyDropDownControl`](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp) is used for handling the ComboBox operations. Each of these two classes [have vectors of unique pointers to the `KeyDropDownControl` objects](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/ShortcutControl.h#L44-L45) so that references to the objects are active until the control is deleted. When the UI windows are activated the `KeyboardManagerState` object [sets the `UIState` variable](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L251-L252) which is used for distinguishing if the UI is up from the keyboard hook thread. The [states are also updated](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/SingleKeyRemapControl.cpp#L53) on opening and closing the Type window. @@ -89,7 +89,7 @@ On making a selection in the drop down, [the selection handler](https://github.c - Conflicting modifier previously remapped (Ctrl->A and Ctrl(left)->B, since Ctrl also includes Ctrl(left)) If the selection is found to be valid, the `singleKeyRemapBuffer` is updated accordingly. For handling `Shortcut` and key in the remap buffer for the right column, we use `std::variant`, which allows us to store either of the two types and check which one of them is present in the buffer by using the `index` method. -[`ValidateAndUpdateKeyBufferElement`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/BufferValidationHelpers.cpp#L8-L66) does not reference any UI components and instead takes all the relevant data as arguments. This method [has tests](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/test/BufferValidationTests.cpp) which covers all the cases that could arise from making selections on the UI. +[`ValidateAndUpdateKeyBufferElement`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/BufferValidationHelpers.cpp#L8-L66) does not reference any UI components and instead takes all the relevant data as arguments. This method [has tests](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/test/BufferValidationTests.cpp) which covers all the cases that could arise from making selections on the UI. ### Shortcut ComboBox Selection Handler On making a selection in the drop down, [the selection handler](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp#L215-L295) validates the input with the buffer from the other column and other rows. Error messages are shown using flyouts if the selection is not considered valid and the drop down and buffer for that entry are reset to empty selection. @@ -115,7 +115,7 @@ Unlike the Single Key handler, there is a different set of errors that can occur - Conflicting shortcut previously remapped for same target app (Ctrl+A->B and Ctrl(left)+A->C, since Ctrl also includes Ctrl(left)) - Illegal shortcut remaps like Win+L or Ctrl+Alt+Del (since these cannot be remapped using LL hooks) -[`ValidateShortcutBufferElement`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/BufferValidationHelpers.cpp#L68-L304) does not reference any UI components and instead takes all the relevant data as arguments. This method [has tests](https://github.com/microsoft/PowerToys/blob/master/src/modules/keyboardmanager/test/BufferValidationTests.cpp) which covers all the cases that could arise from making selections on the UI. +[`ValidateShortcutBufferElement`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/BufferValidationHelpers.cpp#L68-L304) does not reference any UI components and instead takes all the relevant data as arguments. This method [has tests](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/test/BufferValidationTests.cpp) which covers all the cases that could arise from making selections on the UI. **Note:** After updating the buffer we have [code to handle a special case](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/KeyDropDownControl.cpp#L269-L279), which was required to prevent scenarios where a drop down can get deleted but the corresponding `KeyDropDownControl` object isn't deleted. The code checks if the drop down is still linked to the parent and accordingly deletes the `KeyDropDownControl` object from the vector. diff --git a/doc/devdocs/modules/launcher/debugging.md b/doc/devdocs/modules/launcher/debugging.md index bf1606b9a..24e7ad63d 100644 --- a/doc/devdocs/modules/launcher/debugging.md +++ b/doc/devdocs/modules/launcher/debugging.md @@ -3,7 +3,7 @@ ## Debugging Prerequisite -Setup development environment for PowerToys by following instruction [here.](https://github.com/microsoft/PowerToys/tree/master/doc/devdocs#prerequisites-for-compiling-powertoys) +Setup development environment for PowerToys by following instruction [here.](https://github.com/microsoft/PowerToys/tree/main/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. diff --git a/doc/devdocs/modules/launcher/plugins/windowssettings.md b/doc/devdocs/modules/launcher/plugins/windowssettings.md index aa8678577..62244d928 100644 --- a/doc/devdocs/modules/launcher/plugins/windowssettings.md +++ b/doc/devdocs/modules/launcher/plugins/windowssettings.md @@ -26,6 +26,7 @@ The `WindowsSettings.json` use a JSON schema file that make it easier to edit it | `Note` | Yes | String | `Note` | | `IntroducedInBuild` | Yes | Integer | | | `DeprecatedInBuild` | Yes | Integer | | +| `ShowAsFirstResult` | Yes | Boolean | | A minimum entry for the `WindowsSettings.json` looks like: @@ -48,7 +49,8 @@ A full entry for the `WindowsSettings.json` looks like: "AltNames": [ "NiceSetting" ], "Note": "NoteMySettingNote", "IntroducedInBuild" : 1903, - "DeprecatedInBuild" : 2004 + "DeprecatedInBuild" : 2004, + "ShowAsFirstResult" : true } ``` @@ -65,11 +67,12 @@ A full entry for the `WindowsSettings.json` looks like: There are three different score types with different start values. -| Score type | Start value | -| ------------ | ------------ | -| High score | 10000 | -| Medium score | 5000 | -| Low score | 1000 | +| Score type | Start value | +| ------------------ | ------------ | +| First result score | 10500 | +| High score | 10000 | +| Medium score | 5000 | +| Low score | 1000 | Each score will decreased by one when a condition match. @@ -83,6 +86,9 @@ Each score will decreased by one when a condition match. | 6. | One alternative name of the settings starts with the search value | Medium score | | x. | no condition match | Low score | +### Remarks +* For each score condition we check if the property "ShowAsFirstResult" of the setting is true. If yes we use the firstResultScore instead of condition`s score. + ## Important for developers ### General diff --git a/doc/devdocs/settings.md b/doc/devdocs/settings.md index b5b98af61..1dbbfe9fd 100644 --- a/doc/devdocs/settings.md +++ b/doc/devdocs/settings.md @@ -53,7 +53,7 @@ bool ExamplePowertoy::get_config(wchar_t* buffer, int* buffer_size) return settings.serialize_to_buffer(buffer, buffer_size); } ``` -The list of all the available settings elements and their description is [further in this doc](#available-settings-elements). New PowerToy icons need to be [added to the `settings-web` project](https://github.com/microsoft/PowerToys/blob/master/doc/devdocs/settings-web.md#updating-the-icons). +The list of all the available settings elements and their description is [further in this doc](#available-settings-elements). New PowerToy icons need to be [added to the `settings-web` project](https://github.com/microsoft/PowerToys/blob/main/doc/devdocs/settings-web.md#updating-the-icons). ## User changes settings When user closes the settings screen, the runner will call the [`get_config()`](modules/interface.md) method. Use [`PowerToyValues`](/src/common/settings_objects.h) class static `from_json_string` method to parse the settings. After that, the code is similar to loading the settings from disk: diff --git a/doc/devdocs/style.md b/doc/devdocs/style.md index a25f630bb..f63e96298 100644 --- a/doc/devdocs/style.md +++ b/doc/devdocs/style.md @@ -6,7 +6,7 @@ ## Formatting -- We use [`.clang-format`](https://github.com/microsoft/PowerToys/blob/master/src/.clang-format) style file to enable automatic code formatting. You can [easily format source files from Visual Studio](https://devblogs.microsoft.com/cppblog/clangformat-support-in-visual-studio-2017-15-7-preview-1/). For example, `CTRL+K CTRL+D` formats the current document. -- If you prefer another text editor or have ClangFormat disabled in Visual Studio, you could invoke [`format_sources`](https://github.com/microsoft/PowerToys/blob/master/src/codeAnalysis/format_sources.ps1) powershell script from command line. It gets a list of all currently modified files from `git` and invokes clang-format on them. +- We use [`.clang-format`](https://github.com/microsoft/PowerToys/blob/main/src/.clang-format) style file to enable automatic code formatting. You can [easily format source files from Visual Studio](https://devblogs.microsoft.com/cppblog/clangformat-support-in-visual-studio-2017-15-7-preview-1/). For example, `CTRL+K CTRL+D` formats the current document. +- If you prefer another text editor or have ClangFormat disabled in Visual Studio, you could invoke [`format_sources`](https://github.com/microsoft/PowerToys/blob/main/src/codeAnalysis/format_sources.ps1) powershell script from command line. It gets a list of all currently modified files from `git` and invokes clang-format on them. Please note that you should also have `clang-format.exe` in `%PATH%` for it to work. The script can infer the path of `clang-format.exe` version which is shipped with Visual Studio at `%VCINSTALLDIR%\Tools\Llvm\bin\`, if you launch it from the *Native Tools Command Prompt for VS*. - CI doesn't enforce code formatting yet, since we're gradually applying code formatting to the codebase, but please adhere to our formatting style for any new code. diff --git a/doc/images/icons/Find My Mouse.png b/doc/images/icons/Find My Mouse.png new file mode 100644 index 000000000..5098e2237 Binary files /dev/null and b/doc/images/icons/Find My Mouse.png differ diff --git a/doc/images/icons/Mouse Highlighter.png b/doc/images/icons/Mouse Highlighter.png new file mode 100644 index 000000000..789b0b1c0 Binary files /dev/null and b/doc/images/icons/Mouse Highlighter.png differ diff --git a/doc/images/settingsv2/file-explorer.png b/doc/images/settingsv2/file-explorer.png index 7e5bc1090..bb83381ce 100644 Binary files a/doc/images/settingsv2/file-explorer.png and b/doc/images/settingsv2/file-explorer.png differ diff --git a/installer/PowerToysBootstrapper/bootstrapper/DotnetInstallation.cpp b/installer/PowerToysBootstrapper/bootstrapper/DotnetInstallation.cpp index 7647dc143..c5f638335 100644 --- a/installer/PowerToysBootstrapper/bootstrapper/DotnetInstallation.cpp +++ b/installer/PowerToysBootstrapper/bootstrapper/DotnetInstallation.cpp @@ -9,7 +9,7 @@ namespace fs = std::filesystem; namespace updating { - constexpr size_t REQUIRED_MINIMAL_PATCH = 15; + constexpr size_t REQUIRED_MINIMAL_PATCH = 20; bool dotnet_is_installed() { @@ -45,7 +45,7 @@ namespace updating std::optional download_dotnet() { - const wchar_t DOTNET_DESKTOP_DOWNLOAD_LINK[] = L"https://download.visualstudio.microsoft.com/download/pr/d30352fe-d4f3-4203-91b9-01a3b66a802e/bb416e6573fa278fec92113abefc58b3/windowsdesktop-runtime-3.1.15-win-x64.exe"; + const wchar_t DOTNET_DESKTOP_DOWNLOAD_LINK[] = L"https://download.visualstudio.microsoft.com/download/pr/93c69a29-d379-4a5d-bb9e-3116cc14de41/907bbc52446d8bb7baa0c6faebde1d44/windowsdesktop-runtime-3.1.20-win-x64.exe"; const wchar_t DOTNET_DESKTOP_FILENAME[] = L"windowsdesktop-runtime.exe"; auto dotnet_download_path = fs::temp_directory_path() / DOTNET_DESKTOP_FILENAME; diff --git a/installer/PowerToysSetup.sln b/installer/PowerToysSetup.sln index e9e00eaa8..cd095d536 100644 --- a/installer/PowerToysSetup.sln +++ b/installer/PowerToysSetup.sln @@ -7,6 +7,10 @@ Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "PowerToysSetup", "PowerToys EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToysSetupCustomActions", "PowerToysSetupCustomActions\PowerToysSetupCustomActions.vcxproj", "{32F3882B-F2D6-4586-B5ED-11E39E522BD3}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "spdlog", "..\src\logging\logging.vcxproj", "{7E1E3F13-2BD6-3F75-A6A7-873A2B55C60F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "logger", "..\src\common\logger\logger.vcxproj", "{D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -21,6 +25,14 @@ Global {32F3882B-F2D6-4586-B5ED-11E39E522BD3}.Debug|x64.Build.0 = Debug|x64 {32F3882B-F2D6-4586-B5ED-11E39E522BD3}.Release|x64.ActiveCfg = Release|x64 {32F3882B-F2D6-4586-B5ED-11E39E522BD3}.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 + {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|x64.ActiveCfg = Debug|x64 + {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Debug|x64.Build.0 = Debug|x64 + {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|x64.ActiveCfg = Release|x64 + {D9B8FC84-322A-4F9F-BBB9-20915C47DDFD}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index aaab0b125..0cbf25227 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -316,27 +316,27 @@ - + - + - + - + - + - + @@ -344,11 +344,11 @@ - + - + @@ -361,7 +361,7 @@ - + @@ -630,6 +630,10 @@ + + + + @@ -653,6 +657,9 @@ + + + @@ -967,6 +974,7 @@ + @@ -1055,28 +1063,28 @@ - + - + - + - + - + - + - + @@ -1085,7 +1093,7 @@ - + - + - + - + - + - + - + - + - + - + - - + + - + - - + + @@ -1223,23 +1231,23 @@ - + - - + + - + - - + + @@ -1277,12 +1285,12 @@ - + - - + + diff --git a/installer/PowerToysSetupCustomActions/CustomAction.cpp b/installer/PowerToysSetupCustomActions/CustomAction.cpp index f1b43265e..e23b7102e 100644 --- a/installer/PowerToysSetupCustomActions/CustomAction.cpp +++ b/installer/PowerToysSetupCustomActions/CustomAction.cpp @@ -2,6 +2,9 @@ #include "resource.h" #include +#include + +#include "../../src/common/logger/logger.h" #include "../../src/common/utils/MsiUtils.h" #include "../../src/common/utils/modulesRegistry.h" #include "../../src/common/updating/installer.h" @@ -26,6 +29,24 @@ const DWORD USERNAME_LEN = UNLEN + 1; // User Name + '\0' static const wchar_t* POWERTOYS_EXE_COMPONENT = L"{A2C66D91-3485-4D00-B04D-91844E6B345B}"; static const wchar_t* POWERTOYS_UPGRADE_CODE = L"{42B84BF7-5FBF-473B-9C8B-049DC16F7708}"; +struct WcaSink : spdlog::sinks::base_sink +{ + virtual void sink_it_(const spdlog::details::log_msg& msg) override + { + WcaLog(LOGMSG_STANDARD, msg.payload.data()); + } + virtual void flush_() override + { + // we don't need to flush wca log manually + } +}; + +void initSystemLogger() +{ + static std::once_flag initLoggerFlag; + std::call_once(initLoggerFlag, []() { Logger::init(std::vector{ std::make_shared() }); }); +} + HRESULT getInstallFolder(MSIHANDLE hInstall, std::wstring& installationDir) { DWORD len = 0; @@ -34,7 +55,7 @@ HRESULT getInstallFolder(MSIHANDLE hInstall, std::wstring& installationDir) len += 1; installationDir.resize(len); HRESULT hr = MsiGetPropertyW(hInstall, L"CustomActionData", installationDir.data(), &len); - if(installationDir.length()) + if (installationDir.length()) { installationDir.resize(installationDir.length() - 1); } @@ -44,24 +65,30 @@ LExit: } UINT __stdcall ApplyModulesRegistryChangeSetsCA(MSIHANDLE hInstall) { + initSystemLogger(); HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; std::wstring installationFolder; + bool failedToApply = false; hr = WcaInitialize(hInstall, "ApplyModulesRegistryChangeSets"); ExitOnFailure(hr, "Failed to initialize"); hr = getInstallFolder(hInstall, installationFolder); - ExitOnFailure(hr, "Failed to get installfolder."); + ExitOnFailure(hr, "Failed to get installFolder."); + for (const auto& changeSet : getAllModulesChangeSets(installationFolder, false)) { if (!changeSet.apply()) { WcaLog(LOGMSG_STANDARD, "Couldn't apply registry changeSet"); + failedToApply = true; } } - ExitOnFailure(hr, "Failed to extract msix"); - + if (!failedToApply) + { + WcaLog(LOGMSG_STANDARD, "All registry changeSets applied successfully"); + } LExit: er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; return WcaFinalize(er); @@ -69,6 +96,7 @@ LExit: UINT __stdcall UnApplyModulesRegistryChangeSetsCA(MSIHANDLE hInstall) { + initSystemLogger(); HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; std::wstring installationFolder; @@ -76,7 +104,7 @@ UINT __stdcall UnApplyModulesRegistryChangeSetsCA(MSIHANDLE hInstall) hr = WcaInitialize(hInstall, "UndoModulesRegistryChangeSets"); // original func name is too long ExitOnFailure(hr, "Failed to initialize"); hr = getInstallFolder(hInstall, installationFolder); - ExitOnFailure(hr, "Failed to get installfolder."); + ExitOnFailure(hr, "Failed to get installFolder."); for (const auto& changeSet : getAllModulesChangeSets(installationFolder, false)) { changeSet.unApply(); diff --git a/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj b/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj index 07c14882c..935e5b799 100644 --- a/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj +++ b/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj @@ -30,6 +30,7 @@ v142 + @@ -122,6 +123,11 @@ + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + diff --git a/installer/PowerToysSetupCustomActions/stdafx.h b/installer/PowerToysSetupCustomActions/stdafx.h index 7d80cbe2a..2174541f0 100644 --- a/installer/PowerToysSetupCustomActions/stdafx.h +++ b/installer/PowerToysSetupCustomActions/stdafx.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include diff --git a/installer/README.md b/installer/README.md index 44edb9b59..18e481842 100644 --- a/installer/README.md +++ b/installer/README.md @@ -1,3 +1,3 @@ # PowerToys installer instructions -Please go to https://github.com/microsoft/PowerToys/tree/master/doc/devdocs#compile-the-installer for instructions +Please go to https://github.com/microsoft/PowerToys/tree/main/doc/devdocs#compile-the-installer for instructions diff --git a/src/modules/fancyzones/FancyZonesLib/CallTracer.cpp b/src/common/logger/call_tracer.cpp similarity index 96% rename from src/modules/fancyzones/FancyZonesLib/CallTracer.cpp rename to src/common/logger/call_tracer.cpp index a7dd2ad90..ae4b9dfa0 100644 --- a/src/modules/fancyzones/FancyZonesLib/CallTracer.cpp +++ b/src/common/logger/call_tracer.cpp @@ -1,5 +1,7 @@ #include "pch.h" -#include "CallTracer.h" +#include "call_tracer.h" + +#include #include namespace diff --git a/src/modules/fancyzones/FancyZonesLib/CallTracer.h b/src/common/logger/call_tracer.h similarity index 82% rename from src/modules/fancyzones/FancyZonesLib/CallTracer.h rename to src/common/logger/call_tracer.h index c2c0e67e8..a271633c2 100644 --- a/src/modules/fancyzones/FancyZonesLib/CallTracer.h +++ b/src/common/logger/call_tracer.h @@ -1,6 +1,8 @@ #pragma once -#include "common/logger/logger.h" +#include + +#include "logger.h" #define _TRACER_ CallTracer callTracer(__FUNCTION__) diff --git a/src/common/logger/logger.cpp b/src/common/logger/logger.cpp index fa6252db1..444c0e787 100644 --- a/src/common/logger/logger.cpp +++ b/src/common/logger/logger.cpp @@ -3,7 +3,7 @@ #include "pch.h" #include "framework.h" #include "logger.h" -#include +#include #include #include #include @@ -16,26 +16,32 @@ using spdlog::sinks::daily_file_sink_mt; using spdlog::sinks::msvc_sink_mt; using std::make_shared; -std::map logLevelMapping = { - { L"trace", level_enum::trace }, - { L"debug", level_enum::debug }, - { L"info", level_enum::info }, - { L"warn", level_enum::warn }, - { L"err", level_enum::err }, - { L"critical", level_enum::critical }, - { L"off", level_enum::off }, -}; +namespace +{ + const std::unordered_map logLevelMapping = { + { L"trace", level_enum::trace }, + { L"debug", level_enum::debug }, + { L"info", level_enum::info }, + { L"warn", level_enum::warn }, + { L"err", level_enum::err }, + { L"critical", level_enum::critical }, + { L"off", level_enum::off }, + }; +} level_enum getLogLevel(std::wstring_view logSettingsPath) { auto logLevel = get_log_settings(logSettingsPath).logLevel; - level_enum result = logLevelMapping[LogSettings::defaultLogLevel]; - if (logLevelMapping.find(logLevel) != logLevelMapping.end()) + if (auto it = logLevelMapping.find(logLevel); it != logLevelMapping.end()) { - result = logLevelMapping[logLevel]; + return it->second; } - return result; + if (auto it = logLevelMapping.find(LogSettings::defaultLogLevel); it != logLevelMapping.end()) + { + return it->second; + } + return level_enum::trace; } std::shared_ptr Logger::logger = spdlog::null_logger_mt("null"); @@ -89,3 +95,14 @@ void Logger::init(std::string loggerName, std::wstring logFilePath, std::wstring spdlog::flush_every(std::chrono::seconds(3)); logger->info("{} logger is initialized", loggerName); } + +void Logger::init(std::vector sinks) +{ + auto logger = std::make_shared("", begin(sinks), end(sinks)); + if (!logger) + { + return; + } + + Logger::logger = logger; +} diff --git a/src/common/logger/logger.h b/src/common/logger/logger.h index 04f71043c..fa8ebb1d8 100644 --- a/src/common/logger/logger.h +++ b/src/common/logger/logger.h @@ -13,6 +13,7 @@ public: Logger() = delete; static void init(std::string loggerName, std::wstring logFilePath, std::wstring_view logSettingsPath); + static void init(std::vector sinks); // log message should not be localized template diff --git a/src/common/logger/logger.vcxproj b/src/common/logger/logger.vcxproj index e8417dd42..09649a7ce 100644 --- a/src/common/logger/logger.vcxproj +++ b/src/common/logger/logger.vcxproj @@ -31,12 +31,14 @@ + + @@ -50,9 +52,6 @@ {7e1e3f13-2bd6-3f75-a6a7-873a2b55c60f} - - {6955446d-23f7-4023-9bb3-8657f904af99} - diff --git a/src/common/logger/logger.vcxproj.filters b/src/common/logger/logger.vcxproj.filters index cc30326a7..27abca281 100644 --- a/src/common/logger/logger.vcxproj.filters +++ b/src/common/logger/logger.vcxproj.filters @@ -27,6 +27,9 @@ Header Files + + Header Files + @@ -38,6 +41,9 @@ Source Files + + Source Files + diff --git a/src/common/logger/logger_settings.cpp b/src/common/logger/logger_settings.cpp index 7484f42a5..30fce8427 100644 --- a/src/common/logger/logger_settings.cpp +++ b/src/common/logger/logger_settings.cpp @@ -9,7 +9,7 @@ using namespace winrt::Windows::Data::Json; LogSettings::LogSettings() { - this->logLevel = LogSettings::defaultLogLevel; + logLevel = defaultLogLevel; } std::optional from_file(std::wstring_view file_name) diff --git a/src/common/logger/logger_settings.h b/src/common/logger/logger_settings.h index 35b398792..b88af2bc7 100644 --- a/src/common/logger/logger_settings.h +++ b/src/common/logger/logger_settings.h @@ -13,6 +13,8 @@ struct LogSettings inline const static std::wstring actionRunnerLogPath = L"RunnerLogs\\action-runner-log.txt"; inline const static std::string updateLoggerName = "update"; inline const static std::wstring updateLogPath = L"UpdateLogs\\update-log.txt"; + inline const static std::string fileExplorerLoggerName = "FileExplorer"; + inline const static std::wstring fileExplorerLogPath = L"Logs\\file-explorer-log.txt"; inline const static std::string launcherLoggerName = "launcher"; inline const static std::wstring launcherLogPath = L"LogsModuleInterface\\launcher-log.txt"; inline const static std::wstring awakeLogPath = L"Logs\\awake-log.txt"; @@ -24,6 +26,8 @@ struct LogSettings inline const static std::string keyboardManagerLoggerName = "keyboard-manager"; inline const static std::wstring keyboardManagerLogPath = L"Logs\\keyboard-manager-log.txt"; inline const static std::string findMyMouseLoggerName = "find-my-mouse"; + inline const static std::string mouseHighlighterLoggerName = "mouse-highlighter"; + inline const static std::string powerRenameLoggerName = "powerrename"; inline const static int retention = 30; std::wstring logLevel; LogSettings(); diff --git a/src/common/utils/UnhandledExceptionHandler_x64.h b/src/common/utils/UnhandledExceptionHandler_x64.h index 0723e4593..8026870f7 100644 --- a/src/common/utils/UnhandledExceptionHandler_x64.h +++ b/src/common/utils/UnhandledExceptionHandler_x64.h @@ -9,10 +9,7 @@ #include "winapi_error.h" #include "../logger/logger.h" -static IMAGEHLP_SYMBOL64* pSymbol = (IMAGEHLP_SYMBOL64*)malloc(sizeof(IMAGEHLP_SYMBOL64) + MAX_PATH * sizeof(TCHAR)); -static IMAGEHLP_LINE64 line; static BOOLEAN processingException = FALSE; -static CHAR modulePath[MAX_PATH]; static inline const char* exceptionDescription(const DWORD& code) { @@ -64,15 +61,15 @@ static inline const char* exceptionDescription(const DWORD& code) } /* Returns the index of the last backslash in the file path */ -inline int GetFilenameStart(CHAR* path) +inline int GetFilenameStart(wchar_t* path) { int pos = 0; int found = 0; if (path != NULL) { - while (path[pos] != '\0' && pos < MAX_PATH) + while (path[pos] != L'\0' && pos < MAX_PATH) { - if (path[pos] == '\\') + if (path[pos] == L'\\') { found = pos + 1; } @@ -83,22 +80,73 @@ inline int GetFilenameStart(CHAR* path) return found; } -inline void LogStackTrace() +inline std::wstring GetModuleName(HANDLE process, const STACKFRAME64& stack) { - BOOL result; - HANDLE thread; - HANDLE process; - CONTEXT context; - STACKFRAME64 stack; - ULONG frame; - DWORD64 dw64Displacement; - DWORD dwDisplacement; + static wchar_t modulePath[MAX_PATH]{}; + const size_t size = sizeof(modulePath); + memset(&modulePath[0], '\0', size); + + DWORD64 moduleBase = SymGetModuleBase64(process, stack.AddrPC.Offset); + if (!moduleBase) + { + Logger::error(L"Failed to get a module. {}", get_last_error_or_default(GetLastError())); + return std::wstring(); + } + + if (!GetModuleFileNameW((HINSTANCE)moduleBase, modulePath, MAX_PATH)) + { + Logger::error(L"Failed to get a module path. {}", get_last_error_or_default(GetLastError())); + return std::wstring(); + } + + const int start = GetFilenameStart(modulePath); + return std::wstring(modulePath, start); +} + +inline std::wstring GetName(HANDLE process, const STACKFRAME64& stack) +{ + static IMAGEHLP_SYMBOL64* pSymbol = (IMAGEHLP_SYMBOL64*)malloc(sizeof(IMAGEHLP_SYMBOL64) + MAX_PATH * sizeof(TCHAR)); + if (!pSymbol) + { + return std::wstring(); + } - memset(&stack, 0, sizeof(STACKFRAME64)); memset(pSymbol, '\0', sizeof(*pSymbol) + MAX_PATH); - memset(&modulePath[0], '\0', sizeof(modulePath)); + pSymbol->MaxNameLength = MAX_PATH; + pSymbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); + + DWORD64 dw64Displacement = 0; + if (!SymGetSymFromAddr64(process, stack.AddrPC.Offset, &dw64Displacement, pSymbol)) + { + Logger::error(L"Failed to get a symbol. {}", get_last_error_or_default(GetLastError())); + return std::wstring(); + } + + std::string str = pSymbol->Name; + return std::wstring(str.begin(), str.end()); +} + +inline std::wstring GetLine(HANDLE process, const STACKFRAME64& stack) +{ + static IMAGEHLP_LINE64 line{}; + + memset(&line, '\0', sizeof(IMAGEHLP_LINE64)); + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); line.LineNumber = 0; + DWORD dwDisplacement = 0; + if (!SymGetLineFromAddr64(process, stack.AddrPC.Offset, &dwDisplacement, &line)) + { + return std::wstring(); + } + + std::string fileName(line.FileName); + return L"(" + std::wstring(fileName.begin(), fileName.end()) + L":" + std::to_wstring(line.LineNumber) + L")"; +} + +inline void LogStackTrace() +{ + CONTEXT context; try { RtlCaptureContext(&context); @@ -109,9 +157,11 @@ inline void LogStackTrace() return; } - process = GetCurrentProcess(); - thread = GetCurrentThread(); - dw64Displacement = 0; + STACKFRAME64 stack; + memset(&stack, 0, sizeof(STACKFRAME64)); + + HANDLE process = GetCurrentProcess(); + HANDLE thread = GetCurrentThread(); stack.AddrPC.Offset = context.Rip; stack.AddrPC.Mode = AddrModeFlat; stack.AddrStack.Offset = context.Rsp; @@ -119,8 +169,9 @@ inline void LogStackTrace() stack.AddrFrame.Offset = context.Rbp; stack.AddrFrame.Mode = AddrModeFlat; - std::stringstream ss; - for (frame = 0;; frame++) + BOOL result = false; + std::wstringstream ss; + for (;;) { result = StackWalk64( IMAGE_FILE_MACHINE_AMD64, @@ -138,34 +189,10 @@ inline void LogStackTrace() break; } - pSymbol->MaxNameLength = MAX_PATH; - pSymbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); - - if (!SymGetSymFromAddr64(process, stack.AddrPC.Offset, &dw64Displacement, pSymbol)) - { - Logger::error(L"Failed to get a symbol. {}", get_last_error_or_default(GetLastError())); - } - - line.LineNumber = 0; - SymGetLineFromAddr64(process, stack.AddrPC.Offset, &dwDisplacement, &line); - - DWORD64 moduleBase = SymGetModuleBase64(process, stack.AddrPC.Offset); - if (moduleBase) - { - if (!GetModuleFileNameA((HINSTANCE)moduleBase, modulePath, MAX_PATH)) - { - Logger::error(L"Failed to get a module path. {}", get_last_error_or_default(GetLastError())); - } - } - else - { - Logger::error(L"Failed to get a module. {}", get_last_error_or_default(GetLastError())); - } - - ss << std::string(modulePath).substr(GetFilenameStart(modulePath)) << "!" << pSymbol->Name << "(" << line.FileName << ":" << line.LineNumber << std::endl; + ss << GetModuleName(process, stack) << "!" << GetName(process, stack) << GetLine(process, stack) << std::endl; } - Logger::error("STACK TRACE\r\n{}", ss.str()); + Logger::error(L"STACK TRACE\r\n{}", ss.str()); Logger::flush(); } @@ -218,7 +245,6 @@ inline void InitSymbols() { // Preload symbols so they will be available in case of out-of-memory exception SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); - line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); HANDLE process = GetCurrentProcess(); if (!SymInitialize(process, NULL, TRUE)) { diff --git a/src/common/utils/color.h b/src/common/utils/color.h new file mode 100644 index 000000000..3996ecd61 --- /dev/null +++ b/src/common/utils/color.h @@ -0,0 +1,21 @@ +#pragma once + +// helper function to get the RGB from a #FFFFFF string. +inline bool checkValidRGB(std::wstring_view hex, uint8_t* R, uint8_t* G, uint8_t* B) +{ + if (hex.length() != 7) + return false; + hex = hex.substr(1, 6); // remove # + for (auto& c : hex) + { + if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'))) + { + return false; + } + } + if (swscanf_s(hex.data(), L"%2hhx%2hhx%2hhx", R, G, B) != 3) + { + return false; + } + return true; +} diff --git a/src/common/utils/registry.h b/src/common/utils/registry.h index d0ebcb187..43ea144ba 100644 --- a/src/common/utils/registry.h +++ b/src/common/utils/registry.h @@ -9,6 +9,8 @@ #include #include +#include "../logger/logger.h" +#include "../utils/winapi_error.h" #include "../version/version.h" namespace registry @@ -35,7 +37,28 @@ namespace registry template overloaded(Ts...) -> overloaded; + + inline const wchar_t* getScopeName(HKEY scope) + { + if (scope == HKEY_LOCAL_MACHINE) + { + return L"HKLM"; + } + else if (scope == HKEY_CURRENT_USER) + { + return L"HKCU"; + } + else if (scope == HKEY_CLASSES_ROOT) + { + return L"HKCR"; + } + else + { + return L"HK??"; + } + } } + struct ValueChange { using value_t = std::variant; @@ -51,11 +74,28 @@ namespace registry { } + std::wstring toString() const + { + using namespace detail; + + std::wstring value_str; + std::visit(overloaded{ [&](DWORD value) { + std::wostringstream oss; + oss << value; + value_str = oss.str(); + }, + [&](const std::wstring& value) { value_str = value; } }, + value); + + return fmt::format(L"{}\\{}\\{}:{}", detail::getScopeName(scope), path, name ? *name : L"Default", value_str); + } + bool isApplied() const { HKEY key{}; - if (RegOpenKeyExW(scope, path.c_str(), 0, KEY_READ, &key) != ERROR_SUCCESS) + if (auto res = RegOpenKeyExW(scope, path.c_str(), 0, KEY_READ, &key); res != ERROR_SUCCESS) { + Logger::info(L"isApplied of {}: RegOpenKeyExW failed: {}", toString(), get_last_error_or_default(res)); return false; } detail::on_exit closeKey{ [key] { RegCloseKey(key); } }; @@ -65,13 +105,15 @@ namespace registry DWORD retrievedType{}; wchar_t buffer[VALUE_BUFFER_SIZE]; DWORD valueSize = sizeof(buffer); - if (RegQueryValueExW(key, - name.has_value() ? name->c_str() : nullptr, - 0, - &retrievedType, - reinterpret_cast(&buffer), - &valueSize) != ERROR_SUCCESS) + if (auto res = RegQueryValueExW(key, + name.has_value() ? name->c_str() : nullptr, + 0, + &retrievedType, + reinterpret_cast(&buffer), + &valueSize); + res != ERROR_SUCCESS) { + Logger::info(L"isApplied of {}: RegQueryValueExW failed: {}", toString(), get_last_error_or_default(res)); return false; } @@ -94,9 +136,10 @@ namespace registry { HKEY key{}; - if (RegCreateKeyExW(scope, path.c_str(), 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_WRITE, nullptr, &key, nullptr) != - ERROR_SUCCESS) + if (auto res = RegCreateKeyExW(scope, path.c_str(), 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_WRITE, nullptr, &key, nullptr); res != + ERROR_SUCCESS) { + Logger::error(L"apply of {}: RegCreateKeyExW failed: {}", toString(), get_last_error_or_default(res)); return false; } detail::on_exit closeKey{ [key] { RegCloseKey(key); } }; @@ -106,26 +149,35 @@ namespace registry DWORD valueType; valueToBuffer(value, buffer, valueSize, valueType); - return RegSetValueExW(key, - name.has_value() ? name->c_str() : nullptr, - 0, - valueType, - reinterpret_cast(buffer), - valueSize) == ERROR_SUCCESS; + if (auto res = RegSetValueExW(key, + name.has_value() ? name->c_str() : nullptr, + 0, + valueType, + reinterpret_cast(buffer), + valueSize); + res != ERROR_SUCCESS) + { + Logger::error(L"apply of {}: RegSetValueExW failed: {}", toString(), get_last_error_or_default(res)); + return false; + } + + return true; } bool unApply() const { HKEY key{}; - if (RegOpenKeyExW(scope, path.c_str(), 0, KEY_ALL_ACCESS, &key) != ERROR_SUCCESS) + if (auto res = RegOpenKeyExW(scope, path.c_str(), 0, KEY_ALL_ACCESS, &key); res != ERROR_SUCCESS) { + Logger::error(L"unApply of {}: RegOpenKeyExW failed: {}", toString(), get_last_error_or_default(res)); return false; } detail::on_exit closeKey{ [key] { RegCloseKey(key); } }; // delete the value itself - if (RegDeleteKeyValueW(scope, path.c_str(), name.has_value() ? name->c_str() : nullptr) != ERROR_SUCCESS) + if (auto res = RegDeleteKeyValueW(scope, path.c_str(), name.has_value() ? name->c_str() : nullptr); res != ERROR_SUCCESS) { + Logger::error(L"unApply of {}: RegDeleteKeyValueW failed: {}", toString(), get_last_error_or_default(res)); return false; } diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp index 33e182e43..fd5037cb5 100644 --- a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp @@ -19,8 +19,6 @@ namespace ABI } #endif -bool m_doNotActivateOnGameMode = true; - #pragma region Super_Sonar_Base_Code template @@ -54,11 +52,17 @@ protected: HWND m_hwnd; POINT m_sonarPos = ptNowhere; - static constexpr int SonarRadius = 100; - static constexpr int SonarZoomFactor = 9; - static constexpr DWORD FadeDuration = 500; - static constexpr int FinalAlphaNumerator = 1; - static constexpr int FinalAlphaDenominator = 2; + // Only consider double left control click if at least 100ms passed between the clicks, to avoid keyboards that might be sending rapid clicks. + // At actual check, time a fifth of the current double click setting might be used instead to take into account users who might have low values. + static const int MIN_DOUBLE_CLICK_TIME = 100; + + bool m_destroyed = false; + bool m_doNotActivateOnGameMode = true; + int m_sonarRadius = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_RADIUS; + int m_sonarZoomFactor = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_INITIAL_ZOOM; + DWORD m_fadeDuration = FIND_MY_MOUSE_DEFAULT_ANIMATION_DURATION_MS; + int m_finalAlphaNumerator = FIND_MY_MOUSE_DEFAULT_OVERLAY_OPACITY; + static constexpr int FinalAlphaDenominator = 100; winrt::DispatcherQueueController m_dispatcherQueueController{ nullptr }; private: @@ -137,7 +141,7 @@ bool SuperSonar::Initialize(HINSTANCE hinst) m_hwndOwner = CreateWindow(L"static", nullptr, WS_POPUP, 0, 0, 0, 0, nullptr, nullptr, hinst, nullptr); - DWORD exStyle = WS_EX_TRANSPARENT | WS_EX_LAYERED | Shim()->GetExtendedStyle(); + DWORD exStyle = WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_TOOLWINDOW | Shim()->GetExtendedStyle(); return CreateWindowExW(exStyle, className, windowTitle, WS_POPUP, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, m_hwndOwner, nullptr, hinst, this) != nullptr; } @@ -146,6 +150,7 @@ void SuperSonar::Terminate() { auto dispatcherQueue = m_dispatcherQueueController.DispatcherQueue(); bool enqueueSucceeded = dispatcherQueue.TryEnqueue([=]() { + m_destroyed = true; DestroyWindow(m_hwndOwner); }); if (!enqueueSucceeded) @@ -304,23 +309,40 @@ void SuperSonar::OnSonarKeyboardInput(RAWINPUT const& input) break; case SonarState::ControlUp1: - case SonarState::ControlUp2: if (pressed) { - m_sonarState = SonarState::ControlDown2; auto now = GetTickCount(); + auto doubleClickInterval = now - m_lastKeyTime; POINT ptCursor{}; + auto doubleClickTimeSetting = GetDoubleClickTime(); if (GetCursorPos(&ptCursor) && - now - m_lastKeyTime <= GetDoubleClickTime() && + doubleClickInterval >= min(MIN_DOUBLE_CLICK_TIME, doubleClickTimeSetting / 5) && + doubleClickInterval <= doubleClickTimeSetting && IsEqual(m_lastKeyPos, ptCursor)) { + m_sonarState = SonarState::ControlDown2; StartSonar(); } + else + { + m_sonarState = SonarState::ControlDown1; + m_lastKeyTime = GetTickCount(); + m_lastKeyPos = {}; + GetCursorPos(&m_lastKeyPos); + UpdateMouseSnooping(); + } + Logger::info("Detecting double left control click with {} ms interval.", doubleClickInterval); m_lastKeyTime = now; m_lastKeyPos = ptCursor; } break; - + case SonarState::ControlUp2: + // Also deactivate sonar with left control. + if (pressed) + { + StopSonar(); + } + break; case SonarState::ControlDown2: if (!pressed) { @@ -436,7 +458,7 @@ void SuperSonar::UpdateMouseSnooping() struct CompositionSpotlight : SuperSonar { static constexpr UINT WM_OPACITY_ANIMATION_COMPLETED = WM_APP; - static constexpr float SonarRadiusFloat = static_cast(SonarRadius); + float m_sonarRadiusFloat = static_cast(m_sonarRadius); DWORD GetExtendedStyle() { @@ -468,7 +490,7 @@ struct CompositionSpotlight : SuperSonar m_batch.Completed([hwnd = m_hwnd](auto&&, auto&&) { PostMessage(hwnd, WM_OPACITY_ANIMATION_COMPLETED, 0, 0); }); - m_root.Opacity(visible ? static_cast(FinalAlphaNumerator) / FinalAlphaDenominator : 0.0f); + m_root.Opacity(visible ? static_cast(m_finalAlphaNumerator) / FinalAlphaDenominator : 0.0f); if (visible) { ShowWindow(m_hwnd, SW_SHOWNOACTIVATE); @@ -510,38 +532,38 @@ private: layer.RelativeSizeAdjustment({ 1.0f, 1.0f }); // fill the parent m_root.Children().InsertAtTop(layer); - auto backdrop = m_compositor.CreateSpriteVisual(); - backdrop.RelativeSizeAdjustment({ 1.0f, 1.0f }); // fill the parent - backdrop.Brush(m_compositor.CreateColorBrush({ 255, 0, 0, 0 })); - layer.Children().InsertAtTop(backdrop); + m_backdrop = m_compositor.CreateSpriteVisual(); + m_backdrop.RelativeSizeAdjustment({ 1.0f, 1.0f }); // fill the parent + m_backdrop.Brush(m_compositor.CreateColorBrush(m_backgroundColor)); + layer.Children().InsertAtTop(m_backdrop); m_circleGeometry = m_compositor.CreateEllipseGeometry(); // radius set via expression animation - auto circleShape = m_compositor.CreateSpriteShape(m_circleGeometry); - circleShape.FillBrush(m_compositor.CreateColorBrush({ 255, 255, 255, 255 })); - circleShape.Offset({ SonarRadiusFloat * SonarZoomFactor, SonarRadiusFloat * SonarZoomFactor }); + m_circleShape = m_compositor.CreateSpriteShape(m_circleGeometry); + m_circleShape.FillBrush(m_compositor.CreateColorBrush(m_spotlightColor)); + m_circleShape.Offset({ m_sonarRadiusFloat * m_sonarZoomFactor, m_sonarRadiusFloat * m_sonarZoomFactor }); m_spotlight = m_compositor.CreateShapeVisual(); - m_spotlight.Size({ SonarRadiusFloat * 2 * SonarZoomFactor, SonarRadiusFloat * 2 * SonarZoomFactor }); + m_spotlight.Size({ m_sonarRadiusFloat * 2 * m_sonarZoomFactor, m_sonarRadiusFloat * 2 * m_sonarZoomFactor }); m_spotlight.AnchorPoint({ 0.5f, 0.5f }); - m_spotlight.Shapes().Append(circleShape); + m_spotlight.Shapes().Append(m_circleShape); layer.Children().InsertAtTop(m_spotlight); // Implicitly animate the alpha. - auto animation = m_compositor.CreateScalarKeyFrameAnimation(); - animation.Target(L"Opacity"); - animation.InsertExpressionKeyFrame(1.0f, L"this.FinalValue"); - animation.Duration(std::chrono::milliseconds{ FadeDuration }); + m_animation = m_compositor.CreateScalarKeyFrameAnimation(); + m_animation.Target(L"Opacity"); + m_animation.InsertExpressionKeyFrame(1.0f, L"this.FinalValue"); + m_animation.Duration(std::chrono::milliseconds{ m_fadeDuration }); auto collection = m_compositor.CreateImplicitAnimationCollection(); - collection.Insert(L"Opacity", animation); + collection.Insert(L"Opacity", m_animation); m_root.ImplicitAnimations(collection); // Radius of spotlight shrinks as opacity increases. - // At opacity zero, it is SonarRadius * SonarZoomFactor. - // At maximum opacity, it is SonarRadius. + // At opacity zero, it is m_sonarRadius * SonarZoomFactor. + // At maximum opacity, it is m_sonarRadius. auto radiusExpression = m_compositor.CreateExpressionAnimation(); radiusExpression.SetReferenceParameter(L"Root", m_root); wchar_t expressionText[256]; - winrt::check_hresult(StringCchPrintfW(expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity * %d / %d)", SonarRadius * SonarZoomFactor, SonarRadius * SonarZoomFactor, SonarRadius, SonarRadius, FinalAlphaDenominator, FinalAlphaNumerator)); + winrt::check_hresult(StringCchPrintfW(expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity * %d / %d)", m_sonarRadius * m_sonarZoomFactor, m_sonarRadius * m_sonarZoomFactor, m_sonarRadius, m_sonarRadius, FinalAlphaDenominator, m_finalAlphaNumerator)); radiusExpression.Expression(expressionText); m_circleGeometry.StartAnimation(L"Radius", radiusExpression); @@ -560,6 +582,62 @@ private: } } +public: + void ApplySettings(const FindMyMouseSettings& settings, bool applyToRuntimeObjects) { + if (!applyToRuntimeObjects) + { + // Runtime objects not created yet. Just update fields. + m_sonarRadius = settings.spotlightRadius; + m_sonarRadiusFloat = static_cast(m_sonarRadius); + m_backgroundColor = settings.backgroundColor; + m_spotlightColor = settings.spotlightColor; + m_doNotActivateOnGameMode = settings.doNotActivateOnGameMode; + m_fadeDuration = settings.animationDurationMs > 0 ? settings.animationDurationMs : 1; + m_finalAlphaNumerator = settings.overlayOpacity; + m_sonarZoomFactor = settings.spotlightInitialZoom; + } + else + { + // Runtime objects already created. Should update in the owner thread. + auto dispatcherQueue = m_dispatcherQueueController.DispatcherQueue(); + FindMyMouseSettings localSettings = settings; + bool enqueueSucceeded = dispatcherQueue.TryEnqueue([=]() { + if (!m_destroyed) + { + // Runtime objects not created yet. Just update fields. + m_sonarRadius = localSettings.spotlightRadius; + m_sonarRadiusFloat = static_cast(m_sonarRadius); + m_backgroundColor = localSettings.backgroundColor; + m_spotlightColor = localSettings.spotlightColor; + m_doNotActivateOnGameMode = localSettings.doNotActivateOnGameMode; + m_fadeDuration = localSettings.animationDurationMs > 0 ? localSettings.animationDurationMs : 1; + m_finalAlphaNumerator = localSettings.overlayOpacity; + m_sonarZoomFactor = localSettings.spotlightInitialZoom; + + // Apply new settings to runtime composition objects. + m_backdrop.Brush().as().Color(m_backgroundColor); + m_circleShape.FillBrush().as().Color(m_spotlightColor); + m_circleShape.Offset({ m_sonarRadiusFloat * m_sonarZoomFactor, m_sonarRadiusFloat * m_sonarZoomFactor }); + m_spotlight.Size({ m_sonarRadiusFloat * 2 * m_sonarZoomFactor, m_sonarRadiusFloat * 2 * m_sonarZoomFactor }); + m_animation.Duration(std::chrono::milliseconds{ m_fadeDuration }); + m_circleGeometry.StopAnimation(L"Radius"); + + // Update animation + auto radiusExpression = m_compositor.CreateExpressionAnimation(); + radiusExpression.SetReferenceParameter(L"Root", m_root); + wchar_t expressionText[256]; + winrt::check_hresult(StringCchPrintfW(expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity * %d / %d)", m_sonarRadius * m_sonarZoomFactor, m_sonarRadius * m_sonarZoomFactor, m_sonarRadius, m_sonarRadius, FinalAlphaDenominator, m_finalAlphaNumerator)); + radiusExpression.Expression(expressionText); + m_circleGeometry.StartAnimation(L"Radius", radiusExpression); + } + }); + if (!enqueueSucceeded) + { + Logger::error("Couldn't enqueue message to update the sonar settings."); + } + } + } + private: winrt::Compositor m_compositor{ nullptr }; winrt::Desktop::DesktopWindowTarget m_target{ nullptr }; @@ -567,6 +645,11 @@ private: winrt::CompositionEllipseGeometry m_circleGeometry{ nullptr }; winrt::ShapeVisual m_spotlight{ nullptr }; winrt::CompositionCommitBatch m_batch{ nullptr }; + winrt::SpriteVisual m_backdrop{ nullptr }; + winrt::CompositionSpriteShape m_circleShape{ nullptr }; + winrt::Windows::UI::Color m_backgroundColor = FIND_MY_MOUSE_DEFAULT_BACKGROUND_COLOR; + winrt::Windows::UI::Color m_spotlightColor = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_COLOR; + winrt::ScalarKeyFrameAnimation m_animation{ nullptr }; }; template @@ -610,7 +693,7 @@ struct GdiSonar : SuperSonar void OnFadeTimer() { auto now = GetTickCount(); - auto step = (int)((now - m_fadeStart) * MaxAlpha / this->FadeDuration); + auto step = (int)((now - m_fadeStart) * MaxAlpha / this->m_fadeDuration); this->Shim()->InvalidateSonar(); if (m_alpha < m_alphaTarget) @@ -645,13 +728,13 @@ protected: int CurrentSonarRadius() { int range = MaxAlpha - m_alpha; - int radius = this->SonarRadius + this->SonarRadius * range * (this->SonarZoomFactor - 1) / MaxAlpha; + int radius = this->m_sonarRadius + this->m_sonarRadius * range * (this->m_sonarZoomFactor - 1) / MaxAlpha; return radius; } private: static constexpr DWORD FadeFramePeriod = 10; - static constexpr int MaxAlpha = SuperSonar::FinalAlphaNumerator * 255 / SuperSonar::FinalAlphaDenominator; + int MaxAlpha = SuperSonar::m_finalAlphaNumerator * 255 / SuperSonar::FinalAlphaDenominator; static constexpr DWORD TIMER_ID_FADE = 101; private: @@ -771,6 +854,14 @@ struct GdiCrosshairs : GdiSonar #pragma region Super_Sonar_API CompositionSpotlight* m_sonar = nullptr; +void FindMyMouseApplySettings(const FindMyMouseSettings& settings) +{ + if (m_sonar != nullptr) + { + Logger::info("Applying settings."); + m_sonar->ApplySettings(settings, true); + } +} void FindMyMouseDisable() { @@ -786,13 +877,8 @@ bool FindMyMouseIsEnabled() return (m_sonar != nullptr); } -void FindMyMouseSetDoNotActivateOnGameMode(bool doNotActivate) -{ - m_doNotActivateOnGameMode = doNotActivate; -} - // Based on SuperSonar's original wWinMain. -int FindMyMouseMain(HINSTANCE hinst) +int FindMyMouseMain(HINSTANCE hinst, const FindMyMouseSettings& settings) { Logger::info("Starting a sonar instance."); if (m_sonar != nullptr) @@ -802,6 +888,7 @@ int FindMyMouseMain(HINSTANCE hinst) } CompositionSpotlight sonar; + sonar.ApplySettings(settings, false); if (!sonar.Initialize(hinst)) { Logger::error("Couldn't initialize a sonar instance."); diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.h b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.h index d5c4653d1..f5a81b019 100644 --- a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.h +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.h @@ -1,6 +1,26 @@ #pragma once #include "pch.h" -int FindMyMouseMain(HINSTANCE hinst); + +constexpr bool FIND_MY_MOUSE_DEFAULT_DO_NOT_ACTIVATE_ON_GAME_MODE = true; +const winrt::Windows::UI::Color FIND_MY_MOUSE_DEFAULT_BACKGROUND_COLOR = winrt::Windows::UI::ColorHelper::FromArgb(255, 0, 0, 0); +const winrt::Windows::UI::Color FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_COLOR = winrt::Windows::UI::ColorHelper::FromArgb(255, 255, 255, 255); +constexpr int FIND_MY_MOUSE_DEFAULT_OVERLAY_OPACITY = 50; +constexpr int FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_RADIUS = 100; +constexpr int FIND_MY_MOUSE_DEFAULT_ANIMATION_DURATION_MS = 500; +constexpr int FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_INITIAL_ZOOM = 9; + +struct FindMyMouseSettings +{ + bool doNotActivateOnGameMode = FIND_MY_MOUSE_DEFAULT_DO_NOT_ACTIVATE_ON_GAME_MODE; + winrt::Windows::UI::Color backgroundColor = FIND_MY_MOUSE_DEFAULT_BACKGROUND_COLOR; + winrt::Windows::UI::Color spotlightColor = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_COLOR; + int overlayOpacity = FIND_MY_MOUSE_DEFAULT_OVERLAY_OPACITY; + int spotlightRadius = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_RADIUS; + int animationDurationMs = FIND_MY_MOUSE_DEFAULT_ANIMATION_DURATION_MS; + int spotlightInitialZoom = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_INITIAL_ZOOM; +}; + +int FindMyMouseMain(HINSTANCE hinst, const FindMyMouseSettings& settings); void FindMyMouseDisable(); bool FindMyMouseIsEnabled(); -void FindMyMouseSetDoNotActivateOnGameMode(bool doNotActivate); +void FindMyMouseApplySettings(const FindMyMouseSettings& settings); diff --git a/src/modules/MouseUtils/FindMyMouse/dllmain.cpp b/src/modules/MouseUtils/FindMyMouse/dllmain.cpp index 495d74a4b..f771764a5 100644 --- a/src/modules/MouseUtils/FindMyMouse/dllmain.cpp +++ b/src/modules/MouseUtils/FindMyMouse/dllmain.cpp @@ -5,13 +5,19 @@ #include "FindMyMouse.h" #include #include - +#include namespace { const wchar_t JSON_KEY_PROPERTIES[] = L"properties"; const wchar_t JSON_KEY_VALUE[] = L"value"; const wchar_t JSON_KEY_DO_NOT_ACTIVATE_ON_GAME_MODE[] = L"do_not_activate_on_game_mode"; + const wchar_t JSON_KEY_BACKGROUND_COLOR[] = L"background_color"; + const wchar_t JSON_KEY_SPOTLIGHT_COLOR[] = L"spotlight_color"; + const wchar_t JSON_KEY_OVERLAY_OPACITY[] = L"overlay_opacity"; + const wchar_t JSON_KEY_SPOTLIGHT_RADIUS[] = L"spotlight_radius"; + const wchar_t JSON_KEY_ANIMATION_DURATION_MS[] = L"animation_duration_ms"; + const wchar_t JSON_KEY_SPOTLIGHT_INITIAL_ZOOM[] = L"spotlight_initial_zoom"; } extern "C" IMAGE_DOS_HEADER __ImageBase; @@ -48,6 +54,9 @@ private: // The PowerToy state. bool m_enabled = false; + // Find My Mouse specific settings + FindMyMouseSettings m_findMyMouseSettings; + // Load initial settings from the persisted values. void init_settings(); @@ -109,7 +118,7 @@ public: parse_settings(values); - values.save_to_settings_file(); + FindMyMouseApplySettings(m_findMyMouseSettings); } catch (std::exception&) { @@ -122,7 +131,7 @@ public: { m_enabled = true; Trace::EnableFindMyMouse(true); - std::thread([]() { FindMyMouseMain(m_hModule); }).detach(); + std::thread([=]() { FindMyMouseMain(m_hModule, m_findMyMouseSettings); }).detach(); } // Disable the powertoy @@ -158,25 +167,103 @@ void FindMyMouse::init_settings() void FindMyMouse::parse_settings(PowerToysSettings::PowerToyValues& settings) { - FindMyMouseSetDoNotActivateOnGameMode(true); - auto settingsObject = settings.get_raw_json(); + FindMyMouseSettings findMyMouseSettings; if (settingsObject.GetView().Size()) { try { auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_DO_NOT_ACTIVATE_ON_GAME_MODE); - FindMyMouseSetDoNotActivateOnGameMode((bool)jsonPropertiesObject.GetNamedBoolean(JSON_KEY_VALUE)); + findMyMouseSettings.doNotActivateOnGameMode = (bool)jsonPropertiesObject.GetNamedBoolean(JSON_KEY_VALUE); } catch (...) { Logger::warn("Failed to get 'do not activate on game mode' setting"); } + try + { + // Parse background color + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_BACKGROUND_COLOR); + auto backgroundColor = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE); + uint8_t r, g, b; + if (!checkValidRGB(backgroundColor, &r, &g, &b)) + { + Logger::error("Background color RGB value is invalid. Will use default value"); + } + else + { + findMyMouseSettings.backgroundColor = winrt::Windows::UI::ColorHelper::FromArgb(255, r, g, b); + } + } + catch (...) + { + Logger::warn("Failed to initialize background color from settings. Will use default value"); + } + try + { + // Parse spotlight color + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SPOTLIGHT_COLOR); + auto spotlightColor = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE); + uint8_t r, g, b; + if (!checkValidRGB(spotlightColor, &r, &g, &b)) + { + Logger::error("Spotlight color RGB value is invalid. Will use default value"); + } + else + { + findMyMouseSettings.spotlightColor = winrt::Windows::UI::ColorHelper::FromArgb(255, r, g, b); + } + } + catch (...) + { + Logger::warn("Failed to initialize spotlight color from settings. Will use default value"); + } + try + { + // Parse Overlay Opacity + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_OVERLAY_OPACITY); + findMyMouseSettings.overlayOpacity = (UINT)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE); + } + catch (...) + { + Logger::warn("Failed to initialize Overlay Opacity from settings. Will use default value"); + } + try + { + // Parse Spotlight Radius + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SPOTLIGHT_RADIUS); + findMyMouseSettings.spotlightRadius = (UINT)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE); + } + catch (...) + { + Logger::warn("Failed to initialize Spotlight Radius from settings. Will use default value"); + } + try + { + // Parse Animation Duration + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_ANIMATION_DURATION_MS); + findMyMouseSettings.animationDurationMs = (UINT)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE); + } + catch (...) + { + Logger::warn("Failed to initialize Animation Duration from settings. Will use default value"); + } + try + { + // Parse Spotlight Initial Zoom + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SPOTLIGHT_INITIAL_ZOOM); + findMyMouseSettings.spotlightInitialZoom = (UINT)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE); + } + catch (...) + { + Logger::warn("Failed to initialize Spotlight Initial Zoom from settings. Will use default value"); + } } else { Logger::info("Find My Mouse settings are empty"); } + m_findMyMouseSettings = findMyMouseSettings; } diff --git a/src/modules/MouseUtils/MouseHighlighter/Directory.Build.targets b/src/modules/MouseUtils/MouseHighlighter/Directory.Build.targets new file mode 100644 index 000000000..7e1362ac6 --- /dev/null +++ b/src/modules/MouseUtils/MouseHighlighter/Directory.Build.targets @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.base.rc b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.base.rc new file mode 100644 index 000000000..0bcdeca2e --- /dev/null +++ b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.base.rc @@ -0,0 +1,40 @@ +#include +#include "resource.h" +#include "../../../../common/version/version.h" + +#define APSTUDIO_READONLY_SYMBOLS +#include "winres.h" +#undef APSTUDIO_READONLY_SYMBOLS + +1 VERSIONINFO +FILEVERSION FILE_VERSION +PRODUCTVERSION PRODUCT_VERSION +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG +FILEFLAGS VS_FF_DEBUG +#else +FILEFLAGS 0x0L +#endif +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_DLL +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset + BEGIN + VALUE "CompanyName", COMPANY_NAME + VALUE "FileDescription", FILE_DESCRIPTION + VALUE "FileVersion", FILE_VERSION_STRING + VALUE "InternalName", INTERNAL_NAME + VALUE "LegalCopyright", COPYRIGHT_NOTE + VALUE "OriginalFilename", ORIGINAL_FILENAME + VALUE "ProductName", PRODUCT_NAME + VALUE "ProductVersion", PRODUCT_VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset + END +END diff --git a/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.cpp b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.cpp new file mode 100644 index 000000000..8b8c83c1f --- /dev/null +++ b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.cpp @@ -0,0 +1,443 @@ +// MouseHighlighter.cpp : Defines the entry point for the application. +// + +#include "pch.h" +#include "MouseHighlighter.h" +#include "trace.h" + +#ifdef COMPOSITION +namespace winrt +{ + using namespace winrt::Windows::System; + using namespace winrt::Windows::UI::Composition; +} + +namespace ABI +{ + using namespace ABI::Windows::System; + using namespace ABI::Windows::UI::Composition::Desktop; +} +#endif + +struct Highlighter +{ + bool MyRegisterClass(HINSTANCE hInstance); + static Highlighter* instance; + void Terminate(); + void SwitchActivationMode(); + void ApplySettings(MouseHighlighterSettings settings); + +private: + enum class MouseButton + { + Left, + Right + }; + + void DestroyHighlighter(); + static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) noexcept; + void StartDrawing(); + void StopDrawing(); + bool CreateHighlighter(); + void AddDrawingPoint(MouseButton button); + void UpdateDrawingPointPosition(MouseButton button); + void StartDrawingPointFading(MouseButton button); + void ClearDrawing(); + HHOOK m_mouseHook = NULL; + static LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) noexcept; + + static constexpr auto m_className = L"MouseHighlighter"; + static constexpr auto m_windowTitle = L"MouseHighlighter"; + HWND m_hwndOwner = NULL; + HWND m_hwnd = NULL; + HINSTANCE m_hinstance = NULL; + static constexpr DWORD WM_SWITCH_ACTIVATION_MODE = WM_APP; + + winrt::DispatcherQueueController m_dispatcherQueueController{ nullptr }; + winrt::Compositor m_compositor{ nullptr }; + winrt::Desktop::DesktopWindowTarget m_target{ nullptr }; + winrt::ContainerVisual m_root{ nullptr }; + winrt::LayerVisual m_layer{ nullptr }; + winrt::ShapeVisual m_shape{ nullptr }; + + winrt::CompositionSpriteShape m_leftPointer{ nullptr }; + winrt::CompositionSpriteShape m_rightPointer{ nullptr }; + bool m_leftButtonPressed = false; + bool m_rightButtonPressed = false; + + bool m_visible = false; + + // Possible configurable settings + float m_radius = MOUSE_HIGHLIGHTER_DEFAULT_RADIUS; + + int m_fadeDelay_ms = MOUSE_HIGHLIGHTER_DEFAULT_DELAY_MS; + int m_fadeDuration_ms = MOUSE_HIGHLIGHTER_DEFAULT_DURATION_MS; + + winrt::Windows::UI::Color m_leftClickColor = MOUSE_HIGHLIGHTER_DEFAULT_LEFT_BUTTON_COLOR; + winrt::Windows::UI::Color m_rightClickColor = MOUSE_HIGHLIGHTER_DEFAULT_RIGHT_BUTTON_COLOR; +}; + +Highlighter* Highlighter::instance = nullptr; + +bool Highlighter::CreateHighlighter() +{ + try + { + // We need a dispatcher queue. + DispatcherQueueOptions options = + { + sizeof(options), + DQTYPE_THREAD_CURRENT, + DQTAT_COM_ASTA, + }; + ABI::IDispatcherQueueController* controller; + winrt::check_hresult(CreateDispatcherQueueController(options, &controller)); + *winrt::put_abi(m_dispatcherQueueController) = controller; + + // Create the compositor for our window. + m_compositor = winrt::Compositor(); + ABI::IDesktopWindowTarget* target; + winrt::check_hresult(m_compositor.as()->CreateDesktopWindowTarget(m_hwnd, false, &target)); + *winrt::put_abi(m_target) = target; + + // Create visual root + m_root = m_compositor.CreateContainerVisual(); + m_root.RelativeSizeAdjustment({ 1.0f, 1.0f }); + m_target.Root(m_root); + + // Create the shapes container visual and add it to root. + m_shape = m_compositor.CreateShapeVisual(); + m_shape.RelativeSizeAdjustment({ 1.0f, 1.0f }); + m_root.Children().InsertAtTop(m_shape); + + return true; + } catch (...) + { + return false; + } +} + + +void Highlighter::AddDrawingPoint(MouseButton button) +{ + POINT pt; + + // Applies DPIs. + GetCursorPos(&pt); + + // Converts to client area of the Windows. + ScreenToClient(m_hwnd, &pt); + + // Create circle and add it. + auto circleGeometry = m_compositor.CreateEllipseGeometry(); + circleGeometry.Radius({ m_radius, m_radius }); + auto circleShape = m_compositor.CreateSpriteShape(circleGeometry); + circleShape.Offset({ (float)pt.x, (float)pt.y }); + if (button == MouseButton::Left) + { + circleShape.FillBrush(m_compositor.CreateColorBrush(m_leftClickColor)); + m_leftPointer = circleShape; + } + else + { + //right + circleShape.FillBrush(m_compositor.CreateColorBrush(m_rightClickColor)); + m_rightPointer = circleShape; + } + m_shape.Shapes().Append(circleShape); + + // TODO: We're leaking shapes for long drawing sessions. + // Perhaps add a task to the Dispatcher every X circles to clean up. + + // Get back on top in case other Window is now the topmost. + SetWindowPos(m_hwnd, HWND_TOPMOST, GetSystemMetrics(SM_XVIRTUALSCREEN), GetSystemMetrics(SM_YVIRTUALSCREEN), + GetSystemMetrics(SM_CXVIRTUALSCREEN), GetSystemMetrics(SM_CYVIRTUALSCREEN), 0); +} + +void Highlighter::UpdateDrawingPointPosition(MouseButton button) +{ + POINT pt; + + // Applies DPIs. + GetCursorPos(&pt); + + // Converts to client area of the Windows. + ScreenToClient(m_hwnd, &pt); + + if (button == MouseButton::Left) + { + m_leftPointer.Offset({ (float)pt.x, (float)pt.y }); + } + else + { + //right + m_rightPointer.Offset({ (float)pt.x, (float)pt.y }); + } +} +void Highlighter::StartDrawingPointFading(MouseButton button) +{ + winrt::Windows::UI::Composition::CompositionSpriteShape circleShape{ nullptr }; + if (button == MouseButton::Left) + { + circleShape = m_leftPointer; + } + else + { + //right + circleShape = m_rightPointer; + } + + auto brushColor = circleShape.FillBrush().as().Color(); + + // Animate opacity to simulate a fade away effect. + auto animation = m_compositor.CreateColorKeyFrameAnimation(); + animation.InsertKeyFrame(1, winrt::Windows::UI::ColorHelper::FromArgb(0, brushColor.R, brushColor.G, brushColor.B)); + using timeSpan = std::chrono::duration>; + std::chrono::milliseconds duration(m_fadeDuration_ms); + std::chrono::milliseconds delay(m_fadeDelay_ms); + animation.Duration(timeSpan(duration)); + animation.DelayTime(timeSpan(delay)); + + circleShape.FillBrush().StartAnimation(L"Color", animation); +} + + +void Highlighter::ClearDrawing() +{ + m_shape.Shapes().Clear(); +} + +LRESULT CALLBACK Highlighter::MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) noexcept +{ + if (nCode >= 0) + { + MSLLHOOKSTRUCT* hookData = (MSLLHOOKSTRUCT*)lParam; + switch (wParam) + { + case WM_LBUTTONDOWN: + instance->AddDrawingPoint(MouseButton::Left); + instance->m_leftButtonPressed = true; + break; + case WM_RBUTTONDOWN: + instance->AddDrawingPoint(MouseButton::Right); + instance->m_rightButtonPressed = true; + break; + case WM_MOUSEMOVE: + if (instance->m_leftButtonPressed) + { + instance->UpdateDrawingPointPosition(MouseButton::Left); + } + if (instance->m_rightButtonPressed) + { + instance->UpdateDrawingPointPosition(MouseButton::Right); + } + break; + case WM_LBUTTONUP: + if (instance->m_leftButtonPressed) + { + instance->StartDrawingPointFading(MouseButton::Left); + instance->m_leftButtonPressed = false; + } + break; + case WM_RBUTTONUP: + if (instance->m_rightButtonPressed) + { + instance->StartDrawingPointFading(MouseButton::Right); + instance->m_rightButtonPressed = false; + } + break; + default: + break; + } + } + return CallNextHookEx(0, nCode, wParam, lParam); +} + + +void Highlighter::StartDrawing() +{ + Logger::info("Starting draw mode."); + Trace::StartHighlightingSession(); + m_visible = true; + SetWindowPos(m_hwnd, HWND_TOPMOST, GetSystemMetrics(SM_XVIRTUALSCREEN), GetSystemMetrics(SM_YVIRTUALSCREEN), + GetSystemMetrics(SM_CXVIRTUALSCREEN), GetSystemMetrics(SM_CYVIRTUALSCREEN), 0); + ClearDrawing(); + ShowWindow(m_hwnd, SW_SHOWNOACTIVATE); + m_mouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, m_hinstance, 0); +} + +void Highlighter::StopDrawing() +{ + Logger::info("Stopping draw mode."); + m_visible = false; + m_leftButtonPressed = false; + m_rightButtonPressed = false; + m_leftPointer = nullptr; + m_rightPointer = nullptr; + ShowWindow(m_hwnd, SW_HIDE); + UnhookWindowsHookEx(m_mouseHook); + ClearDrawing(); + m_mouseHook = NULL; +} + +void Highlighter::SwitchActivationMode() +{ + PostMessage(m_hwnd, WM_SWITCH_ACTIVATION_MODE, 0, 0); +} + +void Highlighter::ApplySettings(MouseHighlighterSettings settings) { + m_radius = (float)settings.radius; + m_fadeDelay_ms = settings.fadeDelayMs; + m_fadeDuration_ms = settings.fadeDurationMs; + m_leftClickColor = settings.leftButtonColor; + m_rightClickColor = settings.rightButtonColor; +} + +void Highlighter::DestroyHighlighter() +{ + StopDrawing(); + PostQuitMessage(0); +} + +LRESULT CALLBACK Highlighter::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) noexcept +{ + switch (message) + { + case WM_NCCREATE: + instance->m_hwnd = hWnd; + return DefWindowProc(hWnd, message, wParam, lParam); + case WM_CREATE: + return instance->CreateHighlighter() ? 0 : -1; + case WM_NCHITTEST: + return HTTRANSPARENT; + case WM_SWITCH_ACTIVATION_MODE: + if (instance->m_visible) + { + instance->StopDrawing(); + } + else + { + instance->StartDrawing(); + } + break; + case WM_DESTROY: + instance->DestroyHighlighter(); + break; + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + return 0; +} + +bool Highlighter::MyRegisterClass(HINSTANCE hInstance) +{ + WNDCLASS wc{}; + + m_hinstance = hInstance; + + SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + if (!GetClassInfoW(hInstance, m_className, &wc)) + { + wc.lpfnWndProc = WndProc; + wc.hInstance = hInstance; + wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION); + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH); + wc.lpszClassName = m_className; + + if (!RegisterClassW(&wc)) + { + return false; + } + } + + m_hwndOwner = CreateWindow(L"static", nullptr, WS_POPUP, 0, 0, 0, 0, nullptr, nullptr, hInstance, nullptr); + + DWORD exStyle = WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_NOREDIRECTIONBITMAP | WS_EX_TOOLWINDOW; + return CreateWindowExW(exStyle, m_className, m_windowTitle, WS_POPUP, + CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, m_hwndOwner, nullptr, hInstance, nullptr) != nullptr; +} + +void Highlighter::Terminate() +{ + auto dispatcherQueue = m_dispatcherQueueController.DispatcherQueue(); + bool enqueueSucceeded = dispatcherQueue.TryEnqueue([=]() { + DestroyWindow(m_hwndOwner); + }); + if (!enqueueSucceeded) + { + Logger::error("Couldn't enqueue message to destroy the window."); + } +} + +#pragma region MouseHighlighter_API + +void MouseHighlighterApplySettings(MouseHighlighterSettings settings) +{ + if (Highlighter::instance != nullptr) + { + Logger::info("Applying settings."); + Highlighter::instance->ApplySettings(settings); + } +} + +void MouseHighlighterSwitch() +{ + if (Highlighter::instance != nullptr) + { + Logger::info("Switching activation mode."); + Highlighter::instance->SwitchActivationMode(); + } +} + +void MouseHighlighterDisable() +{ + if (Highlighter::instance != nullptr) + { + Logger::info("Terminating the highlighter instance."); + Highlighter::instance->Terminate(); + } +} + +bool MouseHighlighterIsEnabled() +{ + return (Highlighter::instance != nullptr); +} + +int MouseHighlighterMain(HINSTANCE hInstance, MouseHighlighterSettings settings) +{ + Logger::info("Starting a highlighter instance."); + if (Highlighter::instance != nullptr) + { + Logger::error("A highlighter instance was still working when trying to start a new one."); + return 0; + } + + // Perform application initialization: + Highlighter highlighter; + Highlighter::instance = &highlighter; + highlighter.ApplySettings(settings); + if (!highlighter.MyRegisterClass(hInstance)) + { + Logger::error("Couldn't initialize a highlighter instance."); + Highlighter::instance = nullptr; + return FALSE; + } + Logger::info("Initialized the highlighter instance."); + + MSG msg; + + // Main message loop: + while (GetMessage(&msg, nullptr, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + Logger::info("Mouse highlighter message loop ended."); + Highlighter::instance = nullptr; + + return (int)msg.wParam; +} + +#pragma endregion MouseHighlighter_API diff --git a/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.h b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.h new file mode 100644 index 000000000..eb1948bd0 --- /dev/null +++ b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.h @@ -0,0 +1,24 @@ +#pragma once +#include "pch.h" + +constexpr int MOUSE_HIGHLIGHTER_DEFAULT_OPACITY = 160; +const winrt::Windows::UI::Color MOUSE_HIGHLIGHTER_DEFAULT_LEFT_BUTTON_COLOR = winrt::Windows::UI::ColorHelper::FromArgb(MOUSE_HIGHLIGHTER_DEFAULT_OPACITY, 255, 255, 0); +const winrt::Windows::UI::Color MOUSE_HIGHLIGHTER_DEFAULT_RIGHT_BUTTON_COLOR = winrt::Windows::UI::ColorHelper::FromArgb(MOUSE_HIGHLIGHTER_DEFAULT_OPACITY, 0, 0, 255); +constexpr int MOUSE_HIGHLIGHTER_DEFAULT_RADIUS = 20; +constexpr int MOUSE_HIGHLIGHTER_DEFAULT_DELAY_MS = 500; +constexpr int MOUSE_HIGHLIGHTER_DEFAULT_DURATION_MS = 250; + +struct MouseHighlighterSettings +{ + winrt::Windows::UI::Color leftButtonColor = MOUSE_HIGHLIGHTER_DEFAULT_LEFT_BUTTON_COLOR; + winrt::Windows::UI::Color rightButtonColor = MOUSE_HIGHLIGHTER_DEFAULT_RIGHT_BUTTON_COLOR; + int radius = MOUSE_HIGHLIGHTER_DEFAULT_RADIUS; + int fadeDelayMs = MOUSE_HIGHLIGHTER_DEFAULT_DELAY_MS; + int fadeDurationMs = MOUSE_HIGHLIGHTER_DEFAULT_DURATION_MS; +}; + +int MouseHighlighterMain(HINSTANCE hinst, MouseHighlighterSettings settings); +void MouseHighlighterDisable(); +bool MouseHighlighterIsEnabled(); +void MouseHighlighterSwitch(); +void MouseHighlighterApplySettings(MouseHighlighterSettings settings); diff --git a/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj new file mode 100644 index 000000000..5c127f852 --- /dev/null +++ b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj @@ -0,0 +1,145 @@ + + + + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {782a61be-9d85-4081-b35c-1ccc9dcc1e88} + Win32Proj + MouseHighlighter + 10.0.18362.0 + MouseHighlighter + + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\modules\MouseUtils\ + + + false + $(SolutionDir)$(Platform)\$(Configuration)\modules\MouseUtils\ + + + + Level3 + Disabled + true + _DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + MultiThreadedDebug + stdcpplatest + + + Windows + true + $(OutDir)$(TargetName)$(TargetExt) + + + + + Level3 + MaxSpeed + true + true + true + NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + MultiThreaded + stdcpplatest + + + Windows + true + true + true + $(OutDir)$(TargetName)$(TargetExt) + + + + + $(SolutionDir)src\;$(SolutionDir)src\modules;$(SolutionDir)src\common\Telemetry;%(AdditionalIncludeDirectories) + + + + + Use + pch.h + + + + + + + + + + + + + + Create + + + + + + + + + + + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + + {6955446d-23f7-4023-9bb3-8657f904af99} + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj.filters b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj.filters new file mode 100644 index 000000000..ab12bcf6d --- /dev/null +++ b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj.filters @@ -0,0 +1,62 @@ + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Generated Files + + + Header Files + + + + + + Resource Files + + + Resource Files + + + + + {b012a2c8-5ccb-47fc-9429-4ebf877928e2} + cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx + + + {c8345550-9836-40a0-b473-0f4bf6129568} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {7934ee5b-8427-486d-9324-73b6bcf60eed} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {e1083d6b-b856-42a6-bd1f-1710e96170ba} + + + + + Generated Files + + + \ No newline at end of file diff --git a/src/modules/MouseUtils/MouseHighlighter/dllmain.cpp b/src/modules/MouseUtils/MouseHighlighter/dllmain.cpp new file mode 100644 index 000000000..d14ffd4f6 --- /dev/null +++ b/src/modules/MouseUtils/MouseHighlighter/dllmain.cpp @@ -0,0 +1,304 @@ +#include "pch.h" +#include +#include +#include "trace.h" +#include "MouseHighlighter.h" +#include "common/utils/color.h" + +namespace +{ + const wchar_t JSON_KEY_PROPERTIES[] = L"properties"; + const wchar_t JSON_KEY_VALUE[] = L"value"; + const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"activation_shortcut"; + const wchar_t JSON_KEY_LEFT_BUTTON_CLICK_COLOR[] = L"left_button_click_color"; + const wchar_t JSON_KEY_RIGHT_BUTTON_CLICK_COLOR[] = L"right_button_click_color"; + const wchar_t JSON_KEY_HIGHLIGHT_OPACITY[] = L"highlight_opacity"; + const wchar_t JSON_KEY_HIGHLIGHT_RADIUS[] = L"highlight_radius"; + const wchar_t JSON_KEY_HIGHLIGHT_FADE_DELAY_MS[] = L"highlight_fade_delay_ms"; + const wchar_t JSON_KEY_HIGHLIGHT_FADE_DURATION_MS[] = L"highlight_fade_duration_ms"; +} + +extern "C" IMAGE_DOS_HEADER __ImageBase; + +HMODULE m_hModule; + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) +{ + m_hModule = hModule; + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + Trace::RegisterProvider(); + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + case DLL_PROCESS_DETACH: + Trace::UnregisterProvider(); + break; + } + return TRUE; +} + +// The PowerToy name that will be shown in the settings. +const static wchar_t* MODULE_NAME = L"MouseHighlighter"; +// Add a description that will we shown in the module settings page. +const static wchar_t* MODULE_DESC = L""; + +// Implement the PowerToy Module Interface and all the required methods. +class MouseHighlighter : public PowertoyModuleIface +{ +private: + // The PowerToy state. + bool m_enabled = false; + + // Hotkey to invoke the module + HotkeyEx m_hotkey; + + // Mouse Highlighter specific settings + MouseHighlighterSettings m_highlightSettings; + +public: + // Constructor + MouseHighlighter() + { + LoggerHelpers::init_logger(MODULE_NAME, L"ModuleInterface", LogSettings::mouseHighlighterLoggerName); + init_settings(); + }; + + // Destroy the powertoy and free memory + virtual void destroy() override + { + delete this; + } + + // 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 + { + HINSTANCE hinstance = reinterpret_cast(&__ImageBase); + PowerToysSettings::Settings settings(hinstance, get_name()); + return settings.serialize_to_buffer(buffer, buffer_size); + } + + // Signal from the Settings editor to call a custom action. + // This can be used to spawn more complex editors. + virtual void call_custom_action(const wchar_t* action) override + { + } + + // Called by the runner to pass the updated settings values as a serialized JSON. + virtual void set_config(const wchar_t* config) override + { + try + { + // Parse the input JSON string. + PowerToysSettings::PowerToyValues values = + PowerToysSettings::PowerToyValues::from_json_string(config, get_key()); + + parse_settings(values); + + MouseHighlighterApplySettings(m_highlightSettings); + } + catch (std::exception&) + { + Logger::error("Invalid json when trying to parse Mouse Highlighter settings json."); + } + } + + // Enable the powertoy + virtual void enable() + { + m_enabled = true; + Trace::EnableMouseHighlighter(true); + std::thread([=]() { MouseHighlighterMain(m_hModule, m_highlightSettings); }).detach(); + } + + // Disable the powertoy + virtual void disable() + { + m_enabled = false; + Trace::EnableMouseHighlighter(false); + MouseHighlighterDisable(); + } + + // Returns if the powertoys is enabled + virtual bool is_enabled() override + { + return m_enabled; + } + + virtual std::optional GetHotkeyEx() override + { + return m_hotkey; + } + + virtual void OnHotkeyEx() override + { + MouseHighlighterSwitch(); + } + + // Load the settings file. + void init_settings() + { + try + { + // Load and parse the settings file for this PowerToy. + PowerToysSettings::PowerToyValues settings = + PowerToysSettings::PowerToyValues::load_from_settings_file(MouseHighlighter::get_key()); + parse_settings(settings); + } + catch (std::exception&) + { + Logger::error("Invalid json when trying to load the Mouse Highlighter settings json from file."); + } + } + + void parse_settings(PowerToysSettings::PowerToyValues& settings) + { + // TODO: refactor to use common/utils/json.h instead + auto settingsObject = settings.get_raw_json(); + MouseHighlighterSettings highlightSettings; + if (settingsObject.GetView().Size()) + { + try + { + // Parse HotKey + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_ACTIVATION_SHORTCUT); + auto hotkey = PowerToysSettings::HotkeyObject::from_json(jsonPropertiesObject); + m_hotkey = HotkeyEx(); + if (hotkey.win_pressed()) + { + m_hotkey.modifiersMask |= MOD_WIN; + } + + if (hotkey.ctrl_pressed()) + { + m_hotkey.modifiersMask |= MOD_CONTROL; + } + + if (hotkey.shift_pressed()) + { + m_hotkey.modifiersMask |= MOD_SHIFT; + } + + if (hotkey.alt_pressed()) + { + m_hotkey.modifiersMask |= MOD_ALT; + } + + m_hotkey.vkCode = hotkey.get_code(); + } + catch (...) + { + Logger::warn("Failed to initialize Mouse Highlighter activation shortcut"); + } + uint8_t opacity = MOUSE_HIGHLIGHTER_DEFAULT_OPACITY; + try + { + // Parse Opacity + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_HIGHLIGHT_OPACITY); + opacity = (uint8_t)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE); + } + catch (...) + { + Logger::warn("Failed to initialize Opacity from settings. Will use default value"); + } + try + { + // Parse left button click color + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_LEFT_BUTTON_CLICK_COLOR); + auto leftColor = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE); + uint8_t r, g, b; + if (!checkValidRGB(leftColor,&r,&g,&b)) + { + Logger::error("Left click color RGB value is invalid. Will use default value"); + } + else + { + highlightSettings.leftButtonColor = winrt::Windows::UI::ColorHelper::FromArgb(opacity, r, g, b); + } + } + catch (...) + { + Logger::warn("Failed to initialize left click color from settings. Will use default value"); + } + try + { + // Parse right button click color + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_RIGHT_BUTTON_CLICK_COLOR); + auto rightColor = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE); + uint8_t r, g, b; + if (!checkValidRGB(rightColor, &r, &g, &b)) + { + Logger::error("Right click color RGB value is invalid. Will use default value"); + } + else + { + highlightSettings.rightButtonColor = winrt::Windows::UI::ColorHelper::FromArgb(opacity, r, g, b); + } + } + catch (...) + { + Logger::warn("Failed to initialize right click color from settings. Will use default value"); + } + try + { + // Parse Radius + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_HIGHLIGHT_RADIUS); + highlightSettings.radius = (UINT)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE); + } + catch (...) + { + Logger::warn("Failed to initialize Radius from settings. Will use default value"); + } + try + { + // Parse Fade Delay + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_HIGHLIGHT_FADE_DELAY_MS); + highlightSettings.fadeDelayMs = (UINT)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE); + } + catch (...) + { + Logger::warn("Failed to initialize Fade Delay from settings. Will use default value"); + } + try + { + // Parse Fade Duration + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_HIGHLIGHT_FADE_DURATION_MS); + highlightSettings.fadeDurationMs = (UINT)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE); + } + catch (...) + { + Logger::warn("Failed to initialize Fade Duration from settings. Will use default value"); + } + } + else + { + Logger::info("Mouse Highlighter settings are empty"); + } + if (!m_hotkey.modifiersMask) + { + Logger::info("Mouse Highlighter is going to use default shortcut"); + m_hotkey.modifiersMask = MOD_SHIFT | MOD_WIN; + m_hotkey.vkCode = 0x48; // H key + } + m_highlightSettings = highlightSettings; + } +}; + +extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() +{ + return new MouseHighlighter(); +} \ No newline at end of file diff --git a/src/modules/MouseUtils/MouseHighlighter/packages.config b/src/modules/MouseUtils/MouseHighlighter/packages.config new file mode 100644 index 000000000..81f107b8b --- /dev/null +++ b/src/modules/MouseUtils/MouseHighlighter/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/modules/MouseUtils/MouseHighlighter/pch.cpp b/src/modules/MouseUtils/MouseHighlighter/pch.cpp new file mode 100644 index 000000000..1d9f38c57 --- /dev/null +++ b/src/modules/MouseUtils/MouseHighlighter/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/src/modules/MouseUtils/MouseHighlighter/pch.h b/src/modules/MouseUtils/MouseHighlighter/pch.h new file mode 100644 index 000000000..bfb4a4776 --- /dev/null +++ b/src/modules/MouseUtils/MouseHighlighter/pch.h @@ -0,0 +1,22 @@ +#pragma once + +#define COMPOSITION +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include + +#ifdef COMPOSITION +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include diff --git a/src/modules/MouseUtils/MouseHighlighter/resource.base.h b/src/modules/MouseUtils/MouseHighlighter/resource.base.h new file mode 100644 index 000000000..b49d62acd --- /dev/null +++ b/src/modules/MouseUtils/MouseHighlighter/resource.base.h @@ -0,0 +1,14 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by MouseHighlighter.rc + +////////////////////////////// +// Non-localizable + +#define FILE_DESCRIPTION "PowerToys MouseHighlighter" +#define INTERNAL_NAME "MouseHighlighter" +#define ORIGINAL_FILENAME "MouseHighlighter.dll" +#define IDS_KEYBOARDMANAGER_ICON 1001 + +// Non-localizable +////////////////////////////// diff --git a/src/modules/MouseUtils/MouseHighlighter/trace.cpp b/src/modules/MouseUtils/MouseHighlighter/trace.cpp new file mode 100644 index 000000000..feefa1774 --- /dev/null +++ b/src/modules/MouseUtils/MouseHighlighter/trace.cpp @@ -0,0 +1,40 @@ +#include "pch.h" +#include "trace.h" + +TRACELOGGING_DEFINE_PROVIDER( + g_hProvider, + "Microsoft.PowerToys", + // {38e8889b-9731-53f5-e901-e8a7c1753074} + (0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74), + TraceLoggingOptionProjectTelemetry()); + +void Trace::RegisterProvider() noexcept +{ + TraceLoggingRegister(g_hProvider); +} + +void Trace::UnregisterProvider() noexcept +{ + TraceLoggingUnregister(g_hProvider); +} + +// Log if the user has MouseHighlighter enabled or disabled +void Trace::EnableMouseHighlighter(const bool enabled) noexcept +{ + TraceLoggingWrite( + g_hProvider, + "MouseHighlighter_EnableMouseHighlighter", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), + TraceLoggingBoolean(enabled, "Enabled")); +} + +// Log that the user activated the module by starting a highlighting session +void Trace::StartHighlightingSession() noexcept +{ + TraceLoggingWrite( + g_hProvider, + "MouseHighlighter_StartHighlightingSession", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} diff --git a/src/modules/MouseUtils/MouseHighlighter/trace.h b/src/modules/MouseUtils/MouseHighlighter/trace.h new file mode 100644 index 000000000..01d660bbc --- /dev/null +++ b/src/modules/MouseUtils/MouseHighlighter/trace.h @@ -0,0 +1,14 @@ +#pragma once + +class Trace +{ +public: + static void RegisterProvider() noexcept; + static void UnregisterProvider() noexcept; + + // Log if the user has MouseHighlighter enabled or disabled + static void EnableMouseHighlighter(const bool enabled) noexcept; + + // Log that the user activated the module by starting a highlighting session + static void StartHighlightingSession() noexcept; +}; diff --git a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj index f9f67fcd7..d85a26b05 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj +++ b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj @@ -157,6 +157,9 @@ {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + {6955446d-23f7-4023-9bb3-8657f904af99} + {98537082-0fdb-40de-abd8-0dc5a4269bab} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/overlay_window.cpp b/src/modules/ShortcutGuide/ShortcutGuide/overlay_window.cpp index 908a67ad2..7cf670f01 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/overlay_window.cpp +++ b/src/modules/ShortcutGuide/ShortcutGuide/overlay_window.cpp @@ -463,27 +463,36 @@ void D2DOverlayWindow::init() { colors.update(); landscape.load(L"svgs\\overlay.svg", d2d_dc.get()) - .find_thumbnail(L"path-1") - .find_window_group(L"Group-1") - .recolor(0x000000, colors.start_color_menu); + .find_thumbnail(L"monitorRect") + .find_window_group(L"WindowControlsGroup") + .recolor(0x2582FB, colors.start_color_menu); portrait.load(L"svgs\\overlay_portrait.svg", d2d_dc.get()) - .find_thumbnail(L"path-1") - .find_window_group(L"Group-1") - .recolor(0x000000, colors.start_color_menu); + .find_thumbnail(L"monitorRect") + .find_window_group(L"WindowControlsGroup") + .recolor(0x2582FB, colors.start_color_menu); no_active.load(L"svgs\\no_active_window.svg", d2d_dc.get()); arrows.resize(10); for (unsigned i = 0; i < arrows.size(); ++i) { - arrows[i].load(L"svgs\\" + std::to_wstring((i + 1) % 10) + L".svg", d2d_dc.get()).recolor(0x000000, colors.start_color_menu); + arrows[i].load(L"svgs\\" + std::to_wstring((i + 1) % 10) + L".svg", d2d_dc.get()).recolor(0x2582FB, colors.start_color_menu); } light_mode = (theme_setting == Light) || (theme_setting == System && colors.light_mode); - if (!light_mode) + if (light_mode) { - landscape.recolor(0x222222, 0xDDDDDD); - portrait.recolor(0x222222, 0xDDDDDD); + landscape.recolor(0x2E17FC, 0x000000); + portrait.recolor(0x2E17FC, 0x000000); for (auto& arrow : arrows) { - arrow.recolor(0x222222, 0xDDDDDD); + arrow.recolor(0x222222, 0x000000); + } + } + else + { + landscape.recolor(0x2E17FC, 0xFFFFFF); + portrait.recolor(0x2E17FC, 0xFFFFFF); + for (auto& arrow : arrows) + { + arrow.recolor(0x222222, 0xFFFFFF); } } } @@ -496,13 +505,13 @@ void D2DOverlayWindow::resize() { // portrait is broke right now use_overlay = &landscape; no_active_scale = 0.3f; - font = 15.0f; + font = 12.0f; } else { use_overlay = &portrait; no_active_scale = 0.5f; - font = 16.0f; + font = 13.0f; } use_overlay->resize(0, 0, window_width, window_height, 0.8f); auto thumb_no_active_rect = use_overlay->get_thumbnail_rect_and_scale(0, 0, no_active.width(), no_active.height(), no_active_scale).rect; @@ -718,7 +727,7 @@ void D2DOverlayWindow::render(ID2D1DeviceContext5* d2d_dc) // render the monitors if (render_monitors) { - brushColor = D2D1::ColorF(colors.desktop_fill_color, miniature_shown ? current_anim_value : current_anim_value * 0.3f); + brushColor = D2D1::ColorF(colors.start_color_menu, miniature_shown ? current_anim_value * 0.9f : current_anim_value * 0.3f); brush = nullptr; winrt::check_hresult(d2d_dc->CreateSolidColorBrush(brushColor, brush.put())); for (auto& monitor : monitors) diff --git a/src/modules/ShortcutGuide/ShortcutGuide/svgs/0.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/0.svg index f70fdbf28..b797f8b7e 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/svgs/0.svg +++ b/src/modules/ShortcutGuide/ShortcutGuide/svgs/0.svg @@ -1,26 +1,21 @@ - - 0 - Created with Sketch. - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/svgs/1.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/1.svg index c8bcd2c60..6e1e3d28f 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/svgs/1.svg +++ b/src/modules/ShortcutGuide/ShortcutGuide/svgs/1.svg @@ -1,26 +1,21 @@ - - 1 - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/ShortcutGuide/ShortcutGuide/svgs/2.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/2.svg index d09aabe23..5183242c1 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/svgs/2.svg +++ b/src/modules/ShortcutGuide/ShortcutGuide/svgs/2.svg @@ -1,26 +1,21 @@ - - 2 - Created with Sketch. - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/svgs/3.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/3.svg index 97536f3e7..63ad68f06 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/svgs/3.svg +++ b/src/modules/ShortcutGuide/ShortcutGuide/svgs/3.svg @@ -1,26 +1,21 @@ - - 3 - Created with Sketch. - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/svgs/4.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/4.svg index 0f148c6c7..4f7e36f8b 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/svgs/4.svg +++ b/src/modules/ShortcutGuide/ShortcutGuide/svgs/4.svg @@ -1,26 +1,21 @@ - - 4 - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/ShortcutGuide/ShortcutGuide/svgs/5.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/5.svg index 7f8c38d2b..0fd52f658 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/svgs/5.svg +++ b/src/modules/ShortcutGuide/ShortcutGuide/svgs/5.svg @@ -1,26 +1,21 @@ - - 5 - Created with Sketch. - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/svgs/6.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/6.svg index 53bac58d2..00303cdb3 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/svgs/6.svg +++ b/src/modules/ShortcutGuide/ShortcutGuide/svgs/6.svg @@ -1,26 +1,21 @@ - - 6 - Created with Sketch. - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/svgs/7.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/7.svg index 85a7c8467..cce8aedba 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/svgs/7.svg +++ b/src/modules/ShortcutGuide/ShortcutGuide/svgs/7.svg @@ -1,26 +1,21 @@ - - 7 - Created with Sketch. - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/svgs/8.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/8.svg index 574675daa..57f4856b6 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/svgs/8.svg +++ b/src/modules/ShortcutGuide/ShortcutGuide/svgs/8.svg @@ -1,26 +1,21 @@ - - 8 - Created with Sketch. - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/svgs/9.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/9.svg index 57e73e21b..2e0f33c49 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/svgs/9.svg +++ b/src/modules/ShortcutGuide/ShortcutGuide/svgs/9.svg @@ -1,26 +1,21 @@ - - 9 - Created with Sketch. - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/svgs/no_active_window.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/no_active_window.svg index f6b549083..69d390bd0 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/svgs/no_active_window.svg +++ b/src/modules/ShortcutGuide/ShortcutGuide/svgs/no_active_window.svg @@ -1,20 +1,6 @@ - - Group 7 - Created with Sketch. - - - - - - - - - - - - - - + + + \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/svgs/overlay.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/overlay.svg index b4fb5815a..aeb5854f7 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/svgs/overlay.svg +++ b/src/modules/ShortcutGuide/ShortcutGuide/svgs/overlay.svg @@ -1,207 +1,200 @@ - - - - Group 5 - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/ShortcutGuide/ShortcutGuide/svgs/overlay_portrait.svg b/src/modules/ShortcutGuide/ShortcutGuide/svgs/overlay_portrait.svg index 1bf3f301a..fdc20dabb 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/svgs/overlay_portrait.svg +++ b/src/modules/ShortcutGuide/ShortcutGuide/svgs/overlay_portrait.svg @@ -1,209 +1,203 @@ - - Group 6 - Created with Sketch. - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj index 22aa591f1..a9ad4129b 100644 --- a/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj +++ b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj @@ -80,6 +80,9 @@ {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + {6955446d-23f7-4023-9bb3-8657f904af99} + diff --git a/src/modules/awake/Awake/Core/TrayHelper.cs b/src/modules/awake/Awake/Core/TrayHelper.cs index adba7b05f..1fe1b3c8e 100644 --- a/src/modules/awake/Awake/Core/TrayHelper.cs +++ b/src/modules/awake/Awake/Core/TrayHelper.cs @@ -177,7 +177,7 @@ namespace Awake.Core // No keep-awake menu item. CheckButtonToolStripMenuItem? passiveMenuItem = new CheckButtonToolStripMenuItem { - Text = "Off (Passive)", + Text = "Off (Keep using the selected power plan)", }; passiveMenuItem.Checked = mode == AwakeMode.PASSIVE; diff --git a/src/modules/colorPicker/ColorPickerUI/Controls/ColorPickerControl.xaml.cs b/src/modules/colorPicker/ColorPickerUI/Controls/ColorPickerControl.xaml.cs index 62600ca24..b9b7bc61e 100644 --- a/src/modules/colorPicker/ColorPickerUI/Controls/ColorPickerControl.xaml.cs +++ b/src/modules/colorPicker/ColorPickerUI/Controls/ColorPickerControl.xaml.cs @@ -291,7 +291,7 @@ namespace ColorPicker.Controls var newValue = (sender as TextBox).Text; // support hex with 3 and 6 characters - var reg = new Regex("^#([0-9A-F]{3}){1,2}$"); + var reg = new Regex("^#([0-9A-Fa-f]{3}){1,2}$"); if (!reg.IsMatch(newValue)) { diff --git a/src/modules/colorPicker/ColorPickerUI/Helpers/ColorHelper.cs b/src/modules/colorPicker/ColorPickerUI/Helpers/ColorHelper.cs index d242a9e56..7015201b2 100644 --- a/src/modules/colorPicker/ColorPickerUI/Helpers/ColorHelper.cs +++ b/src/modules/colorPicker/ColorPickerUI/Helpers/ColorHelper.cs @@ -44,6 +44,14 @@ namespace ColorPicker.Helpers return (cyan, magenta, yellow, blackKey); } + /// + /// Convert a given to a float color styling(0.1f, 0.1f, 0.1f) + /// + /// The to convert + /// The int / 255d for each value to get value between 0 and 1 + internal static (double red, double green, double blue) ConvertToDouble(Color color) + => (color.R / 255d, color.G / 255d, color.B / 255d); + /// /// Convert a given to a HSB color (hue, saturation, brightness) /// diff --git a/src/modules/colorPicker/ColorPickerUI/Helpers/ColorRepresentationHelper.cs b/src/modules/colorPicker/ColorPickerUI/Helpers/ColorRepresentationHelper.cs index e7bbbf1f9..1d9b45216 100644 --- a/src/modules/colorPicker/ColorPickerUI/Helpers/ColorRepresentationHelper.cs +++ b/src/modules/colorPicker/ColorPickerUI/Helpers/ColorRepresentationHelper.cs @@ -46,6 +46,8 @@ namespace ColorPicker.Helpers ColorRepresentationType.RGB => ColorToRGB(color), ColorRepresentationType.CIELAB => ColorToCIELAB(color), ColorRepresentationType.CIEXYZ => ColorToCIEXYZ(color), + ColorRepresentationType.VEC4 => ColorToFloat(color), + ColorRepresentationType.DecimalValue => ColorToDecimal(color), // Fall-back value, when "_userSettings.CopiedColorRepresentation.Value" is incorrect _ => ColorToHex(color), @@ -77,7 +79,7 @@ namespace ColorPicker.Helpers /// The see cref="Color"/> for the hexadecimal presentation /// A hexadecimal representation of a RGB color private static string ColorToHex(Color color) - => $"#{color.R.ToString("X2", CultureInfo.InvariantCulture)}" + => $"{color.R.ToString("X2", CultureInfo.InvariantCulture)}" + $"{color.G.ToString("X2", CultureInfo.InvariantCulture)}" + $"{color.B.ToString("X2", CultureInfo.InvariantCulture)}"; @@ -99,6 +101,29 @@ namespace ColorPicker.Helpers + $", {brightness.ToString(CultureInfo.InvariantCulture)}%)"; } + /// + /// Return a representation float color styling(0.1f, 0.1f, 0.1f) + /// + /// The to convert + /// a string value (0.1f, 0.1f, 0.1f) + private static string ColorToFloat(Color color) + { + var (red, green, blue) = ColorHelper.ConvertToDouble(color); + var precision = 2; + + return $"({Math.Round(red, precision).ToString("0.##", CultureInfo.InvariantCulture)}f, {Math.Round(green, precision).ToString("0.##", CultureInfo.InvariantCulture)}f, {Math.Round(blue, precision).ToString("0.##", CultureInfo.InvariantCulture)}f, 1f)"; + } + + /// + /// Return a representation decimal color value + /// + /// The to convert + /// a string value number + private static string ColorToDecimal(Color color) + { + return $"{color.R + (color.G * 256) + (color.B * 65536)}"; + } + /// /// Return a representation of a HSI color /// diff --git a/src/modules/colorPicker/ColorPickerUI/ViewModels/ColorEditorViewModel.cs b/src/modules/colorPicker/ColorPickerUI/ViewModels/ColorEditorViewModel.cs index 03966566b..7670582c3 100644 --- a/src/modules/colorPicker/ColorPickerUI/ViewModels/ColorEditorViewModel.cs +++ b/src/modules/colorPicker/ColorPickerUI/ViewModels/ColorEditorViewModel.cs @@ -148,7 +148,9 @@ namespace ColorPicker.ViewModels new ColorFormatModel() { FormatName = ColorRepresentationType.HEX.ToString(), - Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.HEX); }, +#pragma warning disable CA1304 // Specify CultureInfo + Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.HEX).ToLower(); }, +#pragma warning restore CA1304 // Specify CultureInfo }); _allColorRepresentations.Add( @@ -214,6 +216,18 @@ namespace ColorPicker.ViewModels FormatName = ColorRepresentationType.CIEXYZ.ToString(), Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.CIEXYZ); }, }); + _allColorRepresentations.Add( + new ColorFormatModel() + { + FormatName = ColorRepresentationType.VEC4.ToString(), + Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.VEC4); }, + }); + _allColorRepresentations.Add( + new ColorFormatModel() + { + FormatName = "Decimal", + Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.DecimalValue); }, + }); _userSettings.VisibleColorFormats.CollectionChanged += VisibleColorFormats_CollectionChanged; diff --git a/src/modules/colorPicker/ColorPickerUI/Views/ColorEditorView.xaml b/src/modules/colorPicker/ColorPickerUI/Views/ColorEditorView.xaml index d0ec1504b..95a121d4b 100644 --- a/src/modules/colorPicker/ColorPickerUI/Views/ColorEditorView.xaml +++ b/src/modules/colorPicker/ColorPickerUI/Views/ColorEditorView.xaml @@ -24,6 +24,7 @@ Grid.Row="1" Padding="0" TabIndex="2" + KeyboardNavigation.DirectionalNavigation="Contained" Width="64" AutomationProperties.Name="{x:Static p:Resources.Color_History}" HorizontalAlignment="Center" @@ -68,6 +69,7 @@ Height="32" Background="Transparent" VerticalAlignment="Top" + KeyboardNavigation.DirectionalNavigation="Contained" IsHitTestVisible="True"> @@ -118,8 +120,9 @@ @@ -145,6 +148,7 @@ Visibility="{Binding ColorsHistory.Count, Converter={StaticResource numberToVisibilityConverter}}" IsTabStop="True" TabIndex="2" + KeyboardNavigation.DirectionalNavigation="Contained" SelectedColor="{Binding SelectedColor}" SelectedColorChangedCommand="{Binding SelectedColorChangedCommand}" Grid.Column="1" diff --git a/src/modules/colorPicker/UnitTest-ColorPickerUI/Helpers/ColorRepresentationHelperTest.cs b/src/modules/colorPicker/UnitTest-ColorPickerUI/Helpers/ColorRepresentationHelperTest.cs index 4030de319..02ec6c212 100644 --- a/src/modules/colorPicker/UnitTest-ColorPickerUI/Helpers/ColorRepresentationHelperTest.cs +++ b/src/modules/colorPicker/UnitTest-ColorPickerUI/Helpers/ColorRepresentationHelperTest.cs @@ -14,7 +14,7 @@ namespace Microsoft.ColorPicker.UnitTests { [TestMethod] [DataRow(ColorRepresentationType.CMYK, "cmyk(0%, 0%, 0%, 100%)")] - [DataRow(ColorRepresentationType.HEX, "#000000")] + [DataRow(ColorRepresentationType.HEX, "000000")] [DataRow(ColorRepresentationType.NCol, "R0, 0%, 100%")] [DataRow(ColorRepresentationType.HSB, "hsb(0, 0%, 0%)")] [DataRow(ColorRepresentationType.HSI, "hsi(0, 0%, 0%)")] @@ -24,6 +24,8 @@ namespace Microsoft.ColorPicker.UnitTests [DataRow(ColorRepresentationType.RGB, "rgb(0, 0, 0)")] [DataRow(ColorRepresentationType.CIELAB, "CIELab(0, 0, 0)")] [DataRow(ColorRepresentationType.CIEXYZ, "xyz(0, 0, 0)")] + [DataRow(ColorRepresentationType.VEC4, "(0f, 0f, 0f, 1f)")] + [DataRow(ColorRepresentationType.DecimalValue, "0")] public void GetStringRepresentationTest(ColorRepresentationType type, string expected) { diff --git a/src/modules/fancyzones/FancyZones/FancyZones.vcxproj b/src/modules/fancyzones/FancyZones/FancyZones.vcxproj index 40b872f21..fb8f7d7a6 100644 --- a/src/modules/fancyzones/FancyZones/FancyZones.vcxproj +++ b/src/modules/fancyzones/FancyZones/FancyZones.vcxproj @@ -166,6 +166,9 @@ {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + {6955446d-23f7-4023-9bb3-8657f904af99} + {f9c68edf-ac74-4b77-9af1-005d9c9f6a99} diff --git a/src/modules/fancyzones/FancyZones/main.cpp b/src/modules/fancyzones/FancyZones/main.cpp index 34708f11b..c04bc347b 100644 --- a/src/modules/fancyzones/FancyZones/main.cpp +++ b/src/modules/fancyzones/FancyZones/main.cpp @@ -25,9 +25,8 @@ const std::wstring instanceMutexName = L"Local\\PowerToys_FancyZones_InstanceMut int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ PWSTR lpCmdLine, _In_ int nCmdShow) { winrt::init_apartment(); - InitUnhandledExceptionHandler_x64(); - LoggerHelpers::init_logger(moduleName, internalPath, LogSettings::fancyZonesLoggerName); + InitUnhandledExceptionHandler_x64(); auto mutex = CreateMutex(nullptr, true, instanceMutexName.c_str()); if (mutex == nullptr) diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp index cf4df48a5..d16e97249 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -25,9 +26,9 @@ #include "VirtualDesktop.h" #include "MonitorWorkAreaHandler.h" #include "util.h" -#include "CallTracer.h" #include +#include enum class DisplayChangeType { @@ -81,6 +82,13 @@ public: { monitor = NULL; } + + // If accent color or theme is changed need to update colors for zones + if (m_settings->GetSettings()->systemTheme && GetSystemTheme()) + { + m_workAreaHandler.UpdateZoneColors(GetZoneColors()); + } + m_windowMoveHandler.MoveSizeStart(window, monitor, ptScreen, m_workAreaHandler.GetWorkAreasByDesktopId(m_currentDesktopId)); } @@ -140,25 +148,27 @@ public: LRESULT WndProc(HWND, UINT, WPARAM, LPARAM) noexcept; void OnDisplayChange(DisplayChangeType changeType) noexcept; - void AddZoneWindow(HMONITOR monitor, const std::wstring& deviceId) noexcept; + void AddWorkArea(HMONITOR monitor, const std::wstring& deviceId) noexcept; protected: static LRESULT CALLBACK s_WndProc(HWND, UINT, WPARAM, LPARAM) noexcept; private: - void UpdateZoneWindows() noexcept; - void UpdateWindowsPositions() noexcept; + void UpdateWorkAreas() noexcept; + void UpdateWindowsPositions(bool suppressMove = false) noexcept; + void CycleTabs(bool reverse) noexcept; bool OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexcept; bool OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept; bool OnSnapHotkey(DWORD vkCode) noexcept; - bool ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr zoneWindow) noexcept; + bool ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr workArea) noexcept; void RegisterVirtualDesktopUpdates() noexcept; + void UpdateHotkey(int hotkeyId, const PowerToysSettings::HotkeyObject& hotkeyObject, bool enable) noexcept; void OnSettingsChanged() noexcept; std::pair, ZoneIndexSet> GetAppZoneHistoryInfo(HWND window, HMONITOR monitor, const std::unordered_map>& workAreaMap) noexcept; - void MoveWindowIntoZone(HWND window, winrt::com_ptr zoneWindow, const ZoneIndexSet& zoneIndexSet) noexcept; + void MoveWindowIntoZone(HWND window, winrt::com_ptr workArea, const ZoneIndexSet& zoneIndexSet) noexcept; bool MoveToAppLastZone(HWND window, HMONITOR active, HMONITOR primary) noexcept; void OnEditorExitEvent() noexcept; @@ -171,6 +181,7 @@ private: std::vector GetMonitorsSorted() noexcept; HMONITOR WorkAreaKeyFromWindow(HWND window) noexcept; + bool GetSystemTheme() const noexcept; ZoneColors GetZoneColors() const noexcept; const HINSTANCE m_hinstance{}; @@ -201,9 +212,19 @@ private: Exit, Terminate }; + + // IDs used to register hot keys (keyboard shortcuts). + enum class HotkeyId : int + { + Editor = 1, + NextTab = 2, + PrevTab = 3, + }; }; std::function FancyZones::disableModuleCallback = {}; +COLORREF currentAccentColor; +COLORREF currentBackgroundColor; // IFancyZones IFACEMETHODIMP_(void) @@ -224,7 +245,12 @@ FancyZones::Run() noexcept return; } - RegisterHotKey(m_window, 1, m_settings->GetSettings()->editorHotkey.get_modifiers(), m_settings->GetSettings()->editorHotkey.get_code()); + RegisterHotKey(m_window, static_cast(HotkeyId::Editor), m_settings->GetSettings()->editorHotkey.get_modifiers(), m_settings->GetSettings()->editorHotkey.get_code()); + if (m_settings->GetSettings()->windowSwitching) + { + RegisterHotKey(m_window, static_cast(HotkeyId::NextTab), m_settings->GetSettings()->nextTabHotkey.get_modifiers(), m_settings->GetSettings()->nextTabHotkey.get_code()); + RegisterHotKey(m_window, static_cast(HotkeyId::PrevTab), m_settings->GetSettings()->prevTabHotkey.get_modifiers(), m_settings->GetSettings()->prevTabHotkey.get_code()); + } m_virtualDesktop.Init(); @@ -298,14 +324,18 @@ std::pair, ZoneIndexSet> FancyZones::GetAppZoneHistory return std::pair, ZoneIndexSet>{ nullptr, {} }; } -void FancyZones::MoveWindowIntoZone(HWND window, winrt::com_ptr zoneWindow, const ZoneIndexSet& zoneIndexSet) noexcept +void FancyZones::MoveWindowIntoZone(HWND window, winrt::com_ptr workArea, const ZoneIndexSet& zoneIndexSet) noexcept { _TRACER_; auto& fancyZonesData = FancyZonesDataInstance(); - if (!fancyZonesData.IsAnotherWindowOfApplicationInstanceZoned(window, zoneWindow->UniqueId())) + if (!fancyZonesData.IsAnotherWindowOfApplicationInstanceZoned(window, workArea->UniqueId())) { - m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, zoneIndexSet, zoneWindow); - fancyZonesData.UpdateProcessIdToHandleMap(window, zoneWindow->UniqueId()); + if (workArea) + { + Trace::FancyZones::SnapNewWindowIntoZone(workArea->ZoneSet()); + } + m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, zoneIndexSet, workArea); + fancyZonesData.UpdateProcessIdToHandleMap(window, workArea->UniqueId()); } } @@ -450,7 +480,7 @@ FancyZones::OnKeyDown(PKBDLLHOOKSTRUCT info) noexcept digitPressed = info->vkCode - VK_NUMPAD0; } - bool dragging = m_windowMoveHandler.InMoveSize(); + bool dragging = m_windowMoveHandler.InDragging(); bool changeLayoutWhileNotDragging = !dragging && !shift && win && ctrl && alt && digitPressed != -1; bool changeLayoutWhileDragging = dragging && digitPressed != -1; @@ -639,10 +669,15 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa { case WM_HOTKEY: { - if (wparam == 1) + if (wparam == static_cast(HotkeyId::Editor)) { ToggleEditor(); } + else if (wparam == static_cast(HotkeyId::NextTab) || wparam == static_cast(HotkeyId::PrevTab)) + { + bool reverse = wparam == static_cast(HotkeyId::PrevTab); + CycleTabs(reverse); + } } break; @@ -714,7 +749,7 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa } else if (message == WM_PRIV_LOCATIONCHANGE) { - if (m_windowMoveHandler.InMoveSize()) + if (m_windowMoveHandler.InDragging()) { if (auto monitor = MonitorFromPoint(ptScreen, MONITOR_DEFAULTTONULL)) { @@ -785,7 +820,7 @@ void FancyZones::OnDisplayChange(DisplayChangeType changeType) noexcept } } - UpdateZoneWindows(); + UpdateWorkAreas(); if ((changeType == DisplayChangeType::WorkArea) || (changeType == DisplayChangeType::DisplayChange)) { @@ -796,7 +831,7 @@ void FancyZones::OnDisplayChange(DisplayChangeType changeType) noexcept } } -void FancyZones::AddZoneWindow(HMONITOR monitor, const std::wstring& deviceId) noexcept +void FancyZones::AddWorkArea(HMONITOR monitor, const std::wstring& deviceId) noexcept { _TRACER_; if (m_workAreaHandler.IsNewWorkArea(m_currentDesktopId, monitor)) @@ -862,11 +897,11 @@ LRESULT CALLBACK FancyZones::s_WndProc(HWND window, UINT message, WPARAM wparam, DefWindowProc(window, message, wparam, lparam); } -void FancyZones::UpdateZoneWindows() noexcept +void FancyZones::UpdateWorkAreas() noexcept { if (m_settings->GetSettings()->spanZonesAcrossMonitors) { - AddZoneWindow(nullptr, {}); + AddWorkArea(nullptr, {}); } else { @@ -887,7 +922,7 @@ void FancyZones::UpdateZoneWindows() noexcept FancyZones* fancyZones = params->fancyZones; std::wstring deviceId = FancyZonesUtils::GetDisplayDeviceId(mi.szDevice, displayDeviceIdxMap); - fancyZones->AddZoneWindow(monitor, deviceId); + fancyZones->AddWorkArea(monitor, deviceId); } return TRUE; }; @@ -897,19 +932,31 @@ void FancyZones::UpdateZoneWindows() noexcept } } -void FancyZones::UpdateWindowsPositions() noexcept +void FancyZones::UpdateWindowsPositions(bool suppressMove) noexcept { for (const auto [window, desktopId] : m_virtualDesktop.GetWindowsRelatedToDesktops()) { auto zoneIndexSet = GetZoneIndexSet(window); - auto zoneWindow = m_workAreaHandler.GetWorkArea(window, desktopId); - if (zoneWindow) + auto workArea = m_workAreaHandler.GetWorkArea(window, desktopId); + if (workArea) { - m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, zoneIndexSet, zoneWindow); + m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, zoneIndexSet, workArea, suppressMove); } } } +void FancyZones::CycleTabs(bool reverse) noexcept +{ + auto window = GetForegroundWindow(); + HMONITOR current = WorkAreaKeyFromWindow(window); + + auto workArea = m_workAreaHandler.GetWorkArea(m_currentDesktopId, current); + if (workArea) + { + workArea->CycleTabs(window, reverse); + } +} + bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexcept { _TRACER_; @@ -922,8 +969,10 @@ bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexce auto currMonitorInfo = std::find(std::begin(monitorInfo), std::end(monitorInfo), current); do { - if (m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */, m_workAreaHandler.GetWorkArea(m_currentDesktopId, *currMonitorInfo))) + auto workArea = m_workAreaHandler.GetWorkArea(m_currentDesktopId, *currMonitorInfo); + if (m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */, workArea)) { + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); return true; } // We iterated through all zones in current monitor zone layout, move on to next one (or previous depending on direction). @@ -947,20 +996,30 @@ bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexce } else { + auto workArea = m_workAreaHandler.GetWorkArea(m_currentDesktopId, current); // Single monitor environment, or combined multi-monitor environment. if (m_settings->GetSettings()->restoreSize) { - bool moved = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */, m_workAreaHandler.GetWorkArea(m_currentDesktopId, current)); + bool moved = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */, workArea); if (!moved) { FancyZonesUtils::RestoreWindowOrigin(window); FancyZonesUtils::RestoreWindowSize(window); } - return true; + else + { + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); + } + return moved; } else { - return m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, true /* cycle through zones */, m_workAreaHandler.GetWorkArea(m_currentDesktopId, current)); + bool moved = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, true /* cycle through zones */, workArea); + if (moved) + { + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); + } + return moved; } } @@ -999,7 +1058,7 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept auto workArea = m_workAreaHandler.GetWorkArea(m_currentDesktopId, monitor); if (workArea) { - auto zoneSet = workArea->ActiveZoneSet(); + auto zoneSet = workArea->ZoneSet(); if (zoneSet) { const auto zones = zoneSet->GetZones(); @@ -1032,8 +1091,9 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept if (chosenIdx < zoneRects.size()) { // Moving to another monitor succeeded - const auto& [trueZoneIdx, zoneWindow] = zoneRectsInfo[chosenIdx]; - m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }, zoneWindow); + const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx]; + m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }, workArea); + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); return true; } @@ -1046,7 +1106,7 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept auto workArea = m_workAreaHandler.GetWorkArea(m_currentDesktopId, current); if (workArea) { - auto zoneSet = workArea->ActiveZoneSet(); + auto zoneSet = workArea->ZoneSet(); if (zoneSet) { const auto zones = zoneSet->GetZones(); @@ -1076,8 +1136,9 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept if (chosenIdx < zoneRects.size()) { // Moving to another monitor succeeded - const auto& [trueZoneIdx, zoneWindow] = zoneRectsInfo[chosenIdx]; - m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }, zoneWindow); + const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx]; + m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }, workArea); + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); return true; } else @@ -1108,16 +1169,26 @@ bool FancyZones::OnSnapHotkey(DWORD vkCode) noexcept return false; } -bool FancyZones::ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr zoneWindow) noexcept +bool FancyZones::ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr workArea) noexcept { // Check whether Alt is used in the shortcut key combination if (GetAsyncKeyState(VK_MENU) & 0x8000) { - return m_windowMoveHandler.ExtendWindowByDirectionAndPosition(window, vkCode, zoneWindow); + bool result = m_windowMoveHandler.ExtendWindowByDirectionAndPosition(window, vkCode, workArea); + if (result) + { + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); + } + return result; } else { - return m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle, zoneWindow); + bool result = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle, workArea); + if (result) + { + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); + } + return result; } } @@ -1135,21 +1206,36 @@ void FancyZones::RegisterVirtualDesktopUpdates() noexcept FancyZonesDataInstance().SyncVirtualDesktops(m_currentDesktopId); } -void FancyZones::OnSettingsChanged() noexcept +void FancyZones::UpdateHotkey(int hotkeyId, const PowerToysSettings::HotkeyObject& hotkeyObject, bool enable) noexcept { - _TRACER_; - m_settings->ReloadSettings(); + UnregisterHotKey(m_window, hotkeyId); - // Update the hotkey - UnregisterHotKey(m_window, 1); - auto modifiers = m_settings->GetSettings()->editorHotkey.get_modifiers(); - auto code = m_settings->GetSettings()->editorHotkey.get_code(); - auto result = RegisterHotKey(m_window, 1, modifiers, code); + if (!enable) + { + return; + } + + auto modifiers = hotkeyObject.get_modifiers(); + auto code = hotkeyObject.get_code(); + auto result = RegisterHotKey(m_window, hotkeyId, modifiers, code); if (!result) { Logger::error(L"Failed to register hotkey: {}", get_last_error_or_default(GetLastError())); } +} + +void FancyZones::OnSettingsChanged() noexcept +{ + _TRACER_; + m_settings->ReloadSettings(); + + // Update the hotkeys + UpdateHotkey(static_cast(HotkeyId::Editor), m_settings->GetSettings()->editorHotkey, true); + + auto windowSwitching = m_settings->GetSettings()->windowSwitching; + UpdateHotkey(static_cast(HotkeyId::NextTab), m_settings->GetSettings()->nextTabHotkey, windowSwitching); + UpdateHotkey(static_cast(HotkeyId::PrevTab), m_settings->GetSettings()->prevTabHotkey, windowSwitching); // Needed if we toggled spanZonesAcrossMonitors m_workAreaHandler.Clear(); @@ -1177,10 +1263,9 @@ void FancyZones::UpdateZoneSets() noexcept { workArea->UpdateActiveZoneSet(); } - if (m_settings->GetSettings()->zoneSetChange_moveWindows) - { - UpdateWindowsPositions(); - } + + auto moveWindows = m_settings->GetSettings()->zoneSetChange_moveWindows; + UpdateWindowsPositions(!moveWindows); } bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept @@ -1190,8 +1275,8 @@ bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept { HMONITOR monitor = WorkAreaKeyFromWindow(window); - auto zoneWindow = m_workAreaHandler.GetWorkArea(m_currentDesktopId, monitor); - if (zoneWindow && zoneWindow->ActiveZoneSet() && zoneWindow->ActiveZoneSet()->LayoutType() != FancyZonesDataTypes::ZoneSetLayoutType::Blank) + auto workArea = m_workAreaHandler.GetWorkArea(m_currentDesktopId, monitor); + if (workArea && workArea->ZoneSet() && workArea->ZoneSet()->LayoutType() != FancyZonesDataTypes::ZoneSetLayoutType::Blank) { if (vkCode == VK_UP || vkCode == VK_DOWN) { @@ -1209,11 +1294,11 @@ bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept void FancyZones::ApplyQuickLayout(int key) noexcept { std::wstring uuid; - for (auto [zoneUuid, hotkey] : FancyZonesDataInstance().GetLayoutQuickKeys()) + for (auto [layoutUuid, hotkey] : FancyZonesDataInstance().GetLayoutQuickKeys()) { if (hotkey == key) { - uuid = zoneUuid; + uuid = layoutUuid; } } @@ -1262,7 +1347,7 @@ std::vector> FancyZones::GetRawMonitorData() noexcept const auto& activeWorkAreaMap = m_workAreaHandler.GetWorkAreasByDesktopId(m_currentDesktopId); for (const auto& [monitor, workArea] : activeWorkAreaMap) { - if (workArea->ActiveZoneSet() != nullptr) + if (workArea->ZoneSet() != nullptr) { MONITORINFOEX mi; mi.cbSize = sizeof(mi); @@ -1285,14 +1370,50 @@ HMONITOR FancyZones::WorkAreaKeyFromWindow(HWND window) noexcept } } +bool FancyZones::GetSystemTheme() const noexcept +{ + winrt::Windows::UI::ViewManagement::UISettings settings; + auto accentValue = settings.GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::Accent); + auto accentColor = RGB(accentValue.R, accentValue.G, accentValue.B); + + auto backgroundValue = settings.GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::Background); + auto backgroundColor = RGB(backgroundValue.R, backgroundValue.G, backgroundValue.B); + + if (currentAccentColor != accentColor || currentBackgroundColor != backgroundColor) + { + currentAccentColor = accentColor; + currentBackgroundColor = backgroundColor; + return true; + } + + return false; +} + ZoneColors FancyZones::GetZoneColors() const noexcept { - return ZoneColors { - .primaryColor = FancyZonesUtils::HexToRGB(m_settings->GetSettings()->zoneColor), - .borderColor = FancyZonesUtils::HexToRGB(m_settings->GetSettings()->zoneBorderColor), - .highlightColor = FancyZonesUtils::HexToRGB(m_settings->GetSettings()->zoneHighlightColor), - .highlightOpacity = m_settings->GetSettings()->zoneHighlightOpacity - }; + if (m_settings->GetSettings()->systemTheme) + { + GetSystemTheme(); + auto textColor = currentBackgroundColor == RGB(0, 0, 0) ? RGB(255, 255, 255) : RGB(0, 0, 0); + + return ZoneColors{ + .primaryColor = currentBackgroundColor, + .borderColor = currentAccentColor, + .highlightColor = currentAccentColor, + .textColor = textColor, + .highlightOpacity = m_settings->GetSettings()->zoneHighlightOpacity + }; + } + else + { + return ZoneColors{ + .primaryColor = FancyZonesUtils::HexToRGB(m_settings->GetSettings()->zoneColor), + .borderColor = FancyZonesUtils::HexToRGB(m_settings->GetSettings()->zoneBorderColor), + .highlightColor = FancyZonesUtils::HexToRGB(m_settings->GetSettings()->zoneHighlightColor), + .textColor = RGB(0, 0, 0), + .highlightOpacity = m_settings->GetSettings()->zoneHighlightOpacity + }; + } } winrt::com_ptr MakeFancyZones(HINSTANCE hinstance, diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesData.cpp index 72d195be9..277f4ae10 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData.cpp @@ -4,10 +4,10 @@ #include "JsonHelpers.h" #include "ZoneSet.h" #include "Settings.h" -#include "CallTracer.h" #include "GuidUtils.h" #include +#include #include #include #include diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.cpp index 8ab240b62..519d33c3f 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.cpp @@ -178,7 +178,7 @@ namespace FancyZonesDataTypes } /* - Refer to ZoneWindowUtils::GenerateUniqueId parts contain: + Refer to FancyZonesUtils::GenerateUniqueId parts contain: 1. monitor id [string] 2. width of device [int] 3. height of device [int] @@ -258,7 +258,7 @@ namespace FancyZonesDataTypes } /* - Refer to ZoneWindowUtils::GenerateUniqueId parts contain: + Refer to FancyZonesUtils::GenerateUniqueId parts contain: 1. monitor id [string] 2. width of device [int] 3. height of device [int] diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj index 0387a2004..2c770bbea 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj @@ -37,7 +37,6 @@ - @@ -65,7 +64,6 @@ - diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters index 2a1b8750e..e82f0f67a 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters @@ -78,9 +78,6 @@ Header Files - - Header Files - Header Files @@ -149,9 +146,6 @@ Source Files - - Source Files - Source Files diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h index 0a395f9ea..39942359d 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include @@ -8,6 +9,7 @@ namespace ZonedWindowProperties { const wchar_t PropertyMultipleZoneID[] = L"FancyZones_zones"; + const wchar_t PropertySortKeyWithinZone[] = L"FancyZones_TabSortKeyWithinZone"; const wchar_t PropertyRestoreSizeID[] = L"FancyZones_RestoreSize"; const wchar_t PropertyRestoreOriginID[] = L"FancyZones_RestoreOrigin"; @@ -44,3 +46,28 @@ inline void StampWindow(HWND window, Bitmask bitmask) noexcept memcpy(&rawData, data.data(), sizeof data); SetProp(window, ZonedWindowProperties::PropertyMultipleZoneID, rawData); } + +inline std::optional GetTabSortKeyWithinZone(HWND window) +{ + auto rawTabSortKeyWithinZone = ::GetPropW(window, ZonedWindowProperties::PropertySortKeyWithinZone); + if (rawTabSortKeyWithinZone == NULL) + { + return std::nullopt; + } + + auto tabSortKeyWithinZone = reinterpret_cast(rawTabSortKeyWithinZone) - 1; + return tabSortKeyWithinZone; +} + +inline void SetTabSortKeyWithinZone(HWND window, std::optional tabSortKeyWithinZone) +{ + if (!tabSortKeyWithinZone.has_value()) + { + ::RemovePropW(window, ZonedWindowProperties::PropertySortKeyWithinZone); + } + else + { + auto rawTabSortKeyWithinZone = reinterpret_cast(tabSortKeyWithinZone.value() + 1); + ::SetPropW(window, ZonedWindowProperties::PropertySortKeyWithinZone, rawTabSortKeyWithinZone); + } +} \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZonesLib/MonitorWorkAreaHandler.cpp b/src/modules/fancyzones/FancyZonesLib/MonitorWorkAreaHandler.cpp index 900d2664e..5582fdb0e 100644 --- a/src/modules/fancyzones/FancyZonesLib/MonitorWorkAreaHandler.cpp +++ b/src/modules/fancyzones/FancyZonesLib/MonitorWorkAreaHandler.cpp @@ -134,9 +134,9 @@ void MonitorWorkAreaHandler::UpdateZoneColors(const ZoneColors& colors) { for (const auto& workArea : workAreaMap) { - for (const auto& zoneWindow : workArea.second) + for (const auto& workAreaPtr : workArea.second) { - zoneWindow.second->SetZoneColors(colors); + workAreaPtr.second->SetZoneColors(colors); } } } @@ -145,9 +145,9 @@ void MonitorWorkAreaHandler::UpdateOverlappingAlgorithm(OverlappingZonesAlgorith { for (const auto& workArea : workAreaMap) { - for (const auto& zoneWindow : workArea.second) + for (const auto& workAreaPtr : workArea.second) { - zoneWindow.second->SetOverlappingZonesAlgorithm(overlappingAlgorithm); + workAreaPtr.second->SetOverlappingZonesAlgorithm(overlappingAlgorithm); } } } diff --git a/src/modules/fancyzones/FancyZonesLib/OnThreadExecutor.cpp b/src/modules/fancyzones/FancyZonesLib/OnThreadExecutor.cpp index 3e574a370..4f8536514 100644 --- a/src/modules/fancyzones/FancyZonesLib/OnThreadExecutor.cpp +++ b/src/modules/fancyzones/FancyZonesLib/OnThreadExecutor.cpp @@ -1,7 +1,8 @@ #include "pch.h" +#include + #include "on_thread_executor.h" -#include "CallTracer.h" OnThreadExecutor::OnThreadExecutor() : _shutdown_request{ false }, _worker_thread{ [this] { worker_thread(); } } diff --git a/src/modules/fancyzones/FancyZonesLib/Resources.resx b/src/modules/fancyzones/FancyZonesLib/Resources.resx index 44f77e9a4..2d073710b 100644 --- a/src/modules/fancyzones/FancyZonesLib/Resources.resx +++ b/src/modules/fancyzones/FancyZonesLib/Resources.resx @@ -196,6 +196,15 @@ Configure the zone editor hotkey + + Toggle shortcuts for switching between windows in the current zone + + + Shortcut for switching to the next window in the current zone + + + Shortcut for switching to the previous window in the current zone + To exclude an application from snapping to zones add its name here (one per line). Excluded apps will react to the Windows Snap regardless of all other settings. Windows refers to the Operating system @@ -210,7 +219,7 @@ We've detected an application running with administrator privileges. This will prevent certain interactions with these applications. - administrator is context of user account. + administrator is context of user account. Learn more @@ -254,4 +263,7 @@ Flash zones when switching layout + + Use system theme + \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZonesLib/Settings.cpp b/src/modules/fancyzones/FancyZonesLib/Settings.cpp index e9f95ea58..84ff97376 100644 --- a/src/modules/fancyzones/FancyZonesLib/Settings.cpp +++ b/src/modules/fancyzones/FancyZonesLib/Settings.cpp @@ -28,10 +28,14 @@ namespace NonLocalizable const wchar_t SpanZonesAcrossMonitorsID[] = L"fancyzones_span_zones_across_monitors"; const wchar_t MakeDraggedWindowTransparentID[] = L"fancyzones_makeDraggedWindowTransparent"; + const wchar_t SystemTheme[] = L"fancyzones_systemTheme"; const wchar_t ZoneColorID[] = L"fancyzones_zoneColor"; const wchar_t ZoneBorderColorID[] = L"fancyzones_zoneBorderColor"; const wchar_t ZoneHighlightColorID[] = L"fancyzones_zoneHighlightColor"; const wchar_t EditorHotkeyID[] = L"fancyzones_editor_hotkey"; + const wchar_t WindowSwitchingToggleID[] = L"fancyzones_windowSwitching"; + const wchar_t NextTabHotkeyID[] = L"fancyzones_nextTab_hotkey"; + const wchar_t PrevTabHotkeyID[] = L"fancyzones_prevTab_hotkey"; const wchar_t ExcludedAppsID[] = L"fancyzones_excluded_apps"; const wchar_t ZoneHighlightOpacityID[] = L"fancyzones_highlight_opacity"; @@ -77,7 +81,7 @@ private: PCWSTR name; bool* value; int resourceId; - } m_configBools[16] = { + } m_configBools[18] = { { NonLocalizable::ShiftDragID, &m_settings.shiftDrag, IDS_SETTING_DESCRIPTION_SHIFTDRAG }, { NonLocalizable::MouseSwitchID, &m_settings.mouseSwitch, IDS_SETTING_DESCRIPTION_MOUSESWITCH }, { NonLocalizable::OverrideSnapHotKeysID, &m_settings.overrideSnapHotkeys, IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS }, @@ -94,6 +98,8 @@ private: { NonLocalizable::ShowOnAllMonitorsID, &m_settings.showZonesOnAllMonitors, IDS_SETTING_DESCRIPTION_SHOW_FANCY_ZONES_ON_ALL_MONITORS }, { NonLocalizable::SpanZonesAcrossMonitorsID, &m_settings.spanZonesAcrossMonitors, IDS_SETTING_DESCRIPTION_SPAN_ZONES_ACROSS_MONITORS }, { NonLocalizable::MakeDraggedWindowTransparentID, &m_settings.makeDraggedWindowTransparent, IDS_SETTING_DESCRIPTION_MAKE_DRAGGED_WINDOW_TRANSPARENT }, + { NonLocalizable::WindowSwitchingToggleID, &m_settings.windowSwitching, IDS_SETTING_WINDOW_SWITCHING_TOGGLE_LABEL }, + { NonLocalizable::SystemTheme, &m_settings.systemTheme, IDS_SETTING_DESCRIPTION_SYSTEM_THEME }, }; }; @@ -116,6 +122,8 @@ FancyZonesSettings::GetConfig(_Out_ PWSTR buffer, _Out_ int* buffer_size) noexce IDS_SETTING_LAUNCH_EDITOR_BUTTON, IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION); settings.add_hotkey(NonLocalizable::EditorHotkeyID, IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL, m_settings.editorHotkey); + settings.add_hotkey(NonLocalizable::NextTabHotkeyID, IDS_SETTING_NEXT_TAB_HOTKEY_LABEL, m_settings.nextTabHotkey); + settings.add_hotkey(NonLocalizable::PrevTabHotkeyID, IDS_SETTING_PREV_TAB_HOTKEY_LABEL, m_settings.prevTabHotkey); for (auto const& setting : m_configBools) { @@ -182,6 +190,16 @@ void FancyZonesSettings::LoadSettings(PCWSTR config, bool fromFile) noexcept m_settings.editorHotkey = PowerToysSettings::HotkeyObject::from_json(*val); } + if (const auto val = values.get_json(NonLocalizable::NextTabHotkeyID)) + { + m_settings.nextTabHotkey = PowerToysSettings::HotkeyObject::from_json(*val); + } + + if (const auto val = values.get_json(NonLocalizable::PrevTabHotkeyID)) + { + m_settings.prevTabHotkey = PowerToysSettings::HotkeyObject::from_json(*val); + } + if (auto val = values.get_string_value(NonLocalizable::ExcludedAppsID)) { m_settings.excludedApps = std::move(*val); @@ -246,6 +264,8 @@ void FancyZonesSettings::SaveSettings() noexcept values.add_property(NonLocalizable::ZoneHighlightOpacityID, m_settings.zoneHighlightOpacity); values.add_property(NonLocalizable::OverlappingZonesAlgorithmID, (int)m_settings.overlappingZonesAlgorithm); values.add_property(NonLocalizable::EditorHotkeyID, m_settings.editorHotkey.get_json()); + values.add_property(NonLocalizable::NextTabHotkeyID, m_settings.nextTabHotkey.get_json()); + values.add_property(NonLocalizable::PrevTabHotkeyID, m_settings.prevTabHotkey.get_json()); values.add_property(NonLocalizable::ExcludedAppsID, m_settings.excludedApps); values.save_to_settings_file(); diff --git a/src/modules/fancyzones/FancyZonesLib/Settings.h b/src/modules/fancyzones/FancyZonesLib/Settings.h index dac2c992e..033f4e4c9 100644 --- a/src/modules/fancyzones/FancyZonesLib/Settings.h +++ b/src/modules/fancyzones/FancyZonesLib/Settings.h @@ -32,12 +32,16 @@ struct Settings bool showZonesOnAllMonitors = false; bool spanZonesAcrossMonitors = false; bool makeDraggedWindowTransparent = true; + bool systemTheme = true; std::wstring zoneColor = L"#AACDFF"; std::wstring zoneBorderColor = L"#FFFFFF"; std::wstring zoneHighlightColor = L"#008CFF"; int zoneHighlightOpacity = 50; OverlappingZonesAlgorithm overlappingZonesAlgorithm = OverlappingZonesAlgorithm::Smallest; PowerToysSettings::HotkeyObject editorHotkey = PowerToysSettings::HotkeyObject::from_settings(true, false, false, true, VK_OEM_3); + bool windowSwitching = true; + PowerToysSettings::HotkeyObject nextTabHotkey = PowerToysSettings::HotkeyObject::from_settings(true, false, false, false, VK_NEXT); + PowerToysSettings::HotkeyObject prevTabHotkey = PowerToysSettings::HotkeyObject::from_settings(true, false, false, false, VK_PRIOR); std::wstring excludedApps = L""; std::vector excludedAppsArray; }; diff --git a/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.cpp b/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.cpp index 77048d4e3..2581c15d5 100644 --- a/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.cpp +++ b/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.cpp @@ -59,24 +59,24 @@ WindowMoveHandler::WindowMoveHandler(const winrt::com_ptr& { } -void WindowMoveHandler::MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen, const std::unordered_map>& zoneWindowMap) noexcept +void WindowMoveHandler::MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen, const std::unordered_map>& workAreaMap) noexcept { if (!FancyZonesUtils::IsCandidateForZoning(window, m_settings->GetSettings()->excludedAppsArray) || WindowMoveHandlerUtils::IsCursorTypeIndicatingSizeEvent()) { return; } - m_moveSizeWindowInfo.hasNoVisibleOwner = FancyZonesUtils::HasNoVisibleOwner(window); - m_moveSizeWindowInfo.isStandardWindow = FancyZonesUtils::IsStandardWindow(window); - m_inMoveSize = true; + m_draggedWindowInfo.hasNoVisibleOwner = FancyZonesUtils::HasNoVisibleOwner(window); + m_draggedWindowInfo.isStandardWindow = FancyZonesUtils::IsStandardWindow(window); + m_inDragging = true; - auto iter = zoneWindowMap.find(monitor); - if (iter == end(zoneWindowMap)) + auto iter = workAreaMap.find(monitor); + if (iter == end(workAreaMap)) { return; } - m_windowMoveSize = window; + m_draggedWindow = window; if (m_settings->GetSettings()->mouseSwitch) { @@ -94,40 +94,51 @@ void WindowMoveHandler::MoveSizeStart(HWND window, HMONITOR monitor, POINT const if (m_dragEnabled) { - m_zoneWindowMoveSize = iter->second; - SetWindowTransparency(m_windowMoveSize); - m_zoneWindowMoveSize->MoveSizeEnter(m_windowMoveSize); + m_draggedWindowWorkArea = iter->second; + SetWindowTransparency(m_draggedWindow); + m_draggedWindowWorkArea->MoveSizeEnter(m_draggedWindow); if (m_settings->GetSettings()->showZonesOnAllMonitors) { - for (auto [keyMonitor, zoneWindow] : zoneWindowMap) + for (auto [keyMonitor, workArea] : workAreaMap) { - // Skip calling ShowZoneWindow for iter->second (m_zoneWindowMoveSize) since it + // Skip calling ShowZoneWindow for iter->second (m_draggedWindowWorkArea) since it // was already called in MoveSizeEnter - const bool moveSizeEnterCalled = zoneWindow == m_zoneWindowMoveSize; - if (zoneWindow && !moveSizeEnterCalled) + const bool moveSizeEnterCalled = workArea == m_draggedWindowWorkArea; + if (workArea && !moveSizeEnterCalled) { - zoneWindow->ShowZoneWindow(); + workArea->ShowZoneWindow(); } } } } - else if (m_zoneWindowMoveSize) + else if (m_draggedWindowWorkArea) { ResetWindowTransparency(); - m_zoneWindowMoveSize = nullptr; - for (auto [keyMonitor, zoneWindow] : zoneWindowMap) + m_draggedWindowWorkArea = nullptr; + for (auto [keyMonitor, workArea] : workAreaMap) { - if (zoneWindow) + if (workArea) { - zoneWindow->HideZoneWindow(); + workArea->HideZoneWindow(); } } } + + auto workArea = workAreaMap.find(monitor); + if (workArea != workAreaMap.end()) + { + const auto workAreaPtr = workArea->second; + const auto zoneSet = workAreaPtr->ZoneSet(); + if (zoneSet) + { + zoneSet->DismissWindow(window); + } + } } -void WindowMoveHandler::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, const std::unordered_map>& zoneWindowMap) noexcept +void WindowMoveHandler::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, const std::unordered_map>& workAreaMap) noexcept { - if (!m_inMoveSize) + if (!m_inDragging) { return; } @@ -135,44 +146,44 @@ void WindowMoveHandler::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, // This updates m_dragEnabled depending on if the shift key is being held down. UpdateDragState(); - if (m_zoneWindowMoveSize) + if (m_draggedWindowWorkArea) { // Update the WorkArea already handling move/size if (!m_dragEnabled) { // Drag got disabled, tell it to cancel and hide all windows - m_zoneWindowMoveSize = nullptr; + m_draggedWindowWorkArea = nullptr; ResetWindowTransparency(); - for (auto [keyMonitor, zoneWindow] : zoneWindowMap) + for (auto [keyMonitor, workArea] : workAreaMap) { - if (zoneWindow) + if (workArea) { - zoneWindow->HideZoneWindow(); + workArea->HideZoneWindow(); } } } else { - auto iter = zoneWindowMap.find(monitor); - if (iter != zoneWindowMap.end()) + auto iter = workAreaMap.find(monitor); + if (iter != workAreaMap.end()) { - if (iter->second != m_zoneWindowMoveSize) + if (iter->second != m_draggedWindowWorkArea) { // The drag has moved to a different monitor. - m_zoneWindowMoveSize->ClearSelectedZones(); + m_draggedWindowWorkArea->ClearSelectedZones(); if (!m_settings->GetSettings()->showZonesOnAllMonitors) { - m_zoneWindowMoveSize->HideZoneWindow(); + m_draggedWindowWorkArea->HideZoneWindow(); } - m_zoneWindowMoveSize = iter->second; - m_zoneWindowMoveSize->MoveSizeEnter(m_windowMoveSize); + m_draggedWindowWorkArea = iter->second; + m_draggedWindowWorkArea->MoveSizeEnter(m_draggedWindow); } - for (auto [keyMonitor, zoneWindow] : zoneWindowMap) + for (auto [keyMonitor, workArea] : workAreaMap) { - zoneWindow->MoveSizeUpdate(ptScreen, m_dragEnabled, m_ctrlKeyState.state()); + workArea->MoveSizeUpdate(ptScreen, m_dragEnabled, m_ctrlKeyState.state()); } } } @@ -180,21 +191,21 @@ void WindowMoveHandler::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, else if (m_dragEnabled) { // We'll get here if the user presses/releases shift while dragging. - // Restart the drag on the WorkArea that m_windowMoveSize is on - MoveSizeStart(m_windowMoveSize, monitor, ptScreen, zoneWindowMap); + // Restart the drag on the WorkArea that m_draggedWindow is on + MoveSizeStart(m_draggedWindow, monitor, ptScreen, workAreaMap); // m_dragEnabled could get set to false if we're moving an elevated window. // In that case do not proceed. if (m_dragEnabled) { - MoveSizeUpdate(monitor, ptScreen, zoneWindowMap); + MoveSizeUpdate(monitor, ptScreen, workAreaMap); } } } -void WindowMoveHandler::MoveSizeEnd(HWND window, POINT const& ptScreen, const std::unordered_map>& zoneWindowMap) noexcept +void WindowMoveHandler::MoveSizeEnd(HWND window, POINT const& ptScreen, const std::unordered_map>& workAreaMap) noexcept { - if (window != m_windowMoveSize) + if (window != m_draggedWindow) { return; } @@ -203,16 +214,16 @@ void WindowMoveHandler::MoveSizeEnd(HWND window, POINT const& ptScreen, const st m_shiftKeyState.disable(); m_ctrlKeyState.disable(); - if (m_zoneWindowMoveSize) + if (m_draggedWindowWorkArea) { - auto zoneWindow = std::move(m_zoneWindowMoveSize); + auto workArea = std::move(m_draggedWindowWorkArea); ResetWindowTransparency(); bool hasNoVisibleOwner = FancyZonesUtils::HasNoVisibleOwner(window); bool isStandardWindow = FancyZonesUtils::IsStandardWindow(window); if ((isStandardWindow == false && hasNoVisibleOwner == true && - m_moveSizeWindowInfo.isStandardWindow == true && m_moveSizeWindowInfo.hasNoVisibleOwner == true) || + m_draggedWindowInfo.isStandardWindow == true && m_draggedWindowInfo.hasNoVisibleOwner == true) || FancyZonesUtils::IsWindowMaximized(window)) { // Abort the zoning, this is a Chromium based tab that is merged back with an existing window @@ -220,7 +231,7 @@ void WindowMoveHandler::MoveSizeEnd(HWND window, POINT const& ptScreen, const st } else { - zoneWindow->MoveSizeEnd(m_windowMoveSize, ptScreen); + workArea->MoveSizeEnd(m_draggedWindow, ptScreen); } } else @@ -240,17 +251,17 @@ void WindowMoveHandler::MoveSizeEnd(HWND window, POINT const& ptScreen, const st auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL); if (monitor) { - auto zoneWindow = zoneWindowMap.find(monitor); - if (zoneWindow != zoneWindowMap.end()) + auto workArea = workAreaMap.find(monitor); + if (workArea != workAreaMap.end()) { - const auto zoneWindowPtr = zoneWindow->second; - const auto activeZoneSet = zoneWindowPtr->ActiveZoneSet(); - if (activeZoneSet) + const auto workAreaPtr = workArea->second; + const auto zoneSet = workAreaPtr->ZoneSet(); + if (zoneSet) { wil::unique_cotaskmem_string guidString; - if (SUCCEEDED_LOG(StringFromCLSID(activeZoneSet->Id(), &guidString))) + if (SUCCEEDED_LOG(StringFromCLSID(zoneSet->Id(), &guidString))) { - FancyZonesDataInstance().RemoveAppLastZone(window, zoneWindowPtr->UniqueId(), guidString.get()); + FancyZonesDataInstance().RemoveAppLastZone(window, workAreaPtr->UniqueId(), guidString.get()); } } } @@ -258,42 +269,42 @@ void WindowMoveHandler::MoveSizeEnd(HWND window, POINT const& ptScreen, const st ::RemoveProp(window, ZonedWindowProperties::PropertyMultipleZoneID); } - m_inMoveSize = false; + m_inDragging = false; m_dragEnabled = false; m_mouseState = false; - m_windowMoveSize = nullptr; + m_draggedWindow = nullptr; // Also, hide all windows (regardless of settings) - for (auto [keyMonitor, zoneWindow] : zoneWindowMap) + for (auto [keyMonitor, workArea] : workAreaMap) { - if (zoneWindow) + if (workArea) { - zoneWindow->HideZoneWindow(); + workArea->HideZoneWindow(); } } } -void WindowMoveHandler::MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, winrt::com_ptr zoneWindow) noexcept +void WindowMoveHandler::MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, winrt::com_ptr workArea, bool suppressMove) noexcept { - if (window != m_windowMoveSize) + if (window != m_draggedWindow) { - zoneWindow->MoveWindowIntoZoneByIndexSet(window, indexSet); + workArea->MoveWindowIntoZoneByIndexSet(window, indexSet, suppressMove); } } -bool WindowMoveHandler::MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr zoneWindow) noexcept +bool WindowMoveHandler::MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr workArea) noexcept { - return zoneWindow && zoneWindow->MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, cycle); + return workArea && workArea->MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, cycle); } -bool WindowMoveHandler::MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr zoneWindow) noexcept +bool WindowMoveHandler::MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr workArea) noexcept { - return zoneWindow && zoneWindow->MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle); + return workArea && workArea->MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle); } -bool WindowMoveHandler::ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode, winrt::com_ptr zoneWindow) noexcept +bool WindowMoveHandler::ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode, winrt::com_ptr workArea) noexcept { - return zoneWindow && zoneWindow->ExtendWindowByDirectionAndPosition(window, vkCode); + return workArea && workArea->ExtendWindowByDirectionAndPosition(window, vkCode); } void WindowMoveHandler::WarnIfElevationIsRequired(HWND window) noexcept diff --git a/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.h b/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.h index 48eeaae2f..b94ab0231 100644 --- a/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.h +++ b/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.h @@ -14,14 +14,14 @@ class WindowMoveHandler public: WindowMoveHandler(const winrt::com_ptr& settings, const std::function& keyUpdateCallback); - void MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen, const std::unordered_map>& zoneWindowMap) noexcept; - void MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, const std::unordered_map>& zoneWindowMap) noexcept; - void MoveSizeEnd(HWND window, POINT const& ptScreen, const std::unordered_map>& zoneWindowMap) noexcept; + void MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen, const std::unordered_map>& workAreaMap) noexcept; + void MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, const std::unordered_map>& workAreaMap) noexcept; + void MoveSizeEnd(HWND window, POINT const& ptScreen, const std::unordered_map>& workAreaMap) noexcept; - void MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, winrt::com_ptr zoneWindow) noexcept; - bool MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr zoneWindow) noexcept; - bool MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr zoneWindow) noexcept; - bool ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode, winrt::com_ptr zoneWindow) noexcept; + void MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, winrt::com_ptr workArea, bool suppressMove = false) noexcept; + bool MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr workArea) noexcept; + bool MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr workArea) noexcept; + bool ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode, winrt::com_ptr workArea) noexcept; inline void OnMouseDown() noexcept { @@ -34,9 +34,9 @@ public: return m_dragEnabled; } - inline bool InMoveSize() const noexcept + inline bool InDragging() const noexcept { - return m_inMoveSize; + return m_inDragging; } private: @@ -66,10 +66,10 @@ private: winrt::com_ptr m_settings{}; - HWND m_windowMoveSize{}; // The window that is being moved/sized - bool m_inMoveSize{}; // Whether or not a move/size operation is currently active - MoveSizeWindowInfo m_moveSizeWindowInfo; // MoveSizeWindowInfo of the window at the moment when dragging started - winrt::com_ptr m_zoneWindowMoveSize; // "Active" WorkArea, where the move/size is happening. Will update as drag moves between monitors. + bool m_inDragging{}; // Whether or not a move/size operation is currently active + HWND m_draggedWindow{}; // The window that is being moved/sized + MoveSizeWindowInfo m_draggedWindowInfo; // MoveSizeWindowInfo of the window at the moment when dragging started + winrt::com_ptr m_draggedWindowWorkArea; // "Active" WorkArea, where the move/size is happening. Will update as drag moves between monitors. bool m_dragEnabled{}; // True if we should be showing zone hints while dragging WindowTransparencyProperties m_windowTransparencyProperties; diff --git a/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp b/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp index 409819293..5d498f54e 100644 --- a/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp +++ b/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp @@ -1,6 +1,7 @@ #include "pch.h" #include "WorkArea.h" +#include #include #include "FancyZonesData.h" @@ -10,7 +11,6 @@ #include "util.h" #include "on_thread_executor.h" #include "Settings.h" -#include "CallTracer.h" #include #include @@ -117,7 +117,7 @@ public: IFACEMETHODIMP_(void) MoveWindowIntoZoneByIndex(HWND window, ZoneIndex index) noexcept; IFACEMETHODIMP_(void) - MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet) noexcept; + MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, bool suppressMove = false) noexcept; IFACEMETHODIMP_(bool) MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle) noexcept; IFACEMETHODIMP_(bool) @@ -129,7 +129,7 @@ public: IFACEMETHODIMP_(void) SaveWindowProcessToZoneIndex(HWND window) noexcept; IFACEMETHODIMP_(IZoneSet*) - ActiveZoneSet() const noexcept { return m_activeZoneSet.get(); } + ZoneSet() const noexcept { return m_zoneSet.get(); } IFACEMETHODIMP_(ZoneIndexSet) GetWindowZoneIndexes(HWND window) const noexcept; IFACEMETHODIMP_(void) @@ -139,6 +139,8 @@ public: IFACEMETHODIMP_(void) UpdateActiveZoneSet() noexcept; IFACEMETHODIMP_(void) + CycleTabs(HWND window, bool reverse) noexcept; + IFACEMETHODIMP_(void) ClearSelectedZones() noexcept; IFACEMETHODIMP_(void) FlashZones() noexcept; @@ -162,8 +164,7 @@ private: FancyZonesDataTypes::DeviceIdData m_uniqueId; HWND m_window{}; // Hidden tool window used to represent current monitor desktop work area. HWND m_windowMoveSize{}; - winrt::com_ptr m_activeZoneSet; - std::vector> m_zoneSets; + winrt::com_ptr m_zoneSet; ZoneIndexSet m_initialHighlightZone; ZoneIndexSet m_highlightZone; WPARAM m_keyLast{}; @@ -232,6 +233,7 @@ IFACEMETHODIMP WorkArea::MoveSizeEnter(HWND window) noexcept m_highlightZone = {}; m_initialHighlightZone = {}; ShowZoneWindow(); + Trace::WorkArea::MoveOrResizeStarted(m_zoneSet); return S_OK; } @@ -254,7 +256,7 @@ IFACEMETHODIMP WorkArea::MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, } else { - highlightZone = m_activeZoneSet->GetCombinedZoneRange(m_initialHighlightZone, highlightZone); + highlightZone = m_zoneSet->GetCombinedZoneRange(m_initialHighlightZone, highlightZone); } } else @@ -273,7 +275,7 @@ IFACEMETHODIMP WorkArea::MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, if (redraw) { - m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), m_highlightZone, m_zoneColors); + m_zoneWindowDrawing->DrawActiveZoneSet(m_zoneSet->GetZones(), m_highlightZone, m_zoneColors); } return S_OK; @@ -286,18 +288,18 @@ IFACEMETHODIMP WorkArea::MoveSizeEnd(HWND window, POINT const& ptScreen) noexcep return E_INVALIDARG; } - if (m_activeZoneSet) + if (m_zoneSet) { POINT ptClient = ptScreen; MapWindowPoints(nullptr, m_window, &ptClient, 1); - m_activeZoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, m_highlightZone); + m_zoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, m_highlightZone); if (FancyZonesUtils::HasNoVisibleOwner(window)) { SaveWindowProcessToZoneIndex(window); } } - Trace::WorkArea::MoveSizeEnd(m_activeZoneSet); + Trace::WorkArea::MoveOrResizeEnd(m_zoneSet); HideZoneWindow(); m_windowMoveSize = nullptr; @@ -311,20 +313,20 @@ WorkArea::MoveWindowIntoZoneByIndex(HWND window, ZoneIndex index) noexcept } IFACEMETHODIMP_(void) -WorkArea::MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet) noexcept +WorkArea::MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, bool suppressMove) noexcept { - if (m_activeZoneSet) + if (m_zoneSet) { - m_activeZoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, indexSet); + m_zoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, indexSet, suppressMove); } } IFACEMETHODIMP_(bool) WorkArea::MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle) noexcept { - if (m_activeZoneSet) + if (m_zoneSet) { - if (m_activeZoneSet->MoveWindowIntoZoneByDirectionAndIndex(window, m_window, vkCode, cycle)) + if (m_zoneSet->MoveWindowIntoZoneByDirectionAndIndex(window, m_window, vkCode, cycle)) { if (FancyZonesUtils::HasNoVisibleOwner(window)) { @@ -339,9 +341,9 @@ WorkArea::MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool IFACEMETHODIMP_(bool) WorkArea::MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle) noexcept { - if (m_activeZoneSet) + if (m_zoneSet) { - if (m_activeZoneSet->MoveWindowIntoZoneByDirectionAndPosition(window, m_window, vkCode, cycle)) + if (m_zoneSet->MoveWindowIntoZoneByDirectionAndPosition(window, m_window, vkCode, cycle)) { SaveWindowProcessToZoneIndex(window); return true; @@ -353,9 +355,9 @@ WorkArea::MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bo IFACEMETHODIMP_(bool) WorkArea::ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode) noexcept { - if (m_activeZoneSet) + if (m_zoneSet) { - if (m_activeZoneSet->ExtendWindowByDirectionAndPosition(window, m_window, vkCode)) + if (m_zoneSet->ExtendWindowByDirectionAndPosition(window, m_window, vkCode)) { SaveWindowProcessToZoneIndex(window); return true; @@ -367,13 +369,13 @@ WorkArea::ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode) noexcept IFACEMETHODIMP_(void) WorkArea::SaveWindowProcessToZoneIndex(HWND window) noexcept { - if (m_activeZoneSet) + if (m_zoneSet) { - auto zoneIndexSet = m_activeZoneSet->GetZoneIndexSetFromWindow(window); + auto zoneIndexSet = m_zoneSet->GetZoneIndexSetFromWindow(window); if (zoneIndexSet.size()) { OLECHAR* guidString; - if (StringFromCLSID(m_activeZoneSet->Id(), &guidString) == S_OK) + if (StringFromCLSID(m_zoneSet->Id(), &guidString) == S_OK) { FancyZonesDataInstance().SetAppLastZones(window, m_uniqueId, guidString, zoneIndexSet); } @@ -386,10 +388,10 @@ WorkArea::SaveWindowProcessToZoneIndex(HWND window) noexcept IFACEMETHODIMP_(ZoneIndexSet) WorkArea::GetWindowZoneIndexes(HWND window) const noexcept { - if (m_activeZoneSet) + if (m_zoneSet) { wil::unique_cotaskmem_string zoneSetId; - if (SUCCEEDED(StringFromCLSID(m_activeZoneSet->Id(), &zoneSetId))) + if (SUCCEEDED(StringFromCLSID(m_zoneSet->Id(), &zoneSetId))) { return FancyZonesDataInstance().GetAppLastZoneIndexSet(window, m_uniqueId, zoneSetId.get()); } @@ -403,7 +405,7 @@ WorkArea::ShowZoneWindow() noexcept if (m_window) { SetAsTopmostWindow(); - m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), m_highlightZone, m_zoneColors); + m_zoneWindowDrawing->DrawActiveZoneSet(m_zoneSet->GetZones(), m_highlightZone, m_zoneColors); m_zoneWindowDrawing->Show(); } } @@ -427,7 +429,16 @@ WorkArea::UpdateActiveZoneSet() noexcept if (m_window) { m_highlightZone.clear(); - m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), m_highlightZone, m_zoneColors); + m_zoneWindowDrawing->DrawActiveZoneSet(m_zoneSet->GetZones(), m_highlightZone, m_zoneColors); + } +} + +IFACEMETHODIMP_(void) +WorkArea::CycleTabs(HWND window, bool reverse) noexcept +{ + if (m_zoneSet) + { + m_zoneSet->CycleTabs(window, reverse); } } @@ -437,7 +448,7 @@ WorkArea::ClearSelectedZones() noexcept if (m_highlightZone.size()) { m_highlightZone.clear(); - m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), m_highlightZone, m_zoneColors); + m_zoneWindowDrawing->DrawActiveZoneSet(m_zoneSet->GetZones(), m_highlightZone, m_zoneColors); } } @@ -447,7 +458,7 @@ WorkArea::FlashZones() noexcept if (m_window) { SetAsTopmostWindow(); - m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), {}, m_zoneColors); + m_zoneWindowDrawing->DrawActiveZoneSet(m_zoneSet->GetZones(), {}, m_zoneColors); m_zoneWindowDrawing->Flash(); } } @@ -543,16 +554,16 @@ void WorkArea::CalculateZoneSet(OverlappingZonesAlgorithm overlappingAlgorithm) void WorkArea::UpdateActiveZoneSet(_In_opt_ IZoneSet* zoneSet) noexcept { - m_activeZoneSet.copy_from(zoneSet); + m_zoneSet.copy_from(zoneSet); - if (m_activeZoneSet) + if (m_zoneSet) { wil::unique_cotaskmem_string zoneSetId; - if (SUCCEEDED_LOG(StringFromCLSID(m_activeZoneSet->Id(), &zoneSetId))) + if (SUCCEEDED_LOG(StringFromCLSID(m_zoneSet->Id(), &zoneSetId))) { FancyZonesDataTypes::ZoneSetData data{ .uuid = zoneSetId.get(), - .type = m_activeZoneSet->LayoutType() + .type = m_zoneSet->LayoutType() }; FancyZonesDataInstance().SetActiveZoneSet(m_uniqueId, data); } @@ -583,9 +594,9 @@ LRESULT WorkArea::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept ZoneIndexSet WorkArea::ZonesFromPoint(POINT pt) noexcept { - if (m_activeZoneSet) + if (m_zoneSet) { - return m_activeZoneSet->ZonesFromPoint(pt); + return m_zoneSet->ZonesFromPoint(pt); } return {}; } diff --git a/src/modules/fancyzones/FancyZonesLib/WorkArea.h b/src/modules/fancyzones/FancyZonesLib/WorkArea.h index 85b42744b..ab834c352 100644 --- a/src/modules/fancyzones/FancyZonesLib/WorkArea.h +++ b/src/modules/fancyzones/FancyZonesLib/WorkArea.h @@ -51,8 +51,9 @@ interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IWorkArea : * * @param window Handle of window which should be assigned to zone. * @param indexSet The set of zone indices within zone layout. + * @param suppressMove Whether we should just update the records or move window to the zone. */ - IFACEMETHOD_(void, MoveWindowIntoZoneByIndexSet)(HWND window, const ZoneIndexSet& indexSet) = 0; + IFACEMETHOD_(void, MoveWindowIntoZoneByIndexSet)(HWND window, const ZoneIndexSet& indexSet, bool suppressMove = false) = 0; /** * Assign window to the zone based on direction (using WIN + LEFT/RIGHT arrow), based on zone index numbers, * not their on-screen position. @@ -102,7 +103,7 @@ interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IWorkArea : /** * @returns Active zone layout for this work area. */ - IFACEMETHOD_(IZoneSet*, ActiveZoneSet)() const = 0; + IFACEMETHOD_(IZoneSet*, ZoneSet)() const = 0; /* * @returns Zone index of the window */ @@ -113,6 +114,13 @@ interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IWorkArea : * Update currently active zone layout for this work area. */ IFACEMETHOD_(void, UpdateActiveZoneSet)() = 0; + /** + * Cycle through tabs in the zone that the window is in. + * + * @param window Handle of window which is cycled from (the current tab). + * @param reverse Whether to cycle in reverse order (to the previous tab) or to move to the next tab. + */ + IFACEMETHOD_(void, CycleTabs)(HWND window, bool reverse) = 0; /** * Clear the selected zones when this WorkArea loses focus. */ diff --git a/src/modules/fancyzones/FancyZonesLib/Zone.cpp b/src/modules/fancyzones/FancyZonesLib/Zone.cpp index d44130c45..9306e0faf 100644 --- a/src/modules/fancyzones/FancyZonesLib/Zone.cpp +++ b/src/modules/fancyzones/FancyZonesLib/Zone.cpp @@ -35,46 +35,12 @@ public: IFACEMETHODIMP_(RECT) GetZoneRect() const noexcept { return m_zoneRect; } IFACEMETHODIMP_(long) GetZoneArea() const noexcept { return max(m_zoneRect.bottom - m_zoneRect.top, 0) * max(m_zoneRect.right - m_zoneRect.left, 0); } IFACEMETHODIMP_(ZoneIndex) Id() const noexcept { return m_id; } - IFACEMETHODIMP_(RECT) ComputeActualZoneRect(HWND window, HWND zoneWindow) const noexcept; private: RECT m_zoneRect{}; const ZoneIndex m_id{}; - std::map m_windows{}; }; -RECT Zone::ComputeActualZoneRect(HWND window, HWND zoneWindow) const noexcept -{ - // Take care of 1px border - RECT newWindowRect = m_zoneRect; - - RECT windowRect{}; - ::GetWindowRect(window, &windowRect); - - RECT frameRect{}; - - if (SUCCEEDED(DwmGetWindowAttribute(window, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRect, sizeof(frameRect)))) - { - LONG leftMargin = frameRect.left - windowRect.left; - LONG rightMargin = frameRect.right - windowRect.right; - LONG bottomMargin = frameRect.bottom - windowRect.bottom; - newWindowRect.left -= leftMargin; - newWindowRect.right -= rightMargin; - newWindowRect.bottom -= bottomMargin; - } - - // Map to screen coords - MapWindowRect(zoneWindow, nullptr, &newWindowRect); - - if ((::GetWindowLong(window, GWL_STYLE) & WS_SIZEBOX) == 0) - { - newWindowRect.right = newWindowRect.left + (windowRect.right - windowRect.left); - newWindowRect.bottom = newWindowRect.top + (windowRect.bottom - windowRect.top); - } - - return newWindowRect; -} - winrt::com_ptr MakeZone(const RECT& zoneRect, const ZoneIndex zoneId) noexcept { if (ValidateZoneRect(zoneRect) && zoneId >= 0) diff --git a/src/modules/fancyzones/FancyZonesLib/Zone.h b/src/modules/fancyzones/FancyZonesLib/Zone.h index 8be1f1ba1..0700e003b 100644 --- a/src/modules/fancyzones/FancyZonesLib/Zone.h +++ b/src/modules/fancyzones/FancyZonesLib/Zone.h @@ -26,16 +26,6 @@ interface __declspec(uuid("{8228E934-B6EF-402A-9892-15A1441BF8B0}")) IZone : pub * @returns Zone identifier. */ IFACEMETHOD_(ZoneIndex, Id)() const = 0; - /** - * Compute the coordinates of the rectangle to which a window should be resized. - * - * @param window Handle of window which should be assigned to zone. - * @param zoneWindow The m_window of a WorkArea, it's a hidden window representing the - * current monitor desktop work area. - * @returns a RECT structure, describing global coordinates to which a window should be resized - */ - IFACEMETHOD_(RECT, ComputeActualZoneRect)(HWND window, HWND zoneWindow) const = 0; - }; winrt::com_ptr MakeZone(const RECT& zoneRect, const ZoneIndex zoneId) noexcept; diff --git a/src/modules/fancyzones/FancyZonesLib/ZoneColors.h b/src/modules/fancyzones/FancyZonesLib/ZoneColors.h index 867adbe26..33a6934ed 100644 --- a/src/modules/fancyzones/FancyZonesLib/ZoneColors.h +++ b/src/modules/fancyzones/FancyZonesLib/ZoneColors.h @@ -6,5 +6,6 @@ struct ZoneColors COLORREF primaryColor; COLORREF borderColor; COLORREF highlightColor; + COLORREF textColor; int highlightOpacity; }; diff --git a/src/modules/fancyzones/FancyZonesLib/ZoneSet.cpp b/src/modules/fancyzones/FancyZonesLib/ZoneSet.cpp index c89ba458d..1d8c93642 100644 --- a/src/modules/fancyzones/FancyZonesLib/ZoneSet.cpp +++ b/src/modules/fancyzones/FancyZonesLib/ZoneSet.cpp @@ -130,7 +130,7 @@ public: IFACEMETHODIMP_(void) MoveWindowIntoZoneByIndex(HWND window, HWND workAreaWindow, ZoneIndex index) noexcept; IFACEMETHODIMP_(void) - MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const ZoneIndexSet& indexSet) noexcept; + MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const ZoneIndexSet& indexSet, bool suppressMove = false) noexcept; IFACEMETHODIMP_(bool) MoveWindowIntoZoneByDirectionAndIndex(HWND window, HWND workAreaWindow, DWORD vkCode, bool cycle) noexcept; IFACEMETHODIMP_(bool) @@ -139,6 +139,10 @@ public: ExtendWindowByDirectionAndPosition(HWND window, HWND workAreaWindow, DWORD vkCode) noexcept; IFACEMETHODIMP_(void) MoveWindowIntoZoneByPoint(HWND window, HWND workAreaWindow, POINT ptClient) noexcept; + IFACEMETHODIMP_(void) + DismissWindow(HWND window) noexcept; + IFACEMETHODIMP_(void) + CycleTabs(HWND window, bool reverse) noexcept; IFACEMETHODIMP_(bool) CalculateZones(RECT workArea, int zoneCount, int spacing) noexcept; IFACEMETHODIMP_(bool) IsZoneEmpty(ZoneIndex zoneIndex) const noexcept; @@ -151,6 +155,8 @@ private: bool CalculateUniquePriorityGridLayout(Rect workArea, int zoneCount, int spacing) noexcept; bool CalculateCustomLayout(Rect workArea, int spacing) noexcept; bool CalculateGridZones(Rect workArea, FancyZonesDataTypes::GridLayoutInfo gridLayoutInfo, int spacing); + HWND GetNextTab(ZoneIndexSet indexSet, HWND current, bool reverse) noexcept; + void InsertTabIntoZone(HWND window, std::optional tabSortKeyWithinZone, const ZoneIndexSet& indexSet); ZoneIndexSet ZoneSelectSubregion(const ZoneIndexSet& capturedZones, POINT pt) const; ZoneIndexSet ZoneSelectClosestCenter(const ZoneIndexSet& capturedZones, POINT pt) const; @@ -160,6 +166,7 @@ private: ZonesMap m_zones; std::map m_windowIndexSet; + std::map> m_windowsByIndexSets; // Needed for ExtendWindowByDirectionAndPosition std::map m_windowInitialIndexSet; @@ -289,7 +296,7 @@ ZoneSet::MoveWindowIntoZoneByIndex(HWND window, HWND workAreaWindow, ZoneIndex i } IFACEMETHODIMP_(void) -ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const ZoneIndexSet& zoneIds) noexcept +ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const ZoneIndexSet& zoneIds, bool suppressMove) noexcept { if (m_zones.empty()) { @@ -303,18 +310,20 @@ ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const Zo m_windowInitialIndexSet.erase(window); } + auto tabSortKeyWithinZone = GetTabSortKeyWithinZone(window); + DismissWindow(window); + RECT size; bool sizeEmpty = true; Bitmask bitmask = 0; - - m_windowIndexSet[window] = {}; + auto& indexSet = m_windowIndexSet[window]; for (ZoneIndex id : zoneIds) { if (m_zones.contains(id)) { const auto& zone = m_zones.at(id); - const RECT newSize = zone->ComputeActualZoneRect(window, workAreaWindow); + const RECT newSize = zone->GetZoneRect(); if (!sizeEmpty) { size.left = min(size.left, newSize.left); @@ -328,7 +337,7 @@ ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const Zo sizeEmpty = false; } - m_windowIndexSet[window].push_back(id); + indexSet.push_back(id); } if (id < std::numeric_limits::digits) @@ -339,9 +348,16 @@ ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const Zo if (!sizeEmpty) { - SaveWindowSizeAndOrigin(window); - SizeWindowToRect(window, size); + if (!suppressMove) + { + SaveWindowSizeAndOrigin(window); + + auto rect = AdjustRectForSizeWindowToRect(window, size, workAreaWindow); + SizeWindowToRect(window, rect); + } + StampWindow(window, bitmask); + InsertTabIntoZone(window, tabSortKeyWithinZone, indexSet); } } @@ -418,14 +434,14 @@ ZoneSet::MoveWindowIntoZoneByDirectionAndPosition(HWND window, HWND workAreaWind } } - RECT windowRect, windowZoneRect; - if (GetWindowRect(window, &windowRect) && GetWindowRect(workAreaWindow, &windowZoneRect)) + RECT windowRect, workAreaRect; + if (GetWindowRect(window, &windowRect) && GetWindowRect(workAreaWindow, &workAreaRect)) { // Move to coordinates relative to windowZone - windowRect.top -= windowZoneRect.top; - windowRect.bottom -= windowZoneRect.top; - windowRect.left -= windowZoneRect.left; - windowRect.right -= windowZoneRect.left; + windowRect.top -= workAreaRect.top; + windowRect.bottom -= workAreaRect.top; + windowRect.left -= workAreaRect.left; + windowRect.right -= workAreaRect.left; auto result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects); if (result < zoneRects.size()) @@ -439,7 +455,7 @@ ZoneSet::MoveWindowIntoZoneByDirectionAndPosition(HWND window, HWND workAreaWind // Consider all zones as available zoneRects.resize(m_zones.size()); std::transform(m_zones.begin(), m_zones.end(), zoneRects.begin(), [](auto zone) { return zone.second->GetZoneRect(); }); - windowRect = FancyZonesUtils::PrepareRectForCycling(windowRect, windowZoneRect, vkCode); + windowRect = FancyZonesUtils::PrepareRectForCycling(windowRect, workAreaRect, vkCode); result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects); if (result < zoneRects.size()) @@ -546,6 +562,108 @@ ZoneSet::MoveWindowIntoZoneByPoint(HWND window, HWND workAreaWindow, POINT ptCli MoveWindowIntoZoneByIndexSet(window, workAreaWindow, zones); } +void ZoneSet::DismissWindow(HWND window) noexcept +{ + auto& indexSet = m_windowIndexSet[window]; + if (!indexSet.empty()) + { + auto& windows = m_windowsByIndexSets[indexSet]; + windows.erase(find(begin(windows), end(windows), window)); + if (windows.empty()) + { + m_windowsByIndexSets.erase(indexSet); + } + + indexSet.clear(); + } + + SetTabSortKeyWithinZone(window, std::nullopt); +} + +IFACEMETHODIMP_(void) +ZoneSet::CycleTabs(HWND window, bool reverse) noexcept +{ + auto indexSet = GetZoneIndexSetFromWindow(window); + + // Do nothing in case the window is not recognized + if (indexSet.empty()) + { + return; + } + + for (;;) + { + auto next = GetNextTab(indexSet, window, reverse); + + // Determine whether the window still exists + if (!IsWindow(next)) + { + // Dismiss the encountered window since it was probably closed + DismissWindow(next); + continue; + } + + SwitchToWindow(next); + + break; + } +} + +HWND ZoneSet::GetNextTab(ZoneIndexSet indexSet, HWND current, bool reverse) noexcept +{ + const auto& tabs = m_windowsByIndexSets[indexSet]; + auto tabIt = std::find(tabs.begin(), tabs.end(), current); + if (!reverse) + { + ++tabIt; + return tabIt == tabs.end() ? tabs.front() : *tabIt; + } + else + { + return tabIt == tabs.begin() ? tabs.back() : *(--tabIt); + } +} + +void ZoneSet::InsertTabIntoZone(HWND window, std::optional tabSortKeyWithinZone, const ZoneIndexSet& indexSet) +{ + if (tabSortKeyWithinZone.has_value()) + { + // Insert the tab using the provided sort key + auto predicate = [tabSortKeyWithinZone](HWND tab) { + auto currentTabSortKeyWithinZone = GetTabSortKeyWithinZone(tab); + if (currentTabSortKeyWithinZone.has_value()) + { + return currentTabSortKeyWithinZone.value() > tabSortKeyWithinZone; + } + else + { + return false; + } + }; + + auto position = std::find_if(m_windowsByIndexSets[indexSet].begin(), m_windowsByIndexSets[indexSet].end(), predicate); + m_windowsByIndexSets[indexSet].insert(position, window); + } + else + { + // Insert the tab at the end + tabSortKeyWithinZone = 0; + if (!m_windowsByIndexSets[indexSet].empty()) + { + auto prevTab = m_windowsByIndexSets[indexSet].back(); + auto prevTabSortKeyWithinZone = GetTabSortKeyWithinZone(prevTab); + if (prevTabSortKeyWithinZone.has_value()) + { + tabSortKeyWithinZone = prevTabSortKeyWithinZone.value() + 1; + } + } + + m_windowsByIndexSets[indexSet].push_back(window); + } + + SetTabSortKeyWithinZone(window, tabSortKeyWithinZone); +} + IFACEMETHODIMP_(bool) ZoneSet::CalculateZones(RECT workAreaRect, int zoneCount, int spacing) noexcept { diff --git a/src/modules/fancyzones/FancyZonesLib/ZoneSet.h b/src/modules/fancyzones/FancyZonesLib/ZoneSet.h index 9d7705cb0..de1bbfc63 100644 --- a/src/modules/fancyzones/FancyZonesLib/ZoneSet.h +++ b/src/modules/fancyzones/FancyZonesLib/ZoneSet.h @@ -64,8 +64,9 @@ interface __declspec(uuid("{E4839EB7-669D-49CF-84A9-71A2DFD851A3}")) IZoneSet : * @param workAreaWindow The m_window of a WorkArea, it's a hidden window representing the * current monitor desktop work area. * @param indexSet The set of zone indices within zone layout. + * @param suppressMove Whether we should just update the records or move window to the zone. */ - IFACEMETHOD_(void, MoveWindowIntoZoneByIndexSet)(HWND window, HWND workAreaWindow, const ZoneIndexSet& indexSet) = 0; + IFACEMETHOD_(void, MoveWindowIntoZoneByIndexSet)(HWND window, HWND workAreaWindow, const ZoneIndexSet& indexSet, bool suppressMove = false) = 0; /** * Assign window to the zone based on direction (using WIN + LEFT/RIGHT arrow), based on zone index numbers, * not their on-screen position. @@ -119,6 +120,21 @@ interface __declspec(uuid("{E4839EB7-669D-49CF-84A9-71A2DFD851A3}")) IZoneSet : */ IFACEMETHOD_(void, MoveWindowIntoZoneByPoint) (HWND window, HWND workAreaWindow, POINT ptClient) = 0; + /** + * Dismiss window from zone. + * + * @param window Handle of window which should be dismissed from zone. + */ + IFACEMETHOD_(void, DismissWindow) + (HWND window) = 0; + /** + * Cycle through tabs in the zone that the window is in. + * + * @param window Handle of window which is cycled from (the current tab). + * @param reverse Whether to cycle in reverse order (to the previous tab) or to move to the next tab. + */ + IFACEMETHOD_(void, CycleTabs) + (HWND window, bool reverse) = 0; /** * Calculate zone coordinates within zone layout based on number of zones and spacing. * diff --git a/src/modules/fancyzones/FancyZonesLib/ZoneWindowDrawing.cpp b/src/modules/fancyzones/FancyZonesLib/ZoneWindowDrawing.cpp index caabd0cad..3c3040b19 100644 --- a/src/modules/fancyzones/FancyZonesLib/ZoneWindowDrawing.cpp +++ b/src/modules/fancyzones/FancyZonesLib/ZoneWindowDrawing.cpp @@ -1,12 +1,12 @@ #include "pch.h" #include "ZoneWindowDrawing.h" -#include "CallTracer.h" #include #include #include #include +#include #include namespace @@ -131,10 +131,8 @@ ZoneWindowDrawing::RenderResult ZoneWindowDrawing::Render() // Draw backdrop m_renderTarget->Clear(D2D1::ColorF(0.f, 0.f, 0.f, 0.f)); - ID2D1SolidColorBrush* textBrush = nullptr; IDWriteTextFormat* textFormat = nullptr; - m_renderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black, animationAlpha), &textBrush); auto writeFactory = GetWriteFactory(); if (writeFactory) @@ -144,6 +142,7 @@ ZoneWindowDrawing::RenderResult ZoneWindowDrawing::Render() for (auto drawableRect : m_sceneRects) { + ID2D1SolidColorBrush* textBrush = nullptr; ID2D1SolidColorBrush* borderBrush = nullptr; ID2D1SolidColorBrush* fillBrush = nullptr; @@ -151,6 +150,7 @@ ZoneWindowDrawing::RenderResult ZoneWindowDrawing::Render() drawableRect.borderColor.a *= animationAlpha; drawableRect.fillColor.a *= animationAlpha; + m_renderTarget->CreateSolidColorBrush(drawableRect.textColor, &textBrush); m_renderTarget->CreateSolidColorBrush(drawableRect.borderColor, &borderBrush); m_renderTarget->CreateSolidColorBrush(drawableRect.fillColor, &fillBrush); @@ -174,6 +174,11 @@ ZoneWindowDrawing::RenderResult ZoneWindowDrawing::Render() textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER); m_renderTarget->DrawTextW(idStr.c_str(), (UINT32)idStr.size(), textFormat, drawableRect.rect, textBrush); } + + if (textBrush) + { + textBrush->Release(); + } } if (textFormat) @@ -181,11 +186,6 @@ ZoneWindowDrawing::RenderResult ZoneWindowDrawing::Render() textFormat->Release(); } - if (textBrush) - { - textBrush->Release(); - } - // The lock must be released here, as EndDraw() will wait for vertical sync lock.unlock(); @@ -289,6 +289,7 @@ void ZoneWindowDrawing::DrawActiveZoneSet(const IZoneSet::ZonesMap& zones, auto borderColor = ConvertColor(colors.borderColor); auto inactiveColor = ConvertColor(colors.primaryColor); auto highlightColor = ConvertColor(colors.highlightColor); + auto textColor = ConvertColor(colors.textColor); inactiveColor.a = colors.highlightOpacity / 100.f; highlightColor.a = colors.highlightOpacity / 100.f; @@ -313,6 +314,7 @@ void ZoneWindowDrawing::DrawActiveZoneSet(const IZoneSet::ZonesMap& zones, .rect = ConvertRect(zone->GetZoneRect()), .borderColor = borderColor, .fillColor = inactiveColor, + .textColor = textColor, .id = zone->Id() }; @@ -334,6 +336,7 @@ void ZoneWindowDrawing::DrawActiveZoneSet(const IZoneSet::ZonesMap& zones, .rect = ConvertRect(zone->GetZoneRect()), .borderColor = borderColor, .fillColor = highlightColor, + .textColor = textColor, .id = zone->Id() }; diff --git a/src/modules/fancyzones/FancyZonesLib/ZoneWindowDrawing.h b/src/modules/fancyzones/FancyZonesLib/ZoneWindowDrawing.h index ee5322e55..8b78e3688 100644 --- a/src/modules/fancyzones/FancyZonesLib/ZoneWindowDrawing.h +++ b/src/modules/fancyzones/FancyZonesLib/ZoneWindowDrawing.h @@ -20,6 +20,7 @@ class ZoneWindowDrawing D2D1_RECT_F rect; D2D1_COLOR_F borderColor; D2D1_COLOR_F fillColor; + D2D1_COLOR_F textColor; ZoneIndex id; }; diff --git a/src/modules/fancyzones/FancyZonesLib/trace.cpp b/src/modules/fancyzones/FancyZonesLib/trace.cpp index 12148b04b..f1c0f209f 100644 --- a/src/modules/fancyzones/FancyZonesLib/trace.cpp +++ b/src/modules/fancyzones/FancyZonesLib/trace.cpp @@ -14,8 +14,11 @@ #define EventEditorLaunchKey "FancyZones_EditorLaunch" #define EventSettingsKey "FancyZones_Settings" #define EventDesktopChangedKey "FancyZones_VirtualDesktopChanged" -#define EventZoneWindowKeyUpKey "FancyZones_ZoneWindowKeyUp" -#define EventMoveSizeEndKey "FancyZones_MoveSizeEnd" +#define EventWorkAreaKeyUpKey "FancyZones_ZoneWindowKeyUp" +#define EventSnapNewWindowIntoZone "FancyZones_SnapNewWindowIntoZone" +#define EventKeyboardSnapWindowToZone "FancyZones_KeyboardSnapWindowToZone" +#define EventMoveOrResizeStartedKey "FancyZones_MoveOrResizeStarted" +#define EventMoveOrResizeEndedKey "FancyZones_MoveOrResizeEnded" #define EventCycleActiveZoneSetKey "FancyZones_CycleActiveZoneSet" #define EventQuickLayoutSwitchKey "FancyZones_QuickLayoutSwitch" @@ -52,7 +55,10 @@ #define ZoneBorderColorKey "ZoneBorderColor" #define ZoneHighlightColorKey "ZoneHighlightColor" #define ZoneHighlightOpacityKey "ZoneHighlightOpacity" -#define HotkeyKey "Hotkey" +#define EditorHotkeyKey "EditorHotkey" +#define WindowSwitchingToggleKey "WindowSwitchingToggle" +#define NextTabHotkey "NextTabHotkey" +#define PrevTabHotkey "PrevTabHotkey" #define ExcludedAppsCountKey "ExcludedAppsCount" #define KeyboardValueKey "KeyboardValue" #define ActiveSetKey "ActiveSet" @@ -75,7 +81,8 @@ struct ZoneSetInfo size_t NumberOfWindows = 0; }; -ZoneSetInfo GetZoneSetInfo(_In_opt_ winrt::com_ptr set) noexcept + +ZoneSetInfo GetZoneSetInfo(_In_opt_ IZoneSet* set) noexcept { ZoneSetInfo info; if (set) @@ -94,6 +101,11 @@ ZoneSetInfo GetZoneSetInfo(_In_opt_ winrt::com_ptr set) noexcept return info; } +ZoneSetInfo GetZoneSetInfo(_In_opt_ winrt::com_ptr set) noexcept +{ + return GetZoneSetInfo(set.get()); +} + void Trace::RegisterProvider() noexcept { TraceLoggingRegister(g_hProvider); @@ -244,15 +256,47 @@ void Trace::FancyZones::QuickLayoutSwitched(bool shortcutUsed) noexcept TraceLoggingBoolean(shortcutUsed, QuickLayoutSwitchedWithShortcutUsed)); } +void Trace::FancyZones::SnapNewWindowIntoZone(IZoneSet* activeSet) noexcept +{ + auto const zoneInfo = GetZoneSetInfo(activeSet); + TraceLoggingWrite( + g_hProvider, + EventSnapNewWindowIntoZone, + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), + TraceLoggingValue(reinterpret_cast(activeSet), ActiveSetKey), + TraceLoggingValue(zoneInfo.NumberOfZones, NumberOfZonesKey), + TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey)); +} + +void Trace::FancyZones::KeyboardSnapWindowToZone(IZoneSet* activeSet) noexcept +{ + auto const zoneInfo = GetZoneSetInfo(activeSet); + TraceLoggingWrite( + g_hProvider, + EventKeyboardSnapWindowToZone, + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), + TraceLoggingValue(reinterpret_cast(activeSet), ActiveSetKey), + TraceLoggingValue(zoneInfo.NumberOfZones, NumberOfZonesKey), + TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey)); +} + +static std::wstring HotKeyToString(const PowerToysSettings::HotkeyObject& hotkey) +{ + return L"alt:" + std::to_wstring(hotkey.alt_pressed()) + + L", ctrl:" + std::to_wstring(hotkey.ctrl_pressed()) + + L", shift:" + std::to_wstring(hotkey.shift_pressed()) + + L", win:" + std::to_wstring(hotkey.win_pressed()) + + L", code:" + std::to_wstring(hotkey.get_code()) + + L", keyFromCode:" + hotkey.get_key(); +} + void Trace::SettingsTelemetry(const Settings& settings) noexcept { - const auto& editorHotkey = settings.editorHotkey; - std::wstring hotkeyStr = L"alt:" + std::to_wstring(editorHotkey.alt_pressed()) - + L", ctrl:" + std::to_wstring(editorHotkey.ctrl_pressed()) - + L", shift:" + std::to_wstring(editorHotkey.shift_pressed()) - + L", win:" + std::to_wstring(editorHotkey.win_pressed()) - + L", code:" + std::to_wstring(editorHotkey.get_code()) - + L", keyFromCode:" + editorHotkey.get_key(); + auto editorHotkeyStr = HotKeyToString(settings.editorHotkey); + auto nextTabHotkeyStr = HotKeyToString(settings.nextTabHotkey); + auto prevTabHotkeyStr = HotKeyToString(settings.prevTabHotkey); TraceLoggingWrite( g_hProvider, @@ -281,7 +325,10 @@ void Trace::SettingsTelemetry(const Settings& settings) noexcept TraceLoggingWideString(settings.zoneHighlightColor.c_str(), ZoneHighlightColorKey), TraceLoggingInt32(settings.zoneHighlightOpacity, ZoneHighlightOpacityKey), TraceLoggingInt32((int)settings.overlappingZonesAlgorithm, OverlappingZonesAlgorithmKey), - TraceLoggingWideString(hotkeyStr.c_str(), HotkeyKey), + TraceLoggingWideString(editorHotkeyStr.c_str(), EditorHotkeyKey), + TraceLoggingBoolean(settings.windowSwitching, WindowSwitchingToggleKey), + TraceLoggingWideString(nextTabHotkeyStr.c_str(), NextTabHotkey), + TraceLoggingWideString(prevTabHotkeyStr.c_str(), PrevTabHotkey), TraceLoggingInt32(static_cast(settings.excludedAppsArray.size()), ExcludedAppsCountKey)); } @@ -298,18 +345,31 @@ void Trace::WorkArea::KeyUp(WPARAM wParam) noexcept { TraceLoggingWrite( g_hProvider, - EventZoneWindowKeyUpKey, + EventWorkAreaKeyUpKey, ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), TraceLoggingValue(wParam, KeyboardValueKey)); } -void Trace::WorkArea::MoveSizeEnd(_In_opt_ winrt::com_ptr activeSet) noexcept +void Trace::WorkArea::MoveOrResizeStarted(_In_opt_ winrt::com_ptr activeSet) noexcept { auto const zoneInfo = GetZoneSetInfo(activeSet); TraceLoggingWrite( g_hProvider, - EventMoveSizeEndKey, + EventMoveOrResizeStartedKey, + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), + TraceLoggingValue(reinterpret_cast(activeSet.get()), ActiveSetKey), + TraceLoggingValue(zoneInfo.NumberOfZones, NumberOfZonesKey), + TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey)); +} + +void Trace::WorkArea::MoveOrResizeEnd(_In_opt_ winrt::com_ptr activeSet) noexcept +{ + auto const zoneInfo = GetZoneSetInfo(activeSet); + TraceLoggingWrite( + g_hProvider, + EventMoveOrResizeEndedKey, ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), TraceLoggingValue(reinterpret_cast(activeSet.get()), ActiveSetKey), diff --git a/src/modules/fancyzones/FancyZonesLib/trace.h b/src/modules/fancyzones/FancyZonesLib/trace.h index 9291b76ea..9435bdcc9 100644 --- a/src/modules/fancyzones/FancyZonesLib/trace.h +++ b/src/modules/fancyzones/FancyZonesLib/trace.h @@ -18,6 +18,8 @@ public: static void EditorLaunched(int value) noexcept; static void Error(const DWORD errorCode, std::wstring errorMessage, std::wstring methodName) noexcept; static void QuickLayoutSwitched(bool shortcutUsed) noexcept; + static void SnapNewWindowIntoZone(IZoneSet* activeSet) noexcept; + static void KeyboardSnapWindowToZone(IZoneSet* activeSet) noexcept; }; static void SettingsTelemetry(const Settings& settings) noexcept; @@ -33,7 +35,8 @@ public: }; static void KeyUp(WPARAM wparam) noexcept; - static void MoveSizeEnd(_In_opt_ winrt::com_ptr activeSet) noexcept; + static void MoveOrResizeStarted(_In_opt_ winrt::com_ptr activeSet) noexcept; + static void MoveOrResizeEnd(_In_opt_ winrt::com_ptr activeSet) noexcept; static void CycleActiveZoneSet(_In_opt_ winrt::com_ptr activeSet, InputMode mode) noexcept; }; }; diff --git a/src/modules/fancyzones/FancyZonesLib/util.cpp b/src/modules/fancyzones/FancyZonesLib/util.cpp index 9d5d21385..cae3c92da 100644 --- a/src/modules/fancyzones/FancyZonesLib/util.cpp +++ b/src/modules/fancyzones/FancyZonesLib/util.cpp @@ -300,6 +300,38 @@ namespace FancyZonesUtils } } + RECT AdjustRectForSizeWindowToRect(HWND window, RECT rect, HWND windowOfRect) noexcept + { + RECT newWindowRect = rect; + + RECT windowRect{}; + ::GetWindowRect(window, &windowRect); + + // Take care of borders + RECT frameRect{}; + if (SUCCEEDED(DwmGetWindowAttribute(window, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRect, sizeof(frameRect)))) + { + LONG leftMargin = frameRect.left - windowRect.left; + LONG rightMargin = frameRect.right - windowRect.right; + LONG bottomMargin = frameRect.bottom - windowRect.bottom; + newWindowRect.left -= leftMargin; + newWindowRect.right -= rightMargin; + newWindowRect.bottom -= bottomMargin; + } + + // Take care of windows that cannot be resized + if ((::GetWindowLong(window, GWL_STYLE) & WS_SIZEBOX) == 0) + { + newWindowRect.right = newWindowRect.left + (windowRect.right - windowRect.left); + newWindowRect.bottom = newWindowRect.top + (windowRect.bottom - windowRect.top); + } + + // Convert to screen coordinates + MapWindowRect(windowOfRect, nullptr, &newWindowRect); + + return newWindowRect; + } + void SizeWindowToRect(HWND window, RECT rect) noexcept { WINDOWPLACEMENT placement{}; @@ -337,6 +369,22 @@ namespace FancyZonesUtils ::SetWindowPlacement(window, &placement); } + void SwitchToWindow(HWND window) noexcept + { + // Check if the window is minimized + if (IsIconic(window)) + { + // Show the window since SetForegroundWindow fails on minimized windows + ShowWindow(window, SW_RESTORE); + } + + // This is a hack to bypass the restriction on setting the foreground window + INPUT inputs[1] = { { .type = INPUT_MOUSE } }; + SendInput(ARRAYSIZE(inputs), inputs, sizeof(INPUT)); + + SetForegroundWindow(window); + } + bool HasNoVisibleOwner(HWND window) noexcept { auto owner = GetWindow(window, GW_OWNER); @@ -660,22 +708,22 @@ namespace FancyZonesUtils return closestIdx; } - RECT PrepareRectForCycling(RECT windowRect, RECT zoneWindowRect, DWORD vkCode) noexcept + RECT PrepareRectForCycling(RECT windowRect, RECT workAreaRect, DWORD vkCode) noexcept { LONG deltaX = 0, deltaY = 0; switch (vkCode) { case VK_UP: - deltaY = zoneWindowRect.bottom - zoneWindowRect.top; + deltaY = workAreaRect.bottom - workAreaRect.top; break; case VK_DOWN: - deltaY = zoneWindowRect.top - zoneWindowRect.bottom; + deltaY = workAreaRect.top - workAreaRect.bottom; break; case VK_LEFT: - deltaX = zoneWindowRect.right - zoneWindowRect.left; + deltaX = workAreaRect.right - workAreaRect.left; break; case VK_RIGHT: - deltaX = zoneWindowRect.left - zoneWindowRect.right; + deltaX = workAreaRect.left - workAreaRect.right; } windowRect.left += deltaX; diff --git a/src/modules/fancyzones/FancyZonesLib/util.h b/src/modules/fancyzones/FancyZonesLib/util.h index 3413234ba..de1c2bd93 100644 --- a/src/modules/fancyzones/FancyZonesLib/util.h +++ b/src/modules/fancyzones/FancyZonesLib/util.h @@ -189,9 +189,14 @@ namespace FancyZonesUtils UINT GetDpiForMonitor(HMONITOR monitor) noexcept; void OrderMonitors(std::vector>& monitorInfo); + // Parameter rect is in windowOfRect coordinates + RECT AdjustRectForSizeWindowToRect(HWND window, RECT rect, HWND windowOfRect) noexcept; + // Parameter rect must be in screen coordinates (e.g. obtained from GetWindowRect) void SizeWindowToRect(HWND window, RECT rect) noexcept; + void SwitchToWindow(HWND window) noexcept; + bool HasNoVisibleOwner(HWND window) noexcept; bool IsStandardWindow(HWND window); bool IsCandidateForLastKnownZone(HWND window, const std::vector& excludedApps) noexcept; @@ -209,7 +214,7 @@ namespace FancyZonesUtils std::wstring GenerateUniqueIdAllMonitorsArea(const std::wstring& virtualDesktopId); std::wstring TrimDeviceId(const std::wstring& deviceId); - RECT PrepareRectForCycling(RECT windowRect, RECT zoneWindowRect, DWORD vkCode) noexcept; + RECT PrepareRectForCycling(RECT windowRect, RECT workAreaRect, DWORD vkCode) noexcept; size_t ChooseNextZoneByPosition(DWORD vkCode, RECT windowRect, const std::vector& zoneRects) noexcept; // If HWND is already dead, we assume it wasn't elevated diff --git a/src/modules/fancyzones/FancyZonesModuleInterface/FancyZonesModuleInterface.vcxproj b/src/modules/fancyzones/FancyZonesModuleInterface/FancyZonesModuleInterface.vcxproj index a8cf7552b..6b79113e6 100644 --- a/src/modules/fancyzones/FancyZonesModuleInterface/FancyZonesModuleInterface.vcxproj +++ b/src/modules/fancyzones/FancyZonesModuleInterface/FancyZonesModuleInterface.vcxproj @@ -56,6 +56,9 @@ {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + {6955446d-23f7-4023-9bb3-8657f904af99} + {f9c68edf-ac74-4b77-9af1-005d9c9f6a99} diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/FancyZones.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/FancyZones.Spec.cpp index 80c6b7d9a..922824526 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/FancyZones.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/FancyZones.Spec.cpp @@ -70,6 +70,9 @@ namespace FancyZonesUnitTests PowerToysSettings::Settings ptSettings(HINSTANCE{}, L"FancyZonesUnitTests"); ptSettings.add_hotkey(L"fancyzones_editor_hotkey", IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL, settings.editorHotkey); + ptSettings.add_bool_toggle(L"fancyzones_windowSwitching", IDS_SETTING_WINDOW_SWITCHING_TOGGLE_LABEL, settings.windowSwitching); + ptSettings.add_hotkey(L"fancyzones_nextTab_hotkey", IDS_SETTING_NEXT_TAB_HOTKEY_LABEL, settings.nextTabHotkey); + ptSettings.add_hotkey(L"fancyzones_prevTab_hotkey", IDS_SETTING_PREV_TAB_HOTKEY_LABEL, settings.prevTabHotkey); ptSettings.add_bool_toggle(L"fancyzones_shiftDrag", IDS_SETTING_DESCRIPTION_SHIFTDRAG, settings.shiftDrag); ptSettings.add_bool_toggle(L"fancyzones_mouseSwitch", IDS_SETTING_DESCRIPTION_MOUSESWITCH, settings.mouseSwitch); ptSettings.add_bool_toggle(L"fancyzones_overrideSnapHotkeys", IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS, settings.overrideSnapHotkeys); diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/FancyZonesSettings.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/FancyZonesSettings.Spec.cpp index 0e13a0ca6..3e6b4c530 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/FancyZonesSettings.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/FancyZonesSettings.Spec.cpp @@ -41,6 +41,7 @@ namespace FancyZonesUnitTests Assert::AreEqual(expected.showZonesOnAllMonitors, actual.showZonesOnAllMonitors); Assert::AreEqual(expected.spanZonesAcrossMonitors, actual.spanZonesAcrossMonitors); Assert::AreEqual(expected.makeDraggedWindowTransparent, actual.makeDraggedWindowTransparent); + Assert::AreEqual(expected.windowSwitching, actual.windowSwitching); Assert::AreEqual(expected.zoneColor.c_str(), actual.zoneColor.c_str()); Assert::AreEqual(expected.zoneBorderColor.c_str(), actual.zoneBorderColor.c_str()); Assert::AreEqual(expected.zoneHighlightColor.c_str(), actual.zoneHighlightColor.c_str()); @@ -53,6 +54,8 @@ namespace FancyZonesUnitTests } compareHotkeyObjects(expected.editorHotkey, actual.editorHotkey); + compareHotkeyObjects(expected.nextTabHotkey, actual.nextTabHotkey); + compareHotkeyObjects(expected.prevTabHotkey, actual.prevTabHotkey); } TEST_CLASS (FancyZonesSettingsCreationUnitTest) @@ -62,7 +65,6 @@ namespace FancyZonesUnitTests PCWSTR m_moduleKey = L"FancyZonesUnitTests"; std::wstring m_tmpName; - const PowerToysSettings::HotkeyObject m_defaultHotkeyObject = PowerToysSettings::HotkeyObject::from_settings(true, false, false, false, VK_OEM_3); const Settings m_defaultSettings; TEST_METHOD_INITIALIZE(Init) @@ -128,6 +130,9 @@ namespace FancyZonesUnitTests values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHighlightColor); values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity); values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json()); + values.add_property(L"fancyzones_windowSwitching", expected.windowSwitching); + values.add_property(L"fancyzones_nextTab_hotkey", expected.nextTabHotkey.get_json()); + values.add_property(L"fancyzones_prevTab_hotkey", expected.prevTabHotkey.get_json()); values.add_property(L"fancyzones_excluded_apps", expected.excludedApps); values.save_to_settings_file(); @@ -168,6 +173,9 @@ namespace FancyZonesUnitTests values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHighlightColor); values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity); values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json()); + values.add_property(L"fancyzones_windowSwitching", expected.windowSwitching); + values.add_property(L"fancyzones_nextTab_hotkey", expected.nextTabHotkey.get_json()); + values.add_property(L"fancyzones_prevTab_hotkey", expected.prevTabHotkey.get_json()); values.add_property(L"fancyzones_excluded_apps", expected.excludedApps); values.save_to_settings_file(); @@ -202,6 +210,8 @@ namespace FancyZonesUnitTests .zoneHighlightColor = L"#00FFD7", .zoneHighlightOpacity = 45, .editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_OEM_3), + .nextTabHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_NEXT), + .prevTabHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_PRIOR), .excludedApps = L"app", .excludedAppsArray = { L"APP" }, }; @@ -212,6 +222,9 @@ namespace FancyZonesUnitTests values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHighlightColor); values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity); values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json()); + values.add_property(L"fancyzones_windowSwitching", expected.windowSwitching); + values.add_property(L"fancyzones_nextTab_hotkey", expected.nextTabHotkey.get_json()); + values.add_property(L"fancyzones_prevTab_hotkey", expected.prevTabHotkey.get_json()); values.add_property(L"fancyzones_excluded_apps", expected.excludedApps); values.save_to_settings_file(); @@ -246,6 +259,9 @@ namespace FancyZonesUnitTests values.add_property(L"fancyzones_makeDraggedWindowTransparent", expected.makeDraggedWindowTransparent); values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity); values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json()); + values.add_property(L"fancyzones_windowSwitching", expected.windowSwitching); + values.add_property(L"fancyzones_nextTab_hotkey", expected.nextTabHotkey.get_json()); + values.add_property(L"fancyzones_prevTab_hotkey", expected.prevTabHotkey.get_json()); values.add_property(L"fancyzones_excluded_apps", expected.excludedApps); values.save_to_settings_file(); @@ -281,6 +297,9 @@ namespace FancyZonesUnitTests values.add_property(L"fancyzones_zoneColor", expected.zoneColor); values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHighlightColor); values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json()); + values.add_property(L"fancyzones_windowSwitching", expected.windowSwitching); + values.add_property(L"fancyzones_nextTab_hotkey", expected.nextTabHotkey.get_json()); + values.add_property(L"fancyzones_prevTab_hotkey", expected.prevTabHotkey.get_json()); values.add_property(L"fancyzones_excluded_apps", expected.excludedApps); values.save_to_settings_file(); @@ -354,6 +373,9 @@ namespace FancyZonesUnitTests values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHighlightColor); values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity); values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json()); + values.add_property(L"fancyzones_windowSwitching", expected.windowSwitching); + values.add_property(L"fancyzones_nextTab_hotkey", expected.nextTabHotkey.get_json()); + values.add_property(L"fancyzones_prevTab_hotkey", expected.prevTabHotkey.get_json()); values.save_to_settings_file(); @@ -416,6 +438,9 @@ namespace FancyZonesUnitTests IDS_SETTING_LAUNCH_EDITOR_BUTTON, IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION); ptSettings.add_hotkey(L"fancyzones_editor_hotkey", IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL, settings.editorHotkey); + ptSettings.add_bool_toggle(L"fancyzones_windowSwitching", IDS_SETTING_WINDOW_SWITCHING_TOGGLE_LABEL, settings.windowSwitching); + ptSettings.add_hotkey(L"fancyzones_nextTab_hotkey", IDS_SETTING_NEXT_TAB_HOTKEY_LABEL, settings.nextTabHotkey); + ptSettings.add_hotkey(L"fancyzones_prevTab_hotkey", IDS_SETTING_PREV_TAB_HOTKEY_LABEL, settings.prevTabHotkey); ptSettings.add_bool_toggle(L"fancyzones_shiftDrag", IDS_SETTING_DESCRIPTION_SHIFTDRAG, settings.shiftDrag); ptSettings.add_bool_toggle(L"fancyzones_mouseSwitch", IDS_SETTING_DESCRIPTION_MOUSESWITCH, settings.mouseSwitch); ptSettings.add_bool_toggle(L"fancyzones_overrideSnapHotkeys", IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS, settings.overrideSnapHotkeys); @@ -517,6 +542,8 @@ namespace FancyZonesUnitTests .zoneHighlightColor = L"#00AABB", .zoneHighlightOpacity = 45, .editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3), + .nextTabHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_NEXT), + .prevTabHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_PRIOR), .excludedApps = L"app\r\napp2", .excludedAppsArray = { L"APP", L"APP2" }, }; diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj index 4f5e34ca8..2ada2e6a7 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj @@ -62,6 +62,9 @@ {caba8dfb-823b-4bf2-93ac-3f31984150d9} + + {6955446d-23f7-4023-9bb3-8657f904af99} + {f9c68edf-ac74-4b77-9af1-005d9c9f6a99} diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkArea.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkArea.Spec.cpp index 5b83733d0..f9cd75842 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkArea.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkArea.Spec.cpp @@ -81,10 +81,10 @@ namespace FancyZonesUnitTests auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm); testWorkArea(workArea); - auto* activeZoneSet{ workArea->ActiveZoneSet() }; - Assert::IsNotNull(activeZoneSet); - Assert::AreEqual(static_cast(activeZoneSet->LayoutType()), static_cast(FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid)); - Assert::AreEqual(activeZoneSet->GetZones().size(), static_cast(3)); + auto* zoneSet{ workArea->ZoneSet() }; + Assert::IsNotNull(zoneSet); + Assert::AreEqual(static_cast(zoneSet->LayoutType()), static_cast(FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid)); + Assert::AreEqual(zoneSet->GetZones().size(), static_cast(3)); } TEST_METHOD (CreateWorkAreaNoHinst) @@ -92,10 +92,10 @@ namespace FancyZonesUnitTests auto workArea = MakeWorkArea({}, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm); testWorkArea(workArea); - auto* activeZoneSet{ workArea->ActiveZoneSet() }; - Assert::IsNotNull(activeZoneSet); - Assert::AreEqual(static_cast(activeZoneSet->LayoutType()), static_cast(FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid)); - Assert::AreEqual(activeZoneSet->GetZones().size(), static_cast(3)); + auto* zoneSet{ workArea->ZoneSet() }; + Assert::IsNotNull(zoneSet); + Assert::AreEqual(static_cast(zoneSet->LayoutType()), static_cast(FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid)); + Assert::AreEqual(zoneSet->GetZones().size(), static_cast(3)); } TEST_METHOD (CreateWorkAreaNoHinstFlashZones) @@ -103,10 +103,10 @@ namespace FancyZonesUnitTests auto workArea = MakeWorkArea({}, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm); testWorkArea(workArea); - auto* activeZoneSet{ workArea->ActiveZoneSet() }; - Assert::IsNotNull(activeZoneSet); - Assert::AreEqual(static_cast(activeZoneSet->LayoutType()), static_cast(FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid)); - Assert::AreEqual(activeZoneSet->GetZones().size(), static_cast(3)); + auto* zoneSet{ workArea->ZoneSet() }; + Assert::IsNotNull(zoneSet); + Assert::AreEqual(static_cast(zoneSet->LayoutType()), static_cast(FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid)); + Assert::AreEqual(zoneSet->GetZones().size(), static_cast(3)); } TEST_METHOD (CreateWorkAreaNoMonitor) @@ -138,10 +138,10 @@ namespace FancyZonesUnitTests Assert::IsNotNull(workArea.get()); Assert::IsTrue(expectedUniqueId == workArea->UniqueId()); - auto* activeZoneSet{ workArea->ActiveZoneSet() }; - Assert::IsNotNull(activeZoneSet); - Assert::AreEqual(static_cast(activeZoneSet->LayoutType()), static_cast(FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid)); - Assert::AreEqual(activeZoneSet->GetZones().size(), static_cast(3)); + auto* zoneSet{ workArea->ZoneSet() }; + Assert::IsNotNull(zoneSet); + Assert::AreEqual(static_cast(zoneSet->LayoutType()), static_cast(FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid)); + Assert::AreEqual(zoneSet->GetZones().size(), static_cast(3)); } TEST_METHOD (CreateWorkAreaNoDesktopId) @@ -164,10 +164,10 @@ namespace FancyZonesUnitTests const std::wstring expectedWorkArea = std::to_wstring(m_monitorInfo.rcMonitor.right) + L"_" + std::to_wstring(m_monitorInfo.rcMonitor.bottom); Assert::IsNotNull(workArea.get()); - auto* activeZoneSet{ workArea->ActiveZoneSet() }; - Assert::IsNotNull(activeZoneSet); - Assert::AreEqual(static_cast(activeZoneSet->LayoutType()), static_cast(FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid)); - Assert::AreEqual(activeZoneSet->GetZones().size(), static_cast(3)); + auto* zoneSet{ workArea->ZoneSet() }; + Assert::IsNotNull(zoneSet); + Assert::AreEqual(static_cast(zoneSet->LayoutType()), static_cast(FancyZonesDataTypes::ZoneSetLayoutType::PriorityGrid)); + Assert::AreEqual(zoneSet->GetZones().size(), static_cast(3)); } TEST_METHOD (CreateWorkAreaClonedFromParent) @@ -187,7 +187,7 @@ namespace FancyZonesUnitTests // newWorkArea = false - workArea won't be cloned from parent auto actualWorkArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm); - Assert::IsNotNull(actualWorkArea->ActiveZoneSet()); + Assert::IsNotNull(actualWorkArea->ZoneSet()); Assert::IsTrue(m_fancyZonesData.GetDeviceInfoMap().contains(m_uniqueId)); auto currentDeviceInfo = m_fancyZonesData.GetDeviceInfoMap().at(m_uniqueId); @@ -299,7 +299,7 @@ namespace FancyZonesUnitTests const auto actual = workArea->MoveSizeEnd(window, POINT{ 0, 0 }); Assert::AreEqual(expected, actual); - const auto zoneSet = workArea->ActiveZoneSet(); + const auto zoneSet = workArea->ZoneSet(); zoneSet->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); const auto actualZoneIndexSet = zoneSet->GetZoneIndexSetFromWindow(window); Assert::IsFalse(std::vector{} == actualZoneIndexSet); @@ -316,7 +316,7 @@ namespace FancyZonesUnitTests const auto actual = workArea->MoveSizeEnd(window, POINT{ -100, -100 }); Assert::AreEqual(expected, actual); - const auto zoneSet = workArea->ActiveZoneSet(); + const auto zoneSet = workArea->ZoneSet(); const auto actualZoneIndexSet = zoneSet->GetZoneIndexSetFromWindow(window); Assert::IsTrue(std::vector{} == actualZoneIndexSet); } @@ -355,7 +355,7 @@ namespace FancyZonesUnitTests const auto actual = workArea->MoveSizeEnd(window, POINT{ -1, -1 }); Assert::AreEqual(expected, actual); - const auto zoneSet = workArea->ActiveZoneSet(); + const auto zoneSet = workArea->ZoneSet(); zoneSet->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); const auto actualZoneIndex = zoneSet->GetZoneIndexSetFromWindow(window); Assert::IsFalse(std::vector{} == actualZoneIndex); // with invalid point zone remains the same @@ -364,17 +364,17 @@ namespace FancyZonesUnitTests TEST_METHOD (MoveWindowIntoZoneByIndex) { auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm); - Assert::IsNotNull(workArea->ActiveZoneSet()); + Assert::IsNotNull(workArea->ZoneSet()); workArea->MoveWindowIntoZoneByIndex(Mocks::Window(), 0); - const auto actual = workArea->ActiveZoneSet(); + const auto actual = workArea->ZoneSet(); } TEST_METHOD (MoveWindowIntoZoneByDirectionAndIndex) { auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm); - Assert::IsNotNull(workArea->ActiveZoneSet()); + Assert::IsNotNull(workArea->ZoneSet()); const auto window = Mocks::WindowCreate(m_hInst); workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); @@ -389,7 +389,7 @@ namespace FancyZonesUnitTests TEST_METHOD (MoveWindowIntoZoneByDirectionManyTimes) { auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm); - Assert::IsNotNull(workArea->ActiveZoneSet()); + Assert::IsNotNull(workArea->ZoneSet()); const auto window = Mocks::WindowCreate(m_hInst); workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); @@ -406,7 +406,7 @@ namespace FancyZonesUnitTests TEST_METHOD (SaveWindowProcessToZoneIndexNullptrWindow) { auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm); - Assert::IsNotNull(workArea->ActiveZoneSet()); + Assert::IsNotNull(workArea->ZoneSet()); workArea->SaveWindowProcessToZoneIndex(nullptr); @@ -417,11 +417,11 @@ namespace FancyZonesUnitTests TEST_METHOD (SaveWindowProcessToZoneIndexNoWindowAdded) { auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm); - Assert::IsNotNull(workArea->ActiveZoneSet()); + Assert::IsNotNull(workArea->ZoneSet()); auto window = Mocks::WindowCreate(m_hInst); auto zone = MakeZone(RECT{ 0, 0, 100, 100 }, 1); - workArea->ActiveZoneSet()->AddZone(zone); + workArea->ZoneSet()->AddZone(zone); workArea->SaveWindowProcessToZoneIndex(window); @@ -432,12 +432,12 @@ namespace FancyZonesUnitTests TEST_METHOD (SaveWindowProcessToZoneIndexNoWindowAddedWithFilledAppZoneHistory) { auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm); - Assert::IsNotNull(workArea->ActiveZoneSet()); + Assert::IsNotNull(workArea->ZoneSet()); const auto window = Mocks::WindowCreate(m_hInst); const auto processPath = get_process_path(window); const auto deviceId = workArea->UniqueId(); - const auto zoneSetId = workArea->ActiveZoneSet()->Id(); + const auto zoneSetId = workArea->ZoneSet()->Id(); // fill app zone history map Assert::IsTrue(m_fancyZonesData.SetAppLastZones(window, deviceId, Helpers::GuidToString(zoneSetId), { 0 })); @@ -448,7 +448,7 @@ namespace FancyZonesUnitTests // add zone without window const auto zone = MakeZone(RECT{ 0, 0, 100, 100 }, 1); - workArea->ActiveZoneSet()->AddZone(zone); + workArea->ZoneSet()->AddZone(zone); workArea->SaveWindowProcessToZoneIndex(window); Assert::AreEqual((size_t)1, m_fancyZonesData.GetAppZoneHistoryMap().size()); @@ -460,15 +460,15 @@ namespace FancyZonesUnitTests TEST_METHOD (SaveWindowProcessToZoneIndexWindowAdded) { auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm); - Assert::IsNotNull(workArea->ActiveZoneSet()); + Assert::IsNotNull(workArea->ZoneSet()); auto window = Mocks::WindowCreate(m_hInst); const auto processPath = get_process_path(window); const auto deviceId = workArea->UniqueId(); - const auto zoneSetId = workArea->ActiveZoneSet()->Id(); + const auto zoneSetId = workArea->ZoneSet()->Id(); auto zone = MakeZone(RECT{ 0, 0, 100, 100 }, 1); - workArea->ActiveZoneSet()->AddZone(zone); + workArea->ZoneSet()->AddZone(zone); workArea->MoveWindowIntoZoneByIndex(window, 0); //fill app zone history map @@ -482,7 +482,7 @@ namespace FancyZonesUnitTests const auto& actualAppZoneHistory = m_fancyZonesData.GetAppZoneHistoryMap(); Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - const auto& expected = workArea->ActiveZoneSet()->GetZoneIndexSetFromWindow(window); + const auto& expected = workArea->ZoneSet()->GetZoneIndexSetFromWindow(window); const auto& actual = appHistoryArray[0].zoneIndexSet; Assert::IsTrue(expected == actual); } @@ -490,7 +490,7 @@ namespace FancyZonesUnitTests TEST_METHOD (WhenWindowIsNotResizablePlacingItIntoTheZoneShouldNotResizeIt) { auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm); - Assert::IsNotNull(workArea->ActiveZoneSet()); + Assert::IsNotNull(workArea->ZoneSet()); auto window = Mocks::WindowCreate(m_hInst); @@ -501,7 +501,7 @@ namespace FancyZonesUnitTests SetWindowLong(window, GWL_STYLE, GetWindowLong(window, GWL_STYLE) & ~WS_SIZEBOX); auto zone = MakeZone(RECT{ 50, 50, 300, 300 }, 1); - workArea->ActiveZoneSet()->AddZone(zone); + workArea->ZoneSet()->AddZone(zone); workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true); diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/ZoneSet.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/ZoneSet.Spec.cpp index 82cdefab1..96f560b4d 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/ZoneSet.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/ZoneSet.Spec.cpp @@ -297,9 +297,9 @@ namespace FancyZonesUnitTests { winrt::com_ptr zone = MakeZone({ 0, 0, 100, 100 }, 1); HWND window = Mocks::Window(); - HWND zoneWindow = Mocks::Window(); + HWND workArea = Mocks::Window(); m_set->AddZone(zone); - m_set->MoveWindowIntoZoneByIndexSet(window, zoneWindow, { 0 }); + m_set->MoveWindowIntoZoneByIndexSet(window, workArea, { 0 }); auto actual = m_set->GetZoneIndexSetFromWindow(Mocks::Window()); Assert::IsTrue(std::vector{} == actual); @@ -309,9 +309,9 @@ namespace FancyZonesUnitTests { winrt::com_ptr zone = MakeZone({ 0, 0, 100, 100 }, 1); HWND window = Mocks::Window(); - HWND zoneWindow = Mocks::Window(); + HWND workArea = Mocks::Window(); m_set->AddZone(zone); - m_set->MoveWindowIntoZoneByIndexSet(window, zoneWindow, { 0 }); + m_set->MoveWindowIntoZoneByIndexSet(window, workArea, { 0 }); auto actual = m_set->GetZoneIndexSetFromWindow(nullptr); Assert::IsTrue(std::vector{} == actual); @@ -432,7 +432,7 @@ namespace FancyZonesUnitTests TEST_METHOD (MoveWindowIntoZoneByPointDropAddWindow) { const auto window = Mocks::Window(); - const auto zoneWindow = Mocks::Window(); + const auto workArea = Mocks::Window(); winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }, 1); @@ -450,7 +450,7 @@ namespace FancyZonesUnitTests TEST_METHOD (MoveWindowIntoZoneByPointDropAddWindowToSameZone) { const auto window = Mocks::Window(); - const auto zoneWindow = Mocks::Window(); + const auto workArea = Mocks::Window(); winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }, 1); @@ -468,7 +468,7 @@ namespace FancyZonesUnitTests TEST_METHOD (MoveWindowIntoZoneByPointSeveralZonesWithSameWindow) { const auto window = Mocks::Window(); - const auto zoneWindow = Mocks::Window(); + const auto workArea = Mocks::Window(); winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }, 1); diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs index 36718455d..d09a892e1 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs @@ -439,7 +439,9 @@ namespace FancyZonesEditor SecondaryButtonText = Properties.Resources.Cancel, }; + Announce(FancyZonesEditor.Properties.Resources.Delete_Layout_Dialog_Announce, dialog.Content.ToString()); var result = await dialog.ShowAsync(); + if (result == ContentDialogResult.Primary) { LayoutModel model = element.DataContext as LayoutModel; diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.Designer.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.Designer.cs index 07f7f07cd..5e7b4a12a 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.Designer.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.Designer.cs @@ -231,6 +231,15 @@ namespace FancyZonesEditor.Properties { } } + /// + /// Looks up a localized string similar to Delete layout dialog.. + /// + public static string Delete_Layout_Dialog_Announce { + get { + return ResourceManager.GetString("Delete_Layout_Dialog_Announce", resourceCulture); + } + } + /// /// Looks up a localized string similar to Delete zone. /// diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.resx b/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.resx index e6345e165..635f97d19 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.resx +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Properties/Resources.resx @@ -285,6 +285,9 @@ Template settings + + Delete layout dialog. + Are you sure? diff --git a/src/modules/imageresizer/tests/ImageResizerUITest.csproj b/src/modules/imageresizer/tests/ImageResizerUITest.csproj index 5644684dd..3f6df92bc 100644 --- a/src/modules/imageresizer/tests/ImageResizerUITest.csproj +++ b/src/modules/imageresizer/tests/ImageResizerUITest.csproj @@ -28,6 +28,10 @@ x64 + + + + @@ -53,6 +57,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/src/modules/imageresizer/tests/Models/ResizeOperationTests.cs b/src/modules/imageresizer/tests/Models/ResizeOperationTests.cs index 93cd30291..ea22ccd05 100644 --- a/src/modules/imageresizer/tests/Models/ResizeOperationTests.cs +++ b/src/modules/imageresizer/tests/Models/ResizeOperationTests.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Windows.Media; using System.Windows.Media.Imaging; +using ImageResizer.Extensions; using ImageResizer.Properties; using ImageResizer.Test; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -441,6 +442,85 @@ namespace ImageResizer.Models Assert.IsTrue(File.Exists(_directory + @"\Directory\Test (Test).png")); } + [TestMethod] + public void StripMetadata() + { + var operation = new ResizeOperation( + "TestMetadataIssue1928.jpg", + _directory, + Settings( + x => + { + x.RemoveMetadata = true; + })); + + operation.Execute(); + + AssertEx.Image( + _directory.File(), + image => Assert.IsNull(((BitmapMetadata)image.Frames[0].Metadata).DateTaken)); + AssertEx.Image( + _directory.File(), + image => Assert.IsNotNull(((BitmapMetadata)image.Frames[0].Metadata).GetQuerySafe("System.Photo.Orientation"))); + } + + [TestMethod] + public void StripMetadataWhenNoMetadataPresent() + { + var operation = new ResizeOperation( + "TestMetadataIssue1928_NoMetadata.jpg", + _directory, + Settings( + x => + { + x.RemoveMetadata = true; + })); + + operation.Execute(); + + AssertEx.Image( + _directory.File(), + image => Assert.IsNull(((BitmapMetadata)image.Frames[0].Metadata).DateTaken)); + AssertEx.Image( + _directory.File(), + image => Assert.IsNull(((BitmapMetadata)image.Frames[0].Metadata).GetQuerySafe("System.Photo.Orientation"))); + } + + [TestMethod] + public void VerifyFileNameIsSanitized() + { + var operation = new ResizeOperation( + "Test.png", + _directory, + Settings( + s => + { + s.FileName = @"Directory\%1:*?""<>|(%2)"; + s.SelectedSize.Name = "Test\\/"; + })); + + operation.Execute(); + + Assert.IsTrue(File.Exists(_directory + @"\Directory\Test_______(Test__).png")); + } + + [TestMethod] + public void VerifyNotRecommendedNameIsChanged() + { + var operation = new ResizeOperation( + "Test.png", + _directory, + Settings( + s => + { + s.FileName = @"nul"; + })); + + operation.Execute(); + + Assert.IsTrue(File.Exists(_directory + @"\nul_.png")); + } + private static Settings Settings(Action action = null) { var settings = new Settings() diff --git a/src/modules/imageresizer/tests/TestMetadataIssue1928.jpg b/src/modules/imageresizer/tests/TestMetadataIssue1928.jpg new file mode 100644 index 000000000..4d3f266b6 Binary files /dev/null and b/src/modules/imageresizer/tests/TestMetadataIssue1928.jpg differ diff --git a/src/modules/imageresizer/tests/TestMetadataIssue1928_NoMetadata.jpg b/src/modules/imageresizer/tests/TestMetadataIssue1928_NoMetadata.jpg new file mode 100644 index 000000000..4b85fb4f4 Binary files /dev/null and b/src/modules/imageresizer/tests/TestMetadataIssue1928_NoMetadata.jpg differ diff --git a/src/modules/imageresizer/ui/Extensions/BitmapMetadataExtension.cs b/src/modules/imageresizer/ui/Extensions/BitmapMetadataExtension.cs new file mode 100644 index 000000000..d6885c75e --- /dev/null +++ b/src/modules/imageresizer/ui/Extensions/BitmapMetadataExtension.cs @@ -0,0 +1,238 @@ +// 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.Windows.Media.Imaging; + +namespace ImageResizer.Extensions +{ + internal static class BitmapMetadataExtension + { + public static void CopyMetadataPropertyTo(this BitmapMetadata source, BitmapMetadata target, string query) + { + if (source == null || target == null || string.IsNullOrWhiteSpace(query)) + { + return; + } + + try + { + var value = source.GetQuerySafe(query); + + if (value == null) + { + return; + } + + target.SetQuery(query, value); + } + catch (InvalidOperationException) + { + // InvalidOperationException is thrown if metadata object is in readonly state. + return; + } + } + + public static object GetQuerySafe(this BitmapMetadata metadata, string query) + { + if (metadata == null || string.IsNullOrWhiteSpace(query)) + { + return null; + } + + try + { + if (metadata.ContainsQuery(query)) + { + return metadata.GetQuery(query); + } + else + { + return null; + } + } + catch (NotSupportedException) + { + // NotSupportedException is throw if the metadata entry is not preset on the target image (e.g. Orientation not set). + return null; + } + } + + public static void RemoveQuerySafe(this BitmapMetadata metadata, string query) + { + if (metadata == null || string.IsNullOrWhiteSpace(query)) + { + return; + } + + try + { + if (metadata.ContainsQuery(query)) + { + metadata.RemoveQuery(query); + } + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + Debug.WriteLine($"Exception while trying to remove metadata entry at position: {query}"); + Debug.WriteLine(ex); + } + } + + public static void SetQuerySafe(this BitmapMetadata metadata, string query, object value) + { + if (metadata == null || string.IsNullOrWhiteSpace(query) || value == null) + { + return; + } + + try + { + metadata.SetQuery(query, value); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + Debug.WriteLine($"Exception while trying to set metadata {value} at position: {query}"); + Debug.WriteLine(ex); + } + } + + /// + /// Gets all metadata + /// Iterates recursively through all metadata + /// + public static List<(string metadataPath, object value)> GetListOfMetadata(this BitmapMetadata metadata) + { + var listOfAllMetadata = new List<(string metadataPath, object value)>(); + + GetMetadataRecursively(metadata, string.Empty); + + return listOfAllMetadata; + + void GetMetadataRecursively(BitmapMetadata metadata, string query) + { + foreach (string relativeQuery in metadata) + { + string absolutePath = query + relativeQuery; + + object metadataQueryReader = null; + + try + { + metadataQueryReader = GetQueryWithPreCheck(metadata, relativeQuery); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + Debug.WriteLine($"Removing corrupt metadata property {absolutePath}. Skipping metadata entry | {ex.Message}"); + Debug.WriteLine(ex); + } + + if (metadataQueryReader != null) + { + listOfAllMetadata.Add((absolutePath, metadataQueryReader)); + } + else + { + Debug.WriteLine($"No metadata found for query {absolutePath}. Skipping empty null entry because its invalid."); + } + + if (metadataQueryReader is BitmapMetadata innerMetadata) + { + GetMetadataRecursively(innerMetadata, absolutePath); + } + } + } + + object GetQueryWithPreCheck(BitmapMetadata metadata, string query) + { + if (metadata == null || string.IsNullOrWhiteSpace(query)) + { + return null; + } + + if (metadata.ContainsQuery(query)) + { + return metadata.GetQuery(query); + } + else + { + return null; + } + } + } + + /// + /// Prints all metadata to debug console + /// + /// + /// Intented for debug only!!! + /// + public static void PrintsAllMetadataToDebugOutput(this BitmapMetadata metadata) + { + if (metadata == null) + { + Debug.WriteLine($"Metadata was null."); + } + + var listOfMetadata = metadata.GetListOfMetadataForDebug(); + foreach (var metadataItem in listOfMetadata) + { + // Debug.WriteLine($"modifiableMetadata.RemoveQuerySafe(\"{metadataItem.metadataPath}\");"); + Debug.WriteLine($"{metadataItem.metadataPath} | {metadataItem.value}"); + } + } + + /// + /// Gets all metadata + /// Iterates recursively through all metadata + /// + /// + /// Intented for debug only!!! + /// + public static List<(string metadataPath, object value)> GetListOfMetadataForDebug(this BitmapMetadata metadata) + { + var listOfAllMetadata = new List<(string metadataPath, object value)>(); + + GetMetadataRecursively(metadata, string.Empty); + + return listOfAllMetadata; + + void GetMetadataRecursively(BitmapMetadata metadata, string query) + { + foreach (string relativeQuery in metadata) + { + string absolutePath = query + relativeQuery; + + object metadataQueryReader = null; + + try + { + metadataQueryReader = metadata.GetQuerySafe(relativeQuery); + listOfAllMetadata.Add((absolutePath, metadataQueryReader)); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + listOfAllMetadata.Add((absolutePath, $"######## INVALID METADATA: {ex.Message}")); + Debug.WriteLine(ex); + } + + if (metadataQueryReader is BitmapMetadata innerMetadata) + { + GetMetadataRecursively(innerMetadata, absolutePath); + } + } + } + } + } +} diff --git a/src/modules/imageresizer/ui/Models/ResizeOperation.cs b/src/modules/imageresizer/ui/Models/ResizeOperation.cs index 11acf1ccd..f8b57265b 100644 --- a/src/modules/imageresizer/ui/Models/ResizeOperation.cs +++ b/src/modules/imageresizer/ui/Models/ResizeOperation.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/ using System; +using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Abstractions; @@ -10,6 +11,7 @@ using System.Linq; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; +using ImageResizer.Extensions; using ImageResizer.Properties; using ImageResizer.Utilities; using Microsoft.VisualBasic.FileIO; @@ -25,6 +27,14 @@ namespace ImageResizer.Models private readonly string _destinationDirectory; private readonly Settings _settings; + // Filenames to avoid according to https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#file-and-directory-names + private static readonly string[] _avoidFilenames = + { + "CON", "PRN", "AUX", "NUL", + "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", + "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", + }; + public ResizeOperation(string file, string destinationDirectory, Settings settings) { _file = file; @@ -74,20 +84,56 @@ namespace ImageResizer.Models try { // Detect whether metadata can copied successfully - _ = metadata.Clone(); + var modifiableMetadata = metadata.Clone(); + +#if DEBUG + Debug.WriteLine($"### Processing metadata of file {_file}"); + modifiableMetadata.PrintsAllMetadataToDebugOutput(); +#endif + + // read all metadata and build up metadata object from the scratch. Discard invalid (unreadable/unwritable) metadata. + var newMetadata = new BitmapMetadata(metadata.Format); + var listOfMetadata = modifiableMetadata.GetListOfMetadata(); + foreach (var (metadataPath, value) in listOfMetadata) + { + if (value is BitmapMetadata bitmapMetadata) + { + var innerMetadata = new BitmapMetadata(bitmapMetadata.Format); + newMetadata.SetQuerySafe(metadataPath, innerMetadata); + } + else + { + newMetadata.SetQuerySafe(metadataPath, value); + } + } + + metadata = newMetadata; } - catch (ArgumentException) + catch (ArgumentException ex) { metadata = null; + + Debug.WriteLine(ex); } } + if (_settings.RemoveMetadata && metadata != null) + { + // strip any metadata that doesn't affect rendering + var newMetadata = new BitmapMetadata(metadata.Format); + + metadata.CopyMetadataPropertyTo(newMetadata, "System.Photo.Orientation"); + metadata.CopyMetadataPropertyTo(newMetadata, "System.Image.ColorSpace"); + + metadata = newMetadata; + } + encoder.Frames.Add( BitmapFrame.Create( Transform(originalFrame), - thumbnail: null, - metadata, // TODO: Add an option to strip any metadata that doesn't affect rendering (issue #3) - colorContexts: null)); + originalFrame.Thumbnail, + metadata, + originalFrame.ColorContexts)); } path = GetDestinationPath(encoder); @@ -193,16 +239,39 @@ namespace ImageResizer.Models extension = supportedExtensions.FirstOrDefault(); } + // Remove directory characters from the size's name. + string sizeNameSanitized = _settings.SelectedSize.Name; + sizeNameSanitized = sizeNameSanitized + .Replace('\\', '_') + .Replace('/', '_'); + // Using CurrentCulture since this is user facing var fileName = string.Format( CultureInfo.CurrentCulture, _settings.FileNameFormat, originalFileName, - _settings.SelectedSize.Name, + sizeNameSanitized, _settings.SelectedSize.Width, _settings.SelectedSize.Height, encoder.Frames[0].PixelWidth, encoder.Frames[0].PixelHeight); + + // Remove invalid characters from the final file name. + fileName = fileName + .Replace(':', '_') + .Replace('*', '_') + .Replace('?', '_') + .Replace('"', '_') + .Replace('<', '_') + .Replace('>', '_') + .Replace('|', '_'); + + // Avoid creating not recommended filenames + if (_avoidFilenames.Contains(fileName.ToUpperInvariant())) + { + fileName = fileName + "_"; + } + var path = _fileSystem.Path.Combine(directory, fileName + extension); var uniquifier = 1; while (_fileSystem.File.Exists(path)) diff --git a/src/modules/imageresizer/ui/Properties/Resources.Designer.cs b/src/modules/imageresizer/ui/Properties/Resources.Designer.cs index adce4cd85..fc6e78ecc 100644 --- a/src/modules/imageresizer/ui/Properties/Resources.Designer.cs +++ b/src/modules/imageresizer/ui/Properties/Resources.Designer.cs @@ -141,6 +141,15 @@ namespace ImageResizer.Properties { } } + /// + /// Looks up a localized string similar to Remove metadata that doesn't affect rendering. + /// + public static string Input_RemoveMetadata { + get { + return ResourceManager.GetString("Input_RemoveMetadata", resourceCulture); + } + } + /// /// Looks up a localized string similar to R_esize the original pictures (don't create copies). /// diff --git a/src/modules/imageresizer/ui/Properties/Resources.resx b/src/modules/imageresizer/ui/Properties/Resources.resx index 06c013e61..fb2df3874 100644 --- a/src/modules/imageresizer/ui/Properties/Resources.resx +++ b/src/modules/imageresizer/ui/Properties/Resources.resx @@ -280,4 +280,7 @@ Open settings + + Remove metadata that doesn't affect rendering + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/Properties/Settings.cs b/src/modules/imageresizer/ui/Properties/Settings.cs index 7c3eadf75..2e3623386 100644 --- a/src/modules/imageresizer/ui/Properties/Settings.cs +++ b/src/modules/imageresizer/ui/Properties/Settings.cs @@ -30,6 +30,7 @@ namespace ImageResizer.Properties private int _selectedSizeIndex; private bool _replace; private bool _ignoreOrientation; + private bool _removeMetadata; private int _jpegQualityLevel; private PngInterlaceOption _pngInterlaceOption; private TiffCompressOption _tiffCompressOption; @@ -44,6 +45,7 @@ namespace ImageResizer.Properties ShrinkOnly = false; Replace = false; IgnoreOrientation = true; + RemoveMetadata = false; JpegQualityLevel = 90; PngInterlaceOption = System.Windows.Media.Imaging.PngInterlaceOption.Default; TiffCompressOption = System.Windows.Media.Imaging.TiffCompressOption.Default; @@ -280,6 +282,27 @@ namespace ImageResizer.Properties } } + /// + /// Gets or sets a value indicating whether resizing images removes any metadata that doesn't affect rendering. + /// Default is false. + /// + /// + /// Preserved Metadata: + /// System.Photo.Orientation, + /// System.Image.ColorSpace + /// + [JsonConverter(typeof(WrappedJsonValueConverter))] + [JsonPropertyName("imageresizer_removeMetadata")] + public bool RemoveMetadata + { + get => _removeMetadata; + set + { + _removeMetadata = value; + NotifyPropertyChanged(); + } + } + [JsonConverter(typeof(WrappedJsonValueConverter))] [JsonPropertyName("imageresizer_jpegQualityLevel")] public int JpegQualityLevel @@ -423,6 +446,7 @@ namespace ImageResizer.Properties ShrinkOnly = jsonSettings.ShrinkOnly; Replace = jsonSettings.Replace; IgnoreOrientation = jsonSettings.IgnoreOrientation; + RemoveMetadata = jsonSettings.RemoveMetadata; JpegQualityLevel = jsonSettings.JpegQualityLevel; PngInterlaceOption = jsonSettings.PngInterlaceOption; TiffCompressOption = jsonSettings.TiffCompressOption; diff --git a/src/modules/imageresizer/ui/Views/InputPage.xaml b/src/modules/imageresizer/ui/Views/InputPage.xaml index e2410c86e..3015001a2 100644 --- a/src/modules/imageresizer/ui/Views/InputPage.xaml +++ b/src/modules/imageresizer/ui/Views/InputPage.xaml @@ -122,15 +122,19 @@ + + - + + Content="{x:Static p:Resources.Input_RemoveMetadata}" + IsChecked="{Binding Settings.RemoveMetadata}"/> {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + {6955446d-23f7-4023-9bb3-8657f904af99} + diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Community.PowerToys.Run.Plugin.UnitConverter.csproj b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Community.PowerToys.Run.Plugin.UnitConverter.csproj index eee4deed8..b28dcf4bf 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Community.PowerToys.Run.Plugin.UnitConverter.csproj +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Community.PowerToys.Run.Plugin.UnitConverter.csproj @@ -17,7 +17,7 @@ true - ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\Community.UnitConverter\ + ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\UnitConverter\ DEBUG;TRACE full x64 @@ -30,7 +30,7 @@ - ..\..\..\..\..\x64\Release\modules\launcher\Plugins\Community.UnitConverter\ + ..\..\..\..\..\x64\Release\modules\launcher\Plugins\UnitConverter\ TRACE true pdbonly diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Main.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Main.cs index ecfdad00f..c013015d6 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Main.cs +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Main.cs @@ -44,13 +44,9 @@ namespace Community.PowerToys.Run.Plugin.VSCodeWorkspaces var title = $"{a.FolderName}"; var typeWorkspace = a.WorkspaceTypeToString(); - if (a.TypeWorkspace == TypeWorkspace.Codespaces) + if (a.TypeWorkspace != TypeWorkspace.Local) { - title += $" - {typeWorkspace}"; - } - else if (a.TypeWorkspace != TypeWorkspace.Local) - { - title += $" - {(a.ExtraInfo != null ? $"{a.ExtraInfo} ({typeWorkspace})" : typeWorkspace)}"; + title = $"{title}{(a.ExtraInfo != null ? $" - {a.ExtraInfo}" : string.Empty)} ({typeWorkspace})"; } var tooltip = new ToolTipData(title, $"{Resources.Workspace}{(a.TypeWorkspace != TypeWorkspace.Local ? $" {Resources.In} {typeWorkspace}" : string.Empty)}: {SystemPath.RealPath(a.RelativePath)}"); diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Properties/Resources.Designer.cs index 27e97a8d6..6a4647d70 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Properties/Resources.Designer.cs @@ -105,6 +105,15 @@ namespace Community.PowerToys.Run.Plugin.VSCodeWorkspaces.Properties { } } + /// + /// Looks up a localized string similar to Dev Container. + /// + internal static string TypeWorkspaceDevContainer { + get { + return ResourceManager.GetString("TypeWorkspaceDevContainer", resourceCulture); + } + } + /// /// Looks up a localized string similar to Local. /// diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Properties/Resources.resx b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Properties/Resources.resx index 4698bff23..fb8ab7840 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Properties/Resources.resx @@ -142,4 +142,8 @@ Workspace It refers to the "Visual Studio Code workspace" + + Dev Container + As in "Visual Studio Code Dev Container workspace " + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/WorkspacesHelper/ParseVSCodeUri.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/WorkspacesHelper/ParseVSCodeUri.cs index 6978aa636..47e924544 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/WorkspacesHelper/ParseVSCodeUri.cs +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/WorkspacesHelper/ParseVSCodeUri.cs @@ -16,6 +16,8 @@ namespace Community.PowerToys.Run.Plugin.VSCodeWorkspaces.WorkspacesHelper private static readonly Regex CodespacesWorkspace = new Regex(@"^vscode-remote://vsonline\+(.+?(?=\/))(.+)$", RegexOptions.Compiled); + private static readonly Regex DevContainerWorkspace = new Regex(@"^vscode-remote://dev-container\+(.+?(?=\/))(.+)$", RegexOptions.Compiled); + public static (TypeWorkspace? TypeWorkspace, string MachineName, string Path) GetTypeWorkspace(string uri) { if (LocalWorkspace.IsMatch(uri)) @@ -51,7 +53,16 @@ namespace Community.PowerToys.Run.Plugin.VSCodeWorkspaces.WorkspacesHelper if (match.Groups.Count > 1) { - return (TypeWorkspace.Codespaces, string.Empty, match.Groups[2].Value); + return (TypeWorkspace.Codespaces, null, match.Groups[2].Value); + } + } + else if (DevContainerWorkspace.IsMatch(uri)) + { + var match = DevContainerWorkspace.Match(uri); + + if (match.Groups.Count > 1) + { + return (TypeWorkspace.DevContainer, null, match.Groups[2].Value); } } diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/WorkspacesHelper/VSCodeWorkspace.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/WorkspacesHelper/VSCodeWorkspace.cs index 01aa2ea14..9685b1115 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/WorkspacesHelper/VSCodeWorkspace.cs +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/WorkspacesHelper/VSCodeWorkspace.cs @@ -30,6 +30,7 @@ namespace Community.PowerToys.Run.Plugin.VSCodeWorkspaces.WorkspacesHelper case TypeWorkspace.RemoteContainers: return Resources.TypeWorkspaceContainer; case TypeWorkspace.RemoteSSH: return "SSH"; case TypeWorkspace.RemoteWSL: return "WSL"; + case TypeWorkspace.DevContainer: return Resources.TypeWorkspaceDevContainer; } return string.Empty; @@ -43,5 +44,6 @@ namespace Community.PowerToys.Run.Plugin.VSCodeWorkspaces.WorkspacesHelper RemoteWSL = 3, RemoteSSH = 4, RemoteContainers = 5, + DevContainer = 6, } } diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Microsoft.Plugin.Folder.csproj b/src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Microsoft.Plugin.Folder.csproj index ff9960832..7bcf026ce 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Microsoft.Plugin.Folder.csproj +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Microsoft.Plugin.Folder.csproj @@ -17,7 +17,7 @@ true - ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\Microsoft.Plugin.Folder\ + ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\Folder\ DEBUG;TRACE full x64 @@ -30,7 +30,7 @@ - ..\..\..\..\..\x64\Release\modules\launcher\Plugins\Microsoft.Plugin.Folder\ + ..\..\..\..\..\x64\Release\modules\launcher\Plugins\Folder\ TRACE true pdbonly diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Microsoft.Plugin.Indexer.csproj b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Microsoft.Plugin.Indexer.csproj index fb8aa6bf0..2f5a9ba3f 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Microsoft.Plugin.Indexer.csproj +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Microsoft.Plugin.Indexer.csproj @@ -17,7 +17,7 @@ true - ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\Microsoft.Plugin.Indexer\ + ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\Indexer\ DEBUG;TRACE full x64 @@ -30,7 +30,7 @@ - ..\..\..\..\..\x64\Release\modules\launcher\Plugins\Microsoft.Plugin.Indexer\ + ..\..\..\..\..\x64\Release\modules\launcher\Plugins\Indexer\ TRACE true pdbonly diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Microsoft.Plugin.Program.csproj b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Microsoft.Plugin.Program.csproj index e6a644028..8b593159a 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Microsoft.Plugin.Program.csproj +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Microsoft.Plugin.Program.csproj @@ -18,7 +18,7 @@ true - ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\Microsoft.Plugin.Program\ + ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\Program\ DEBUG;TRACE full x64 @@ -31,7 +31,7 @@ - ..\..\..\..\..\x64\Release\modules\launcher\Plugins\Microsoft.Plugin.Program\ + ..\..\..\..\..\x64\Release\modules\launcher\Plugins\Program\ TRACE true pdbonly diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Microsoft.Plugin.Shell.csproj b/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Microsoft.Plugin.Shell.csproj index f3f37b892..3893af0f1 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Microsoft.Plugin.Shell.csproj +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Microsoft.Plugin.Shell.csproj @@ -17,7 +17,7 @@ true - ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\Microsoft.Plugin.Shell\ + ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\Shell\ DEBUG;TRACE full x64 @@ -29,7 +29,7 @@ - ..\..\..\..\..\x64\Release\modules\launcher\Plugins\Microsoft.Plugin.Shell\ + ..\..\..\..\..\x64\Release\modules\launcher\Plugins\Shell\ TRACE true pdbonly diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri.UnitTests/UriHelper/ExtendedUriParserTests.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri.UnitTests/UriHelper/ExtendedUriParserTests.cs index 8da6c03a0..8a960a5f1 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri.UnitTests/UriHelper/ExtendedUriParserTests.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri.UnitTests/UriHelper/ExtendedUriParserTests.cs @@ -11,62 +11,85 @@ namespace Microsoft.Plugin.Uri.UnitTests.UriHelper public class ExtendedUriParserTests { [DataTestMethod] - [DataRow("google.com", true, "https://google.com/")] - [DataRow("http://google.com", true, "http://google.com/")] - [DataRow("localhost", true, "https://localhost/")] - [DataRow("http://localhost", true, "http://localhost/")] - [DataRow("127.0.0.1", true, "https://127.0.0.1/")] - [DataRow("http://127.0.0.1", true, "http://127.0.0.1/")] - [DataRow("http://127.0.0.1:80", true, "http://127.0.0.1/")] - [DataRow("127", false, null)] - [DataRow("", false, null)] - [DataRow("https://google.com", true, "https://google.com/")] - [DataRow("ftps://google.com", true, "ftps://google.com/")] - [DataRow(null, false, null)] - [DataRow("bing.com/search?q=gmx", true, "https://bing.com/search?q=gmx")] - [DataRow("http://bing.com/search?q=gmx", true, "http://bing.com/search?q=gmx")] - [DataRow("h", true, "https://h/")] - [DataRow("http://h", true, "http://h/")] - [DataRow("ht", true, "https://ht/")] - [DataRow("http://ht", true, "http://ht/")] - [DataRow("htt", true, "https://htt/")] - [DataRow("http://htt", true, "http://htt/")] - [DataRow("http", true, "https://http/")] - [DataRow("http://http", true, "http://http/")] - [DataRow("http:", false, null)] - [DataRow("http:/", false, null)] - [DataRow("http://", false, null)] - [DataRow("http://t", true, "http://t/")] - [DataRow("http://te", true, "http://te/")] - [DataRow("http://tes", true, "http://tes/")] - [DataRow("http://test", true, "http://test/")] - [DataRow("http://test.", false, null)] - [DataRow("http://test.c", true, "http://test.c/")] - [DataRow("http://test.co", true, "http://test.co/")] - [DataRow("http://test.com", true, "http://test.com/")] - [DataRow("http:3", true, "https://http:3/")] - [DataRow("http://http:3", true, "http://http:3/")] - [DataRow("[::]", true, "https://[::]/")] - [DataRow("http://[::]", true, "http://[::]/")] - [DataRow("[2001:0DB8::1]", true, "https://[2001:db8::1]/")] - [DataRow("http://[2001:0DB8::1]", true, "http://[2001:db8::1]/")] - [DataRow("[2001:0DB8::1]:80", true, "https://[2001:db8::1]/")] - [DataRow("http://[2001:0DB8::1]:80", true, "http://[2001:db8::1]/")] - [DataRow("mailto:example@mail.com", true, "mailto:example@mail.com")] - [DataRow("tel:411", true, "tel:411")] - [DataRow("ftp://example.com", true, "ftp://example.com/")] - [DataRow("example.com:443", true, "example.com:443")] + [DataRow("google.com", true, "https://google.com/", true)] + [DataRow("http://google.com", true, "http://google.com/", true)] + [DataRow("localhost", true, "https://localhost/", true)] + [DataRow("http://localhost", true, "http://localhost/", true)] + [DataRow("127.0.0.1", true, "https://127.0.0.1/", true)] + [DataRow("http://127.0.0.1", true, "http://127.0.0.1/", true)] + [DataRow("http://127.0.0.1:80", true, "http://127.0.0.1/", true)] + [DataRow("127", false, null, false)] + [DataRow("", false, null, false)] + [DataRow("https://google.com", true, "https://google.com/", true)] + [DataRow("ftps://google.com", true, "ftps://google.com/", false)] + [DataRow(null, false, null, false)] + [DataRow("bing.com/search?q=gmx", true, "https://bing.com/search?q=gmx", true)] + [DataRow("http://bing.com/search?q=gmx", true, "http://bing.com/search?q=gmx", true)] + [DataRow("h", true, "https://h/", true)] + [DataRow("http://h", true, "http://h/", true)] + [DataRow("ht", true, "https://ht/", true)] + [DataRow("http://ht", true, "http://ht/", true)] + [DataRow("htt", true, "https://htt/", true)] + [DataRow("http://htt", true, "http://htt/", true)] + [DataRow("http", true, "https://http/", true)] + [DataRow("http://http", true, "http://http/", true)] + [DataRow("http:", false, null, false)] + [DataRow("http:/", false, null, false)] + [DataRow("http://", false, null, false)] + [DataRow("http://t", true, "http://t/", true)] + [DataRow("http://te", true, "http://te/", true)] + [DataRow("http://tes", true, "http://tes/", true)] + [DataRow("http://test", true, "http://test/", true)] + [DataRow("http://test.", false, null, false)] + [DataRow("http://test.c", true, "http://test.c/", true)] + [DataRow("http://test.co", true, "http://test.co/", true)] + [DataRow("http://test.com", true, "http://test.com/", true)] + [DataRow("http:3", true, "https://http:3/", true)] + [DataRow("http://http:3", true, "http://http:3/", true)] + [DataRow("[::]", true, "https://[::]/", true)] + [DataRow("http://[::]", true, "http://[::]/", true)] + [DataRow("[2001:0DB8::1]", true, "https://[2001:db8::1]/", true)] + [DataRow("http://[2001:0DB8::1]", true, "http://[2001:db8::1]/", true)] + [DataRow("[2001:0DB8::1]:80", true, "https://[2001:db8::1]/", true)] + [DataRow("http://[2001:0DB8::1]:80", true, "http://[2001:db8::1]/", true)] + [DataRow("mailto:example@mail.com", true, "mailto:example@mail.com", false)] + [DataRow("tel:411", true, "tel:411", false)] + [DataRow("ftp://example.com", true, "ftp://example.com/", false)] - public void TryParseCanParseHostName(string query, bool expectedSuccess, string expectedResult) + // This has been parsed as an application URI. Linked issue: #14260 + [DataRow("example.com:443", true, "example.com:443", false)] + [DataRow("mailto:", true, "mailto:", false)] + [DataRow("mailto:/", false, null, false)] + [DataRow("ms-settings:", true, "ms-settings:", false)] + [DataRow("ms-settings:/", false, null, false)] + [DataRow("ms-settings://", false, null, false)] + [DataRow("ms-settings://privacy", true, "ms-settings://privacy/", false)] + [DataRow("ms-settings://privacy/", true, "ms-settings://privacy/", false)] + [DataRow("ms-settings:privacy", true, "ms-settings:privacy", false)] + [DataRow("ms-settings:powersleep", true, "ms-settings:powersleep", false)] + [DataRow("microsoft-edge:http://google.com", true, "microsoft-edge:http://google.com", false)] + [DataRow("microsoft-edge:https://google.com", true, "microsoft-edge:https://google.com", false)] + [DataRow("microsoft-edge:google.com", true, "microsoft-edge:google.com", false)] + [DataRow("microsoft-edge:google.com/", true, "microsoft-edge:google.com/", false)] + [DataRow("microsoft-edge:https://google.com/", true, "microsoft-edge:https://google.com/", false)] + [DataRow("ftp://user:password@localhost:8080", true, "ftp://user:password@localhost:8080/", false)] + [DataRow("ftp://user:password@localhost:8080/", true, "ftp://user:password@localhost:8080/", false)] + [DataRow("ftp://user:password@google.com", true, "ftp://user:password@google.com/", false)] + [DataRow("ftp://user:password@google.com:2121", true, "ftp://user:password@google.com:2121/", false)] + [DataRow("ftp://user:password@1.1.1.1", true, "ftp://user:password@1.1.1.1/", false)] + [DataRow("ftp://user:password@1.1.1.1:2121", true, "ftp://user:password@1.1.1.1:2121/", false)] + + public void TryParseCanParseHostName(string query, bool expectedSuccess, string expectedResult, bool expectedIsWebUri) { // Arrange var parser = new ExtendedUriParser(); // Act - var success = parser.TryParse(query, out var result); + var success = parser.TryParse(query, out var result, out var isWebUriResult); // Assert Assert.AreEqual(expectedResult, result?.ToString()); + Assert.AreEqual(expectedIsWebUri, isWebUriResult); Assert.AreEqual(expectedSuccess, success); } } diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Images/uri.dark.png b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Images/uri.dark.png index 6c34ed158..e08071730 100644 Binary files a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Images/uri.dark.png and b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Images/uri.dark.png differ diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Images/uri.light.png b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Images/uri.light.png index bea54d005..26722696a 100644 Binary files a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Images/uri.light.png and b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Images/uri.light.png differ diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IUriParser.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IUriParser.cs index aa0154bc3..bb72ae324 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IUriParser.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IUriParser.cs @@ -6,6 +6,6 @@ namespace Microsoft.Plugin.Uri.Interfaces { public interface IUriParser { - bool TryParse(string input, out System.Uri result); + bool TryParse(string input, out System.Uri result, out bool isWebUri); } } diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Main.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Main.cs index a1ea328fa..bea9768a9 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Main.cs @@ -63,17 +63,15 @@ namespace Microsoft.Plugin.Uri { results.Add(new Result { - Title = Properties.Resources.Microsoft_plugin_uri_default_browser, + Title = Properties.Resources.Microsoft_plugin_uri_open, SubTitle = BrowserPath, - IcoPath = _uriSettings.ShowBrowserIcon - ? BrowserIconPath - : DefaultIconPath, + IcoPath = DefaultIconPath, Action = action => { if (!Helper.OpenInShell(BrowserPath)) { var title = $"Plugin: {Properties.Resources.Microsoft_plugin_uri_plugin_name}"; - var message = $"{Properties.Resources.Microsoft_plugin_default_browser_open_failed}: "; + var message = $"{Properties.Resources.Microsoft_plugin_uri_open_failed}: "; Context.API.ShowMsg(title, message); return false; } @@ -85,16 +83,19 @@ namespace Microsoft.Plugin.Uri } if (!string.IsNullOrEmpty(query?.Search) - && _uriParser.TryParse(query.Search, out var uriResult) + && _uriParser.TryParse(query.Search, out var uriResult, out var isWebUri) && _uriResolver.IsValidHost(uriResult)) { var uriResultString = uriResult.ToString(); + var isWebUriBool = isWebUri; results.Add(new Result { Title = uriResultString, - SubTitle = Properties.Resources.Microsoft_plugin_uri_website, - IcoPath = _uriSettings.ShowBrowserIcon + SubTitle = isWebUriBool + ? Properties.Resources.Microsoft_plugin_uri_website + : Properties.Resources.Microsoft_plugin_uri_open, + IcoPath = isWebUriBool ? BrowserIconPath : DefaultIconPath, Action = action => @@ -118,7 +119,7 @@ namespace Microsoft.Plugin.Uri private static bool IsActivationKeyword(Query query) { return !string.IsNullOrEmpty(query?.ActionKeyword) - && query?.ActionKeyword == query?.RawQuery; + && query?.ActionKeyword == query?.RawQuery; } private bool IsDefaultBrowserSet() @@ -155,16 +156,23 @@ namespace Microsoft.Plugin.Uri UpdateBrowserIconPath(newTheme); } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive but will log the exception")] + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Design", + "CA1031:Do not catch general exception types", + Justification = "We want to keep the process alive but will log the exception")] private void UpdateBrowserIconPath(Theme newTheme) { try { - var progId = _registryWrapper.GetRegistryValue("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice", "ProgId"); + var progId = _registryWrapper.GetRegistryValue( + "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice", + "ProgId"); var programLocation = // Resolve App Icon (UWP) - _registryWrapper.GetRegistryValue("HKEY_CLASSES_ROOT\\" + progId + "\\Application", "ApplicationIcon") + _registryWrapper.GetRegistryValue( + "HKEY_CLASSES_ROOT\\" + progId + "\\Application", + "ApplicationIcon") // Resolves default file association icon (UWP + Normal) ?? _registryWrapper.GetRegistryValue("HKEY_CLASSES_ROOT\\" + progId + "\\DefaultIcon", null); @@ -174,14 +182,21 @@ namespace Microsoft.Plugin.Uri if (programLocation.StartsWith("@", StringComparison.Ordinal)) { var directProgramLocationStringBuilder = new StringBuilder(128); - if (NativeMethods.SHLoadIndirectString(programLocation, directProgramLocationStringBuilder, (uint)directProgramLocationStringBuilder.Capacity, IntPtr.Zero) == + if (NativeMethods.SHLoadIndirectString( + programLocation, + directProgramLocationStringBuilder, + (uint)directProgramLocationStringBuilder.Capacity, + IntPtr.Zero) == NativeMethods.Hresult.Ok) { // Check if there's a postfix with contract-white/contrast-black icon is available and use that instead var directProgramLocation = directProgramLocationStringBuilder.ToString(); - var themeIcon = newTheme == Theme.Light || newTheme == Theme.HighContrastWhite ? "contrast-white" : "contrast-black"; + var themeIcon = newTheme == Theme.Light || newTheme == Theme.HighContrastWhite + ? "contrast-white" + : "contrast-black"; var extension = Path.GetExtension(directProgramLocation); - var themedProgLocation = $"{directProgramLocation.Substring(0, directProgramLocation.Length - extension.Length)}_{themeIcon}{extension}"; + var themedProgLocation = + $"{directProgramLocation.Substring(0, directProgramLocation.Length - extension.Length)}_{themeIcon}{extension}"; BrowserIconPath = File.Exists(themedProgLocation) ? themedProgLocation : directProgramLocation; diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Microsoft.Plugin.Uri.csproj b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Microsoft.Plugin.Uri.csproj index dfed38204..9e476c813 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Microsoft.Plugin.Uri.csproj +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Microsoft.Plugin.Uri.csproj @@ -17,7 +17,7 @@ true - ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\Microsoft.Plugin.Uri\ + ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\Uri\ DEBUG;TRACE full x64 @@ -30,7 +30,7 @@ - ..\..\..\..\..\x64\Release\modules\launcher\Plugins\Microsoft.Plugin.Uri\ + ..\..\..\..\..\x64\Release\modules\launcher\Plugins\Uri\ TRACE true pdbonly diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Properties/Resources.Designer.cs index 71c6832a1..0d073767a 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Properties/Resources.Designer.cs @@ -70,7 +70,7 @@ namespace Microsoft.Plugin.Uri.Properties { } /// - /// Looks up a localized string similar to Open Default Browser. + /// Looks up a localized string similar to Open default browser. /// public static string Microsoft_plugin_uri_default_browser { get { @@ -79,7 +79,16 @@ namespace Microsoft.Plugin.Uri.Properties { } /// - /// Looks up a localized string similar to Failed to open URL. + /// Looks up a localized string similar to Open URI. + /// + public static string Microsoft_plugin_uri_open { + get { + return ResourceManager.GetString("Microsoft_plugin_uri_open", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to open URI. /// public static string Microsoft_plugin_uri_open_failed { get { diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Properties/Resources.resx index 6ce700dde..19c0a3903 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Properties/Resources.resx @@ -123,8 +123,11 @@ Open default browser + + Open URI + - Failed to open URL + Failed to open URI Opens URLs and UNC network shares. @@ -135,4 +138,4 @@ Open in default browser - + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/ExtendedUriParser.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/ExtendedUriParser.cs index 322b2a5ef..82d232750 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/ExtendedUriParser.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/ExtendedUriParser.cs @@ -10,22 +10,37 @@ namespace Microsoft.Plugin.Uri.UriHelper { public class ExtendedUriParser : IUriParser { - public bool TryParse(string input, out System.Uri result) + public bool TryParse(string input, out System.Uri result, out bool isWebUri) { if (string.IsNullOrEmpty(input)) { result = default; + isWebUri = false; return false; } + // Handling URL with only scheme, typically mailto or application uri. + // Do nothing, return the result without urlBuilder + if (input.EndsWith(":", StringComparison.OrdinalIgnoreCase) + && !input.StartsWith("http", StringComparison.OrdinalIgnoreCase) + && !input.Contains("/", StringComparison.OrdinalIgnoreCase) + && !input.All(char.IsDigit)) + { + result = new System.Uri(input); + isWebUri = false; + return true; + } + // Handle common cases UriBuilder does not handle // Using CurrentCulture since this is a user typed string if (input.EndsWith(":", StringComparison.CurrentCulture) || input.EndsWith(".", StringComparison.CurrentCulture) || input.EndsWith(":/", StringComparison.CurrentCulture) + || input.EndsWith("://", StringComparison.CurrentCulture) || input.All(char.IsDigit)) { result = default; + isWebUri = false; return false; } @@ -35,27 +50,31 @@ namespace Microsoft.Plugin.Uri.UriHelper var hadDefaultPort = urlBuilder.Uri.IsDefaultPort; urlBuilder.Port = hadDefaultPort ? -1 : urlBuilder.Port; - if (input.Contains("HTTP://", StringComparison.OrdinalIgnoreCase)) + if (input.StartsWith("HTTP://", StringComparison.OrdinalIgnoreCase)) { urlBuilder.Scheme = System.Uri.UriSchemeHttp; + isWebUri = true; } else if (input.Contains(":", StringComparison.OrdinalIgnoreCase) && - !input.Contains("http", StringComparison.OrdinalIgnoreCase) && + !input.StartsWith("http", StringComparison.OrdinalIgnoreCase) && !input.Contains("[", StringComparison.OrdinalIgnoreCase)) { // Do nothing, leave unchanged + isWebUri = false; } else { urlBuilder.Scheme = System.Uri.UriSchemeHttps; + isWebUri = true; } result = urlBuilder.Uri; return true; } - catch (System.UriFormatException) + catch (UriFormatException) { result = default; + isWebUri = false; return false; } } diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Microsoft.Plugin.WindowWalker.csproj b/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Microsoft.Plugin.WindowWalker.csproj index 0d9903857..476c7ac4d 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Microsoft.Plugin.WindowWalker.csproj +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Microsoft.Plugin.WindowWalker.csproj @@ -17,7 +17,7 @@ true - ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\Microsoft.Plugin.WindowWalker\ + ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\WindowWalker\ DEBUG;TRACE full x64 @@ -29,7 +29,7 @@ - ..\..\..\..\..\x64\Release\modules\launcher\Plugins\Microsoft.Plugin.WindowWalker\ + ..\..\..\..\..\x64\Release\modules\launcher\Plugins\WindowWalker\ TRACE true pdbonly diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Microsoft.PowerToys.Run.Plugin.Registry.csproj b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Microsoft.PowerToys.Run.Plugin.Registry.csproj index 12a5ec647..a8ae61de2 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Microsoft.PowerToys.Run.Plugin.Registry.csproj +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Microsoft.PowerToys.Run.Plugin.Registry.csproj @@ -15,7 +15,7 @@ true - ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Registry\ + ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\Registry\ DEBUG;TRACE full x64 @@ -27,7 +27,7 @@ - ..\..\..\..\..\x64\Release\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Registry\ + ..\..\..\..\..\x64\Release\modules\launcher\Plugins\Registry\ TRACE true pdbonly diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Properties/Resources.Designer.cs index b41dd96b2..7813a05c2 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Properties/Resources.Designer.cs @@ -277,7 +277,7 @@ namespace Microsoft.PowerToys.Run.Plugin.System.Properties { } /// - /// Looks up a localized string similar to Use localized system commands instead of english ones. + /// Looks up a localized string similar to Use localized system commands instead of English ones. /// internal static string Use_localized_system_commands { get { diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Properties/Resources.resx index 32977da4d..17432f8ea 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Properties/Resources.resx @@ -213,6 +213,6 @@ This should align to the action in Windows of a making your computer go to sleep. - Use localized system commands instead of english ones + Use localized system commands instead of English ones \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Classes/WindowsSetting.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Classes/WindowsSetting.cs index 6c4f23fe9..74fc2653f 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Classes/WindowsSetting.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Classes/WindowsSetting.cs @@ -19,6 +19,7 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings Name = string.Empty; Command = string.Empty; Type = string.Empty; + ShowAsFirstResult = false; } /// @@ -62,6 +63,11 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings /// public uint? DeprecatedInBuild { get; set; } + /// + /// Gets or sets a value indicating whether to use a higher score as normal for this setting to show it as one of the first results. + /// + public bool ShowAsFirstResult { get; set; } + /// /// Gets or sets the the value with the generated area path as string. /// This Property IS NOT PART OF THE DATA IN "WindowsSettings.json". diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Helper/ResultHelper.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Helper/ResultHelper.cs index 3a80c5012..16859bf70 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Helper/ResultHelper.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Helper/ResultHelper.cs @@ -146,6 +146,7 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Helper var lowScore = 1_000; var mediumScore = 5_000; var highScore = 10_000; + var firstResultScore = 10_500; foreach (var result in resultList) { @@ -158,43 +159,44 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Helper { if (windowsSetting.Name.StartsWith(query, StringComparison.CurrentCultureIgnoreCase)) { - result.Score = highScore--; + result.Score = !windowsSetting.ShowAsFirstResult ? highScore-- : firstResultScore--; continue; } // If query starts with second or next word of name, set score. if (windowsSetting.Name.Contains($" {query}", StringComparison.CurrentCultureIgnoreCase)) { - result.Score = mediumScore--; + result.Score = !windowsSetting.ShowAsFirstResult ? mediumScore-- : firstResultScore--; continue; } if (windowsSetting.Areas is null) { - result.Score = lowScore--; + result.Score = !windowsSetting.ShowAsFirstResult ? lowScore-- : firstResultScore--; continue; } if (windowsSetting.Areas.Any(x => x.StartsWith(query, StringComparison.CurrentCultureIgnoreCase))) { - result.Score = lowScore--; + result.Score = !windowsSetting.ShowAsFirstResult ? lowScore-- : firstResultScore--; continue; } if (windowsSetting.AltNames is null) { - result.Score = lowScore--; + result.Score = !windowsSetting.ShowAsFirstResult ? lowScore-- : firstResultScore--; continue; } if (windowsSetting.AltNames.Any(x => x.StartsWith(query, StringComparison.CurrentCultureIgnoreCase))) { - result.Score = mediumScore--; + result.Score = !windowsSetting.ShowAsFirstResult ? mediumScore-- : firstResultScore--; continue; } } - result.Score = lowScore--; + // On empty queries + result.Score = !windowsSetting.ShowAsFirstResult ? lowScore-- : firstResultScore--; } } diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Microsoft.PowerToys.Run.Plugin.WindowsSettings.csproj b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Microsoft.PowerToys.Run.Plugin.WindowsSettings.csproj index 4b7cf12b2..a60d4f0ce 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Microsoft.PowerToys.Run.Plugin.WindowsSettings.csproj +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Microsoft.PowerToys.Run.Plugin.WindowsSettings.csproj @@ -18,7 +18,7 @@ - ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.WindowsSettings\ + ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\WindowsSettings\ DEBUG;TRACE false full @@ -31,7 +31,7 @@ - ..\..\..\..\..\x64\Release\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.WindowsSettings\ + ..\..\..\..\..\x64\Release\modules\launcher\Plugins\WindowsSettings\ TRACE true pdbonly diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Properties/Resources.Designer.cs index adb47c927..0070c3089 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Properties/Resources.Designer.cs @@ -447,15 +447,6 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Properties { } } - /// - /// Looks up a localized string similar to Home page. - /// - internal static string AreaHomePage { - get { - return ResourceManager.GetString("AreaHomePage", resourceCulture); - } - } - /// /// Looks up a localized string similar to Mixed reality. /// @@ -510,6 +501,15 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Properties { } } + /// + /// Looks up a localized string similar to Security and Maintenance. + /// + internal static string AreaSecurityAndMaintenance { + get { + return ResourceManager.GetString("AreaSecurityAndMaintenance", resourceCulture); + } + } + /// /// Looks up a localized string similar to SurfaceHub. /// @@ -537,6 +537,15 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Properties { } } + /// + /// Looks up a localized string similar to System Properties. + /// + internal static string AreaSystemPropertiesAdvanced { + get { + return ResourceManager.GetString("AreaSystemPropertiesAdvanced", resourceCulture); + } + } + /// /// Looks up a localized string similar to Time and language. /// @@ -834,6 +843,15 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Properties { } } + /// + /// Looks up a localized string similar to Change User Account Control settings. + /// + internal static string ChangeUACSettings { + get { + return ResourceManager.GetString("ChangeUACSettings", resourceCulture); + } + } + /// /// Looks up a localized string similar to Choose which folders appear on Start. /// @@ -1230,6 +1248,15 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Properties { } } + /// + /// Looks up a localized string similar to Edit environment variables. + /// + internal static string EditEnvironmentVariables { + get { + return ResourceManager.GetString("EditEnvironmentVariables", resourceCulture); + } + } + /// /// Looks up a localized string similar to Edition. /// @@ -1239,6 +1266,24 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Properties { } } + /// + /// Looks up a localized string similar to Edit the system environment variables. + /// + internal static string EditSystemEnvironmentVariables { + get { + return ResourceManager.GetString("EditSystemEnvironmentVariables", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Edit environment variables for your account. + /// + internal static string EditUserEnvironmentVariables { + get { + return ResourceManager.GetString("EditUserEnvironmentVariables", resourceCulture); + } + } + /// /// Looks up a localized string similar to Email. /// @@ -1275,6 +1320,15 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Properties { } } + /// + /// Looks up a localized string similar to Env vars. + /// + internal static string EnvVars { + get { + return ResourceManager.GetString("EnvVars", resourceCulture); + } + } + /// /// Looks up a localized string similar to Ethernet. /// @@ -2211,6 +2265,15 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Properties { } } + /// + /// Looks up a localized string similar to Editing this setting may require administrative privileges.. + /// + internal static string NoteEditingRequireAdminPrivileges { + get { + return ResourceManager.GetString("NoteEditingRequireAdminPrivileges", resourceCulture); + } + } + /// /// Looks up a localized string similar to Only present if user is enrolled in WIP.. /// @@ -2436,6 +2499,24 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Properties { } } + /// + /// Looks up a localized string similar to Control Panel (Application homepage). + /// + internal static string OpenControlPanel { + get { + return ResourceManager.GetString("OpenControlPanel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Settings (Application homepage). + /// + internal static string OpenSettingsApp { + get { + return ResourceManager.GetString("OpenSettingsApp", resourceCulture); + } + } + /// /// Looks up a localized string similar to OS. /// @@ -3094,11 +3175,11 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Properties { } /// - /// Looks up a localized string similar to Settings home page. + /// Looks up a localized string similar to Settings app. /// - internal static string SettingsHomePage { + internal static string SettingsApp { get { - return ResourceManager.GetString("SettingsHomePage", resourceCulture); + return ResourceManager.GetString("SettingsApp", resourceCulture); } } @@ -3300,6 +3381,15 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Properties { } } + /// + /// Looks up a localized string similar to System env vars. + /// + internal static string SystemEnvVars { + get { + return ResourceManager.GetString("SystemEnvVars", resourceCulture); + } + } + /// /// Looks up a localized string similar to System properties and Add New Hardware wizard. /// @@ -3309,6 +3399,15 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Properties { } } + /// + /// Looks up a localized string similar to System variables. + /// + internal static string SystemVariables { + get { + return ResourceManager.GetString("SystemVariables", resourceCulture); + } + } + /// /// Looks up a localized string similar to Tab. /// @@ -3516,6 +3615,15 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Properties { } } + /// + /// Looks up a localized string similar to UAC. + /// + internal static string UAC { + get { + return ResourceManager.GetString("UAC", resourceCulture); + } + } + /// /// Looks up a localized string similar to Uninstall. /// @@ -3534,6 +3642,15 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Properties { } } + /// + /// Looks up a localized string similar to UserAccountControlSettings.exe. + /// + internal static string UserAccountControlSettings_exe { + get { + return ResourceManager.GetString("UserAccountControlSettings.exe", resourceCulture); + } + } + /// /// Looks up a localized string similar to User accounts. /// @@ -3543,6 +3660,33 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings.Properties { } } + /// + /// Looks up a localized string similar to User environment variables. + /// + internal static string UserEnvironmentVariables { + get { + return ResourceManager.GetString("UserEnvironmentVariables", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to User env vars. + /// + internal static string UserEnvVars { + get { + return ResourceManager.GetString("UserEnvVars", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to User variables. + /// + internal static string UserVariables { + get { + return ResourceManager.GetString("UserVariables", resourceCulture); + } + } + /// /// Looks up a localized string similar to Version. /// diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Properties/Resources.resx index e9928c47e..058572f48 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Properties/Resources.resx @@ -275,9 +275,6 @@ Hardware and Sound - - Home page - Mixed reality @@ -296,6 +293,10 @@ Programs + + Security and Maintenance + Area Security and Maintenance in legacy Control Panel app. + SurfaceHub @@ -305,6 +306,10 @@ System and Security + + System Properties + System Properties dialog sysdm.cpl + Time and language @@ -428,6 +433,9 @@ Cellular and SIM Area NetworkAndInternet + + Change User Account Control settings + Choose which folders appear on Start Area Personalization @@ -599,10 +607,20 @@ Ease of access center Area Control Panel (legacy settings) + + Edit environment variables + Used as AltName on EditSystemEnvironmentVars settings to make both entries available via 'Edit environment variables'. + Edition Means the "Windows Edition" + + Edit the system environment variables + + + Edit environment variables for your account + Email Area Privacy @@ -619,6 +637,10 @@ Environment Area MixedReality, only available if the Mixed Reality Portal app is installed. + + Env vars + Short english form. Don't translate! + Ethernet Area NetworkAndInternet @@ -1009,6 +1031,9 @@ Only available on devices that support advanced display options. + + Editing this setting may require administrative privileges. + Only present if user is enrolled in WIP. @@ -1094,6 +1119,14 @@ On-Screen + + Control Panel (Application homepage) + 'Control Panel' is here the name of the legacy settings app. + + + Settings (Application homepage) + 'Settings' is here the name of the modern settings app. + OS Means the "Operating System" @@ -1373,9 +1406,8 @@ Session cleanup Area SurfaceHub - - Settings home page - Area Home, Overview-page for all areas of settings + + Settings app Set up a kiosk @@ -1462,10 +1494,17 @@ System Area Control Panel (legacy settings) + + System env vars + Short english form. Don't translate! + System properties and Add New Hardware wizard Area Control Panel (legacy settings) + + System variables + Tab Means the key "Tabulator" on the keyboard @@ -1552,6 +1591,10 @@ Typing Area Device + + UAC + Short version of 'User account control' + Uninstall Area MixedReality, only available if the Mixed Reality Portal app is installed. @@ -1560,10 +1603,24 @@ USB Area Device + + UserAccountControlSettings.exe + Name of the executable + User accounts Area Control Panel (legacy settings) + + User environment variables + + + User env vars + Short english form. Don't translate! + + + User variables + Version Means The "Windows Version" diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/WindowsSettings.json b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/WindowsSettings.json index f22791969..93d19335b 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/WindowsSettings.json +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/WindowsSettings.json @@ -1,1739 +1,1770 @@ { - "$schema" : "./WindowsSettings.schema.json", + "$schema": "./WindowsSettings.schema.json", "Settings": [ { - "Name": "AccessWorkOrSchool", - "Areas": [ "AreaAccounts" ], - "Type": "AppSettingsApp", - "AltNames": [ "Workplace" ], - "Command": "ms-settings:workplace" + "Name": "OpenSettingsApp", + "Type": "AppSettingsApp", + "AltNames": [ "SettingsApp", "AppSettingsApp" ], + "Command": "ms-settings:", + "ShowAsFirstResult": true }, - { - "Name": "EmailAndAppAccounts", - "Areas": [ "AreaAccounts" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:emailandaccounts" - }, - { - "Name": "FamilyAndOtherPeople", - "Areas": [ "AreaAccounts" ], - "Type": "AppSettingsApp", - "AltNames": [ "OtherUsers" ], - "Command": "ms-settings:otherusers" - }, - { - "Name": "SetUpKiosk", - "Areas": [ "AreaAccounts" ], - "Type": "AppSettingsApp", - "AltNames": [ "AssignedAccess" ], - "Command": "ms-settings:assignedaccess" - }, - { - "Name": "SignInOptions", - "Areas": [ "AreaAccounts" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:signinoptions" - }, - { - "Name": "SignInOptionsDynamicLock", - "Areas": [ "AreaAccounts" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:signinoptions-dynamiclock" - }, - { - "Name": "SyncYourSettings", - "Areas": [ "AreaAccounts" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:sync" - }, - { - "Name": "WindowsHelloSetupFace", - "Areas": [ "AreaAccounts" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:signinoptions-launchfaceenrollment" - }, - { - "Name": "WindowsHelloSetupFingerprint", - "Areas": [ "AreaAccounts" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:signinoptions-launchfingerprintenrollment" - }, - { - "Name": "YourInfo", - "Areas": [ "AreaAccounts" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:yourinfo" - }, - { - "Name": "AppsAndFeatures", - "Areas": [ "AreaApps" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:appsfeatures" - }, - { - "Name": "AppFeatures", - "Areas": [ "AreaApps" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:appsfeatures-app" - }, - { - "Name": "AppsForWebsites", - "Areas": [ "AreaApps" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:appsforwebsites" - }, - { - "Name": "DefaultApps", - "Areas": [ "AreaApps" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:defaultapps" - }, - { - "Name": "ManageOptionalFeatures", - "Areas": [ "AreaApps" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:optionalfeatures" - }, - { - "Name": "OfflineMaps", - "Areas": [ "AreaApps" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:maps" - }, - { - "Name": "OfflineMapsDownloadMaps", - "Areas": [ "AreaApps" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:maps-downloadmaps" - }, - { - "Name": "StartupApps", - "Areas": [ "AreaApps" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:startupapps" - }, - { - "Name": "VideoPlayback", - "Areas": [ "AreaApps" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:videoplayback" - }, - { - "Name": "Notifications", - "Areas": [ "AreaCortana" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:cortana-notifications" - }, - { - "Name": "MoreDetails", - "Areas": [ "AreaCortana" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:cortana-moredetails" - }, - { - "Name": "PermissionsAndHistory", - "Areas": [ "AreaCortana" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:cortana-permissions" - }, - { - "Name": "WindowsSearch", - "Areas": [ "AreaCortana" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:cortana-windowssearch" - }, - { - "Name": "CortanaLanguage", - "Areas": [ "AreaCortana" ], - "Type": "AppSettingsApp", - "AltNames": [ "Talk" ], - "Command": "ms-settings:cortana-language" - }, - { - "Name": "Cortana", - "Areas": [ "AreaCortana" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:cortana" - }, - { - "Name": "TalkToCortana", - "Areas": [ "AreaCortana" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:cortana-talktocortana" - }, - { - "Name": "AutoPlay", - "Areas": [ "AreaDevices" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:autoplay" - }, - { - "Name": "Bluetooth", - "Areas": [ "AreaDevices" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:bluetooth" - }, - { - "Name": "ConnectedDevices", - "Areas": [ "AreaDevices" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:connecteddevices" - }, - { - "Name": "DefaultCamera", - "Areas": [ "AreaDevices" ], - "Type": "AppSettingsApp", - "DeprecatedInBuild": 17763, - "Note": "NoteDeprecated17763", - "Command": "ms-settings:camera" - }, - { - "Name": "MouseAndTouchpad", - "Areas": [ "AreaDevices" ], - "Type": "AppSettingsApp", - "Note": "NoteTouchpad", - "Command": "ms-settings:mousetouchpad" - }, - { - "Name": "PenAndWindowsInk", - "Areas": [ "AreaDevices" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:pen" - }, - { - "Name": "PrintersAndScanners", - "Areas": [ "AreaDevices" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:printers" - }, - { - "Name": "Touchpad", - "Areas": [ "AreaDevices" ], - "Type": "AppSettingsApp", - "Note": "NoteTouchpad", - "Command": "ms-settings:devices-touchpad" - }, - { - "Name": "Typing", - "Areas": [ "AreaDevices" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:typing" - }, - { - "Name": "Usb", - "Areas": [ "AreaDevices" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:usb" - }, - { - "Name": "Wheel", - "Areas": [ "AreaDevices" ], - "Type": "AppSettingsApp", - "Note": "NoteDialPaired", - "Command": "ms-settings:wheel" - }, - { - "Name": "Phone", - "Areas": [ "AreaPhone" ], - "Type": "AppSettingsApp", - "AltNames": [ "MobileDevices" ], - "Command": "ms-settings:mobile-devices" - }, - { - "Name": "Audio", - "Areas": [ "AreaEaseOfAccess" ], - "Type": "AppSettingsApp", - "AltNames": [ "Mono", "Volume", "AudioAlerts" ], - "Command": "ms-settings:easeofaccess-audio" - }, - { - "Name": "ClosedCaptions", - "Areas": [ "AreaEaseOfAccess" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:easeofaccess-closedcaptioning" - }, - { - "Name": "ColorFilters", - "Areas": [ "AreaEaseOfAccess" ], - "Type": "AppSettingsApp", - "AltNames": [ "InvertedColors", "Grayscale", "RedGreen", "BlueYellow", "GreenWeek", "RedWeek", "deuteranopia", "protanopia", "tritanopia" ], - "Command": "ms-settings:easeofaccess-colorfilter" - }, - { - "Name": "MousePointer", - "Areas": [ "AreaEaseOfAccess" ], - "Type": "AppSettingsApp", - "AltNames": [ "TouchFeedback" ], - "Command": "ms-settings:easeofaccess-MousePointer" - }, - { - "Name": "Display", - "Areas": [ "AreaEaseOfAccess" ], - "Type": "AppSettingsApp", - "AltNames": [ "Transparency", "Animations", "ScrollBars", "Size" ], - "Command": "ms-settings:easeofaccess-display" - }, - { - "Name": "EyeControl", - "Areas": [ "AreaEaseOfAccess" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:easeofaccess-eyecontrol" - }, - { - "Name": "Fonts", - "Areas": [ "AreaEaseOfAccess" ], - "Type": "AppSettingsApp", - "DeprecatedInBuild": 17763, - "Note": "NoteDeprecated17763", - "Command": "ms-settings:fonts" - }, - { - "Name": "HighContrast", - "Areas": [ "AreaEaseOfAccess" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:easeofaccess-highcontrast" - }, - { - "Name": "Keyboard", - "Areas": [ "AreaEaseOfAccess" ], - "Type": "AppSettingsApp", - "AltNames": [ "PrintScreen", "Shortcuts", "OnScreen", "Keys", "ScrollLock", "CapsLock", "NumLock" ], - "Command": "ms-settings:easeofaccess-keyboard" - }, - { - "Name": "Magnifier", - "Areas": [ "AreaEaseOfAccess" ], - "Type": "AppSettingsApp", - "AltNames": [ "Zoom" ], - "Command": "ms-settings:easeofaccess-magnifier" - }, - { - "Name": "Mouse", - "Areas": [ "AreaEaseOfAccess" ], - "Type": "AppSettingsApp", - "AltNames": [ "Keypad", "Touch" ], - "Command": "ms-settings:easeofaccess-mouse" - }, - { - "Name": "Narrator", - "Areas": [ "AreaEaseOfAccess" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:easeofaccess-narrator" - }, - { - "Name": "OtherOptions", - "Areas": [ "AreaEaseOfAccess" ], - "Type": "AppSettingsApp", - "DeprecatedInBuild": 17763, - "Note": "NoteDeprecated17763", - "Command": "ms-settings:easeofaccess-otheroptions" - }, - { - "Name": "Speech", - "Areas": [ "AreaEaseOfAccess" ], - "Type": "AppSettingsApp", - "AltNames": [ "Recognition", "Talk" ], - "Command": "ms-settings:easeofaccess-speechrecognition" - }, - { - "Name": "Extras", - "Areas": [ "AreaExtras" ], - "Type": "AppSettingsApp", - "Note": "NoteThirdParty", - "Command": "ms-settings:extras" - }, - { - "Name": "Broadcasting", - "Areas": [ "AreaGaming" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:gaming-broadcasting" - }, - { - "Name": "GameBar", - "Areas": [ "AreaGaming" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:gaming-gamebar" - }, - { - "Name": "GameDvr", - "Areas": [ "AreaGaming" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:gaming-gamedvr" - }, - { - "Name": "GameMode", - "Areas": [ "AreaGaming" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:gaming-gamemode" - }, - { - "Name": "PlayingGameFullScreen", - "Areas": [ "AreaGaming" ], - "Type": "AppSettingsApp", - "AltNames": [ "QuietMomentsGame" ], - "Command": "ms-settings:quietmomentsgame" - }, - { - "Name": "TruePlay", - "Areas": [ "AreaGaming" ], - "Type": "AppSettingsApp", - "DeprecatedInBuild": 17763, - "Note": "NoteDeprecated17763", - "Command": "ms-settings:gaming-trueplay" - }, - { - "Name": "XboxNetworking", - "Areas": [ "AreaGaming" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:gaming-xboxnetworking" - }, - { - "Name": "SettingsHomePage", - "Areas": [ "AreaHomePage" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:" - }, - { - "Name": "AudioAndSpeech", - "Areas": [ "AreaMixedReality" ], - "Type": "AppSettingsApp", - "AltNames": [ "HolographicAudio" ], - "Note": "NoteMixedReality", - "Command": "ms-settings:holographic-audio" - }, - { - "Name": "Environment", - "Areas": [ "AreaMixedReality" ], - "Type": "AppSettingsApp", - "AltNames": [ "HolographicEnvironment" ], - "Note": "NoteMixedReality", - "Command": "ms-settings:privacy-holographic-environment" - }, - { - "Name": "HeadsetDisplay", - "Areas": [ "AreaMixedReality" ], - "Type": "AppSettingsApp", - "AltNames": [ "HolographicHeadset" ], - "Note": "NoteMixedReality", - "Command": "ms-settings:holographic-headset" - }, - { - "Name": "Uninstall", - "Areas": [ "AreaMixedReality" ], - "Type": "AppSettingsApp", - "AltNames": [ "HolographicManagement" ], - "Note": "NoteMixedReality", - "Command": "ms-settings:holographic-management" - }, - { - "Name": "AirplaneMode", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:network-airplanemode" - }, - { - "Name": "Proximity", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:proximity" - }, - { - "Name": "CellularAndSim", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:network-cellular" - }, - { - "Name": "DataUsage", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:datausage" - }, - { - "Name": "DialUp", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:network-dialup" - }, - { - "Name": "DirectAccess", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppSettingsApp", - "Note": "NoteDirectAccess", - "Command": "ms-settings:network-directaccess" - }, - { - "Name": "Ethernet", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppSettingsApp", - "AltNames": [ "DNS", "Sdns", "SecureDNS", "Gateway", "Dhcp", "Ip" ], - "Command": "ms-settings:network-ethernet" - }, - { - "Name": "ManageKnownNetworks", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppSettingsApp", - "AltNames": [ "WiFiSettings", "ShortNameWiFi" ], - "Note": "NoteWiFiAdapter", - "Command": "ms-settings:network-wifisettings" - }, - { - "Name": "MobileHotspot", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:network-mobilehotspot" - }, - { - "Name": "NFC", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppSettingsApp", - "AltNames": [ "NFCTransactions" ], - "Command": "ms-settings:nfctransactions" - }, - { - "Name": "Proxy", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:network-proxy" - }, - { - "Name": "NetworkStatus", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:network-status" - }, - { - "Name": "Network", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppSettingsApp", - "AltNames": [ "DNS", "Sdns", "SecureDNS", "Gateway", "Dhcp", "Ip" ], - "Command": "ms-settings:network" - }, - { - "Name": "Vpn", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:network-vpn" - }, - { - "Name": "WiFi", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppSettingsApp", - "AltNames": [ "Wireless", "MeteredConnection", "ShortNameWiFi" ], - "Note": "NoteWiFiAdapter", - "Command": "ms-settings:network-wifi" - }, - { - "Name": "WiFiCalling", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppSettingsApp", - "AltNames": [ "ShortNameWiFi" ], - "Note": "NoteWiFiAdapter", - "Command": "ms-settings:network-wificalling" - }, - { - "Name": "Background", - "Areas": [ "AreaPersonalization" ], - "Type": "AppSettingsApp", - "AltNames": [ "Wallpaper", "Picture", "Image" ], - "Command": "ms-settings:personalization-background" - }, - { - "Name": "ChooseWhichFoldersAppearOnStart", - "Areas": [ "AreaPersonalization" ], - "Type": "AppSettingsApp", - "AltNames": [ "StartPlaces" ], - "Command": "ms-settings:personalization-start-places" - }, - { - "Name": "Colors", - "Areas": [ "AreaPersonalization" ], - "Type": "AppSettingsApp", - "AltNames": [ "DarkMode", "LightMode", "DarkColor", "LightColor", "AppColor", "TaskbarColor", "WindowBorder" ], - "Command": "ms-settings:colors" - }, - { - "Name": "Glance", - "Areas": [ "AreaPersonalization" ], - "Type": "AppSettingsApp", - "DeprecatedInBuild": 17763, - "Note": "NoteDeprecated17763", - "Command": "ms-settings:personalization-glance" - }, - { - "Name": "LockScreen", - "Areas": [ "AreaPersonalization" ], - "Type": "AppSettingsApp", - "AltNames": [ "Image", "Picture" ], - "Command": "ms-settings:lockscreen" - }, - { - "Name": "NavigationBar", - "Areas": [ "AreaPersonalization" ], - "Type": "AppSettingsApp", - "DeprecatedInBuild": 17763, - "Note": "NoteDeprecated17763", - "Command": "ms-settings:personalization-navbar" - }, - { - "Name": "PersonalizationCategory", - "Type": "AppSettingsApp", - "Command": "ms-settings:personalization" - }, - { - "Name": "Start", - "Areas": [ "AreaPersonalization" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:personalization-start" - }, - { - "Name": "Taskbar", - "Areas": [ "AreaPersonalization" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:taskbar" - }, - { - "Name": "Themes", - "Areas": [ "AreaPersonalization" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:themes" - }, - { - "Name": "AddYourPhone", - "Areas": [ "AreaPhone" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:mobile-devices-addphone", - "Note": "NoteAddYourPhone" - }, - { - "Name": "DirectOpenYourPhone", - "Areas": [ "AreaPhone" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:mobile-devices-addphone-direct", - "Note": "NoteAddYourPhone" - }, - { - "Name": "AccessoryApps", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "DeprecatedInBuild": 17763, - "Note": "NoteDeprecated17763", - "Command": "ms-settings:privacy-accessoryapps" - }, - { - "Name": "AccountInfo", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-accountinfo" - }, - { - "Name": "ActivityHistory", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-activityhistory" - }, - { - "Name": "AdvertisingId", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "DeprecatedInBuild": 17763, - "Note": "NoteDeprecated17763", - "Command": "ms-settings:privacy-advertisingid" - }, - { - "Name": "AppDiagnostics", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-appdiagnostics" - }, - { - "Name": "AutomaticFileDownloads", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-automaticfiledownloads" - }, - { - "Name": "BackgroundApps", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-backgroundapps" - }, - { - "Name": "Calendar", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-calendar" - }, - { - "Name": "CallHistory", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-callhistory" - }, - { - "Name": "Camera", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-webcam" - }, - { - "Name": "Contacts", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-contacts" - }, - { - "Name": "Documents", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-documents" - }, - { - "Name": "Email", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-email" - }, - { - "Name": "EyeTracker", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Note": "NoteEyetrackerHardware", - "Command": "ms-settings:privacy-eyetracker" - }, - { - "Name": "FeedbackAndDiagnostics", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-feedback" - }, - { - "Name": "FileSystem", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-broadfilesystemaccess" - }, - { - "Name": "General", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-general" - }, - { - "Name": "InkingAndTyping", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "AltNames": [ "SpeechTyping" ], - "Command": "ms-settings:privacy-speechtyping" - }, - { - "Name": "Location", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-location" - }, - { - "Name": "Messaging", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-messaging" - }, - { - "Name": "Microphone", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-microphone" - }, - { - "Name": "Motion", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-motion" - }, - { - "Name": "Notifications", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-notifications" - }, - { - "Name": "OtherDevices", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "AltNames": [ "CustomDevices" ], - "Command": "ms-settings:privacy-customdevices" - }, - { - "Name": "PhoneCalls", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-phonecalls" - }, - { - "Name": "Pictures", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-pictures" - }, - { - "Name": "Radios", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-radios" - }, - { - "Name": "Speech", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-speech" - }, - { - "Name": "Tasks", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-tasks" - }, - { - "Name": "Videos", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-videos" - }, - { - "Name": "VoiceActivation", - "Areas": [ "AreaPrivacy" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:privacy-voiceactivation" - }, - { - "Name": "Accounts", - "Areas": [ "AreaSurfaceHub" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:surfacehub-accounts" - }, - { - "Name": "SessionCleanup", - "Areas": [ "AreaSurfaceHub" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:surfacehub-sessioncleanup" - }, - { - "Name": "TeamConferencing", - "Areas": [ "AreaSurfaceHub" ], - "Type": "AppSettingsApp", - "AltNames": [ "calling" ], - "Command": "ms-settings:surfacehub-calling" - }, - { - "Name": "TeamDeviceManagement", - "Areas": [ "AreaSurfaceHub" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:surfacehub-devicemanagenent" - }, - { - "Name": "WelcomeScreen", - "Areas": [ "AreaSurfaceHub" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:surfacehub-welcome" - }, - { - "Name": "About", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "AltNames": [ "Ram", "Processor", "Os", "Id", "Edition", "Version" ], - "Command": "ms-settings:about" - }, - { - "Name": "AdvancedDisplaySettings", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "Note": "NoteDisplayGraphics", - "Command": "ms-settings:display-advanced" - }, - { - "Name": "AppVolumeAndDevicePreferences", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "IntroducedInBuild": 18362, - "Note": "NoteSince18362", - "Command": "ms-settings:apps-volume" - }, - { - "Name": "BatterySaver", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "Note": "NoteBattery", - "Command": "ms-settings:batterysaver" - }, - { - "Name": "BatterySaverSettings", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "Note": "NoteBattery", - "Command": "ms-settings:batterysaver-settings" - }, - { - "Name": "BatteryUse", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "AltNames": [ "BatterySaverUsageDetails" ], - "Note": "NoteBattery", - "Command": "ms-settings:batterysaver-usagedetails" - }, - { - "Name": "Clipboard", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:clipboard" - }, - { - "Name": "Display", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "AltNames": [ "NightLight", "BlueLight", "WarmerColor", "RedEye" ], - "Command": "ms-settings:display" - }, - { - "Name": "DefaultSaveLocations", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:savelocations" - }, - { - "Name": "ScreenRotation", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:screenrotation" - }, - { - "Name": "DuplicatingMyDisplay", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "AltNames": [ "Presentation" ], - "Command": "ms-settings:quietmomentspresentation" - }, - { - "Name": "DuringTheseHours", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "AltNames": [ "Scheduled" ], - "Command": "ms-settings:quietmomentsscheduled" - }, - { - "Name": "Encryption", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:deviceencryption" - }, - { - "Name": "FocusAssistQuietHours", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:quiethours" - }, - { - "Name": "FocusAssistQuietMoments", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:quietmomentshome" - }, - { - "Name": "GraphicsSettings", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "AltNames": [ "AdvancedGraphics" ], - "Note": "NoteAdvancedGraphics", - "Command": "ms-settings:display-advancedgraphics" - }, - { - "Name": "Messaging", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:messaging" - }, - { - "Name": "Multitasking", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "AltNames": [ "Timeline", "Tab", "AltAndTab", "VirtualDesktops" ], - "Command": "ms-settings:multitasking" - }, - { - "Name": "NightLightSettings", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:nightlight" - }, - { - "Name": "PhoneDefaultApps", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:phone-defaultapps" - }, - { - "Name": "ProjectingToThisPc", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:project" - }, - { - "Name": "SharedExperiences", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "AltNames": [ "Crossdevice" ], - "Command": "ms-settings:crossdevice" - }, - { - "Name": "TabletMode", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:tabletmode" - }, - { - "Name": "Taskbar", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:taskbar" - }, - { - "Name": "NotificationsAndActions", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:notifications" - }, - { - "Name": "RemoteDesktop", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:remotedesktop" - }, - { - "Name": "Phone", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "DeprecatedInBuild": 17763, - "Note": "NoteDeprecated17763", - "Command": "ms-settings:phone" - }, - { - "Name": "PowerAndSleep", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:powersleep" - }, - { - "Name": "Sound", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:sound" - }, - { - "Name": "StorageSense", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:storagesense" - }, - { - "Name": "StoragePolicies", - "Areas": [ "AreaSystem" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:storagepolicies" - }, - { - "Name": "DateAndTime", - "Areas": [ "AreaTimeAndLanguage" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:dateandtime" - }, - { - "Name": "JapanImeSettings", - "Areas": [ "AreaTimeAndLanguage" ], - "Type": "AppSettingsApp", - "AltNames": [ "jpnime" ], - "Note": "NoteImeJapan", - "Command": "ms-settings:regionlanguage-jpnime" - }, - { - "Name": "Region", - "Areas": [ "AreaTimeAndLanguage" ], - "Type": "AppSettingsApp", - "AltNames": [ "RegionFormatting" ], - "Command": "ms-settings:regionformatting" - }, - { - "Name": "Keyboard", - "Areas": [ "AreaTimeAndLanguage" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:keyboard" - }, - { - "Name": "RegionalLanguage", - "Areas": [ "AreaTimeAndLanguage" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:regionlanguage" - }, - { - "Name": "BopomofoIme", - "Areas": [ "AreaTimeAndLanguage" ], - "Type": "AppSettingsApp", - "AltNames": [ "bpmf" ], - "Command": "ms-settings:regionlanguage-bpmfime" - }, - { - "Name": "CangjieIme", - "Areas": [ "AreaTimeAndLanguage" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:regionlanguage-cangjieime" - }, - { - "Name": "PinyinImeSettingsDomainLexicon", - "Areas": [ "AreaTimeAndLanguage" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:regionlanguage-chsime-pinyin-domainlexicon" - }, - { - "Name": "PinyinImeSettingsKeyConfiguration", - "Areas": [ "AreaTimeAndLanguage" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:regionlanguage-chsime-pinyin-keyconfig" - }, - { - "Name": "PinyinImeSettingsUdp", - "Areas": [ "AreaTimeAndLanguage" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:regionlanguage-chsime-pinyin-udp" - }, - { - "Name": "WubiImeSettingsUdp", - "Areas": [ "AreaTimeAndLanguage" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:regionlanguage-chsime-wubi-udp" - }, - { - "Name": "Quickime", - "Areas": [ "AreaTimeAndLanguage" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:regionlanguage-quickime" - }, - { - "Name": "PinyinImeSettings", - "Areas": [ "AreaTimeAndLanguage" ], - "Type": "AppSettingsApp", - "Note": "NoteImePinyin", - "Command": "ms-settings:regionlanguage-chsime-pinyin" - }, - { - "Name": "Speech", - "Areas": [ "AreaTimeAndLanguage" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:speech" - }, - { - "Name": "WubiImeSettings", - "Areas": [ "AreaTimeAndLanguage" ], - "Type": "AppSettingsApp", - "Note": "NoteImeWubi", - "Command": "ms-settings:regionlanguage-chsime-wubi" - }, - { - "Name": "Activation", - "Areas": [ "AreaUpdateAndSecurity" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:activation" - }, - { - "Name": "Backup", - "Areas": [ "AreaUpdateAndSecurity" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:backup" - }, - { - "Name": "DeliveryOptimization", - "Areas": [ "AreaUpdateAndSecurity" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:delivery-optimization" - }, - { - "Name": "FindMyDevice", - "Areas": [ "AreaUpdateAndSecurity" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:findmydevice" - }, - { - "Name": "ForDevelopers", - "Areas": [ "AreaUpdateAndSecurity" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:developers" - }, - { - "Name": "Recovery", - "Areas": [ "AreaUpdateAndSecurity" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:recovery" - }, - { - "Name": "Troubleshoot", - "Areas": [ "AreaUpdateAndSecurity" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:troubleshoot" - }, - { - "Name": "WindowsSecurity", - "Areas": [ "AreaUpdateAndSecurity" ], - "Type": "AppSettingsApp", - "AltNames": [ "WindowsDefender", "Firewall", "Virus", "CoreIsolation", "SecurityProcessor", "IsolatedBrowsing", "ExploitProtection" ], - "Command": "ms-settings:windowsdefender" - }, - { - "Name": "WindowsInsiderProgram", - "Areas": [ "AreaUpdateAndSecurity" ], - "Type": "AppSettingsApp", - "Note": "NoteEnrolledWIP", - "Command": "ms-settings:windowsinsider" - }, - { - "Name": "WindowsUpdate", - "Areas": [ "AreaUpdateAndSecurity" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:windowsupdate" - }, - { - "Name": "WindowsUpdateCheckForUpdates", - "Areas": [ "AreaUpdateAndSecurity" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:windowsupdate-action" - }, - { - "Name": "WindowsUpdateAdvancedOptions", - "Areas": [ "AreaUpdateAndSecurity" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:windowsupdate-options" - }, - { - "Name": "WindowsUpdateRestartOptions", - "Areas": [ "AreaUpdateAndSecurity" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:windowsupdate-restartoptions" - }, - { - "Name": "WindowsUpdateViewUpdateHistory", - "Areas": [ "AreaUpdateAndSecurity" ], - "Type": "AppSettingsApp", - "Command": "ms-settings:windowsupdate-history" - }, - { - "Name": "WindowsUpdateViewOptionalUpdates", - "Areas": [ "AreaUpdateAndSecurity" ], - "Type": "AppSettingsApp", - "IntroducedInBuild": 19041, - "Note": "NoteSince19041", - "Command": "ms-settings:windowsupdate-optionalupdates" - }, - { - "Name": "WorkplaceProvisioning", - "Areas": [ "AreaUserAccounts" ], - "Type": "AppSettingsApp", - "Note": "NoteWorkplaceProvisioning", - "Command": "ms-settings:workplace-provisioning" - }, - { - "Name": "Provisioning", - "Areas": [ "AreaUserAccounts" ], - "Type": "AppSettingsApp", - "Note": "NoteMobileProvisioning", - "Command": "ms-settings:provisioning" - }, - { - "Name": "WindowsAnywhere", - "Areas": [ "AreaUserAccounts" ], - "Type": "AppSettingsApp", - "Note": "NoteWindowsAnywhere", - "Command": "ms-settings:windowsanywhere" - }, - { - "Name": "AccessibilityOptions", - "Areas": [ "AreaEaseOfAccess" ], - "Type": "AppControlPanel", - "AltNames": [ "access.cpl" ], - "Command": "control access.cpl" - }, - { - "Name": "ActionCenter", - "Areas": [ "AreaSystemAndSecurity" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.ActionCenter" - }, - { - "Name": "AddHardware", - "Areas": [ "AreaHardwareAndSound" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.AddHardware" - }, - { - "Name": "AddRemovePrograms", - "Areas": [ "AreaHardwareAndSound" ], - "Type": "AppControlPanel", - "AltNames": [ "appwiz.cpl" ], - "Command": "control appwiz.cpl" - }, - { - "Name": "AdministrativeTools", - "Areas": [ "AreaSystemAndSecurity" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.AdministrativeTools" - }, - { - "Name": "AutoPlay", - "Areas": [ "AreaPrograms" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.AutoPlay" - }, - { - "Name": "BackupAndRestore", - "Areas": [ "AreaSystemAndSecurity" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.BackupAndRestore" - }, - { - "Name": "BiometricDevices", - "Areas": [ "AreaHardwareAndSound" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.BiometricDevices" - }, - { - "Name": "BitLockerDriveEncryption", - "Areas": [ "AreaSystemAndSecurity" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.BitLockerDriveEncryption" - }, - { - "Name": "BluetoothDevices", - "Areas": [ "AreaHardwareAndSound" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.BluetoothDevices" - }, - { - "Name": "ColorManagement", - "Areas": [ "AreaAppearanceAndPersonalization" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.ColorManagement" - }, - { - "Name": "CredentialManager", - "Areas": [ "AreaUserAccounts" ], - "Type": "AppControlPanel", - "AltNames": [ "Password" ], - "Command": "control /name Microsoft.CredentialManager" - }, - { - "Name": "ClientServiceForNetWare", - "Areas": [ "AreaPrograms" ], - "Type": "AppControlPanel", - "AltNames": [ "nwc.cpl" ], - "Command": "control nwc.cpl" - }, - { - "Name": "DateAndTime", - "Areas": [ "AreaClockAndRegion" ], - "Type": "AppControlPanel", - "AltNames": [ "timedate.cpl" ], - "Command": "control /name Microsoft.DateAndTime" - }, - { - "Name": "DefaultLocation", - "Areas": [ "AreaClockAndRegion" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.DefaultLocation" - }, - { - "Name": "DefaultPrograms", - "Areas": [ "AreaPrograms" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.DefaultPrograms" - }, - { - "Name": "DeviceManager", - "Areas": [ "AreaHardwareAndSound" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.DeviceManager" - }, - { - "Name": "DevicesAndPrinters", - "Areas": [ "AreaHardwareAndSound" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.DevicesAndPrinters" - }, - { - "Name": "EaseOfAccessCenter", - "Areas": [ "AreaEaseOfAccess" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.EaseOfAccessCenter" - }, - { - "Name": "FolderOptions", - "Areas": [ "AreaAppearanceAndPersonalization" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.FolderOptions" - }, - { - "Name": "Fonts", - "Areas": [ "AreaAppearanceAndPersonalization" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.Fonts" - }, - { - "Name": "GameControllers", - "Areas": [ "AreaHardwareAndSound" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.GameControllers" - }, - { - "Name": "GetPrograms", - "Areas": [ "AreaPrograms" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.GetPrograms" - }, - { - "Name": "GettingStarted", - "Type": "AppControlPanel", - "Command": "control /name Microsoft.GettingStarted" - }, - { - "Name": "HomeGroup", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.HomeGroup" - }, - { - "Name": "IndexingOptions", - "Areas": [ "AreaSystemAndSecurity" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.IndexingOptions" - }, - { - "Name": "Infrared", - "Areas": [ "AreaHardwareAndSound" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.Infrared" - }, - { - "Name": "InternetOptions", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppControlPanel", - "AltNames": [ "inetcpl.cpl" ], - "Command": "control /name Microsoft.InternetOptions" - }, - { - "Name": "MailMicrosoftExchangeOrWindowsMessaging", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppControlPanel", - "AltNames": [ "mlcfg32.cpl" ], - "Command": "control mlcfg32.cpl" - }, - { - "Name": "Mouse", - "Areas": [ "AreaHardwareAndSound" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.Mouse" - }, - { - "Name": "NetworkAndSharingCenter", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.NetworkAndSharingCenter" - }, - { - "Name": "NetworkConnection", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppControlPanel", - "Command": "control netconnections" - }, - { - "Name": "NetworkSetupWizard", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppControlPanel", - "AltNames": [ "netsetup.cpl" ], - "Command": "control netsetup.cpl" - }, - { - "Name": "OdbcDataSourceAdministrator32Bit", - "Areas": [ "AreaSystemAndSecurity", "AreaAdministrativeTools" ], - "Type": "AppControlPanel", - "AltNames": [ "odbccp32.cpl" ], - "Command": "%windir%/syswow64/odbcad32.exe" - }, - { - "Name": "OdbcDataSourceAdministrator64Bit", - "Areas": [ "AreaSystemAndSecurity", "AreaAdministrativeTools" ], - "Type": "AppControlPanel", - "Command": "%windir%/system32/odbcad32.exe" - }, - { - "Name": "OfflineFiles", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.OfflineFiles" - }, - { - "Name": "ParentalControls", - "Areas": [ "AreaUserAccounts" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.ParentalControls" - }, - { - "Name": "PenAndInputDevices", - "Areas": [ "AreaHardwareAndSound" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.PenAndInputDevices" - }, - { - "Name": "PenAndTouch", - "Areas": [ "AreaHardwareAndSound" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.PenAndTouch" - }, - { - "Name": "PeopleNearMe", - "Areas": [ "AreaUserAccounts" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.PeopleNearMe" - }, - { - "Name": "PerformanceInformationAndTools", - "Areas": [ "AreaSystemAndSecurity" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.PerformanceInformationAndTools" - }, - { - "Name": "PhoneAndModemOptions", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.PhoneAndModemOptions" - }, - { - "Name": "PhoneAndModem", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppControlPanel", - "AltNames": [ "modem.cpl" ], - "Command": "control /name Microsoft.PhoneAndModem" - }, - { - "Name": "PowerOptions", - "Areas": [ "AreaSystemAndSecurity" ], - "Type": "AppControlPanel", - "AltNames": [ "powercfg.cpl" ], - "Command": "control /name Microsoft.PowerOptions" - }, - { - "Name": "Printers", - "Areas": [ "AreaHardwareAndSound" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.Printers" - }, - { - "Name": "ProblemReportsAndSolutions", - "Areas": [ "AreaSystemAndSecurity" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.ProblemReportsAndSolutions" - }, - { - "Name": "ProgramsAndFeatures", - "Areas": [ "AreaPrograms" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.ProgramsAndFeatures" - }, - { - "Name": "Recovery", - "Areas": [ "AreaSystemAndSecurity" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.Recovery" - }, - { - "Name": "RegionAndLanguage", - "Areas": [ "AreaClockAndRegion" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.RegionAndLanguage" - }, - { - "Name": "RemoteAppAndDesktopConnections", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.RemoteAppAndDesktopConnections" - }, - { - "Name": "ScannersAndCameras", - "Areas": [ "AreaHardwareAndSound" ], - "Type": "AppControlPanel", - "AltNames": [ "sticpl.cpl" ], - "Command": "control /name Microsoft.ScannersAndCameras" - }, - { - "Name": "ScheduledTasks", - "Areas": [ "AreaSystemAndSecurity" ], - "Type": "AppControlPanel", - "AltNames": [ "schedtasks" ], - "Command": "control schedtasks" - }, - { - "Name": "SecurityCenter", - "Areas": [ "AreaSystemAndSecurity" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.SecurityCenter" - }, - { - "Name": "Sound", - "Areas": [ "AreaHardwareAndSound" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.Sound" - }, - { - "Name": "SpeechRecognition", - "Areas": [ "AreaEaseOfAccess" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.SpeechRecognition" - }, - { - "Name": "SyncCenter", - "Areas": [ "AreaSystemAndSecurity" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.SyncCenter" - }, - { - "Name": "System", - "Areas": [ "AreaSystemAndSecurity" ], - "Type": "AppControlPanel", - "AltNames": [ "sysdm.cpl" ], - "Command": "control sysdm.cpl" - }, - { - "Name": "TabletPcSettings", - "Areas": [ "AreaSystemAndSecurity" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.TabletPCSettings" - }, - { - "Name": "TextToSpeech", - "Areas": [ "AreaEaseOfAccess" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.TextToSpeech" - }, - { - "Name": "UserAccounts", - "Areas": [ "AreaUserAccounts" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.UserAccounts" - }, - { - "Name": "WelcomeCenter", - "Areas": [ "AreaSystemAndSecurity" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.WelcomeCenter" - }, - { - "Name": "WindowsAnytimeUpgrade", - "Areas": [ "AreaSystemAndSecurity" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.WindowsAnytimeUpgrade" - }, - { - "Name": "WindowsCardSpace", - "Areas": [ "AreaHardwareAndSound" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.CardSpace" - }, - { - "Name": "WindowsDefender", - "Areas": [ "AreaSystemAndSecurity" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.WindowsDefender" - }, - { - "Name": "WindowsFirewall", - "Areas": [ "AreaSystemAndSecurity" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.WindowsFirewall" - }, - { - "Name": "WindowsMobilityCenter", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppControlPanel", - "Command": "control /name Microsoft.MobilityCenter" - }, - { - "Name": "DisplayProperties", - "Areas": [ "AreaHardwareAndSound" ], - "Type": "AppControlPanel", - "AltNames": [ "desk.cpl" ], - "Command": "control Desk.cpl" - }, - { - "Name": "FindFast", - "Areas": [ "AreaSystemAndSecurity" ], - "Type": "AppControlPanel", - "AltNames": [ "findfast.cpl" ], - "Command": "control FindFast.cpl" - }, - { - "Name": "RegionalSettingsProperties", - "Areas": [ "AreaEaseOfAccess" ], - "Type": "AppControlPanel", - "AltNames": [ "intl.cpl" ], - "Command": "control Intl.cpl" - }, - { - "Name": "JoystickProperties", - "Areas": [ "AreaHardwareAndSound" ], - "Type": "AppControlPanel", - "AltNames": [ "joy.cpl" ], - "Command": "control Joy.cpl" - }, - { - "Name": "MouseFontsKeyboardAndPrintersProperties", - "Areas": [ "AreaHardwareAndSound" ], - "Type": "AppControlPanel", - "AltNames": [ "main.cpl" ], - "Command": "control Main.cpl" - }, - { - "Name": "MultimediaProperties", - "Areas": [ "AreaHardwareAndSound" ], - "Type": "AppControlPanel", - "AltNames": [ "mmsys.cpl" ], - "Command": "control Mmsys.cpl" - }, - { - "Name": "NetworkProperties", - "Areas": [ "AreaNetworkAndInternet" ], - "Type": "AppControlPanel", - "AltNames": [ "netcpl.cpl" ], - "Command": "control Netcpl.cpl" - }, - { - "Name": "PasswordProperties", - "Areas": [ "AreaUserAccounts" ], - "Type": "AppControlPanel", - "AltNames": [ "password.cpl" ], - "Command": "control Password.cpl" - }, - { - "Name": "SystemPropertiesAndAddNewHardwareWizard", - "Areas": [ "AreaHardwareAndSound" ], - "Type": "AppControlPanel", - "AltNames": [ "sysdm.cpl" ], - "Command": "control Sysdm.cpl" - }, - { - "Name": "DesktopThemes", - "Areas": [ "AreaAppearanceAndPersonalization" ], - "Type": "AppControlPanel", - "AltNames": [ "themes.cpl" ], - "Command": "control Themes.cpl" - }, - { - "Name": "MicrosoftMailPostOffice", - "Areas": [ "AreaPrograms" ], - "Type": "AppControlPanel", - "AltNames": [ "wgpocpl.cpl" ], - "Command": "control Wgpocpl.cpl" - } -]} + { + "Name": "OpenControlPanel", + "Type": "AppControlPanel", + "Command": "control.exe", + "ShowAsFirstResult": true + }, + { + "Name": "AccessWorkOrSchool", + "Areas": [ "AreaAccounts" ], + "Type": "AppSettingsApp", + "AltNames": [ "Workplace" ], + "Command": "ms-settings:workplace" + }, + { + "Name": "EmailAndAppAccounts", + "Areas": [ "AreaAccounts" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:emailandaccounts" + }, + { + "Name": "FamilyAndOtherPeople", + "Areas": [ "AreaAccounts" ], + "Type": "AppSettingsApp", + "AltNames": [ "OtherUsers" ], + "Command": "ms-settings:otherusers" + }, + { + "Name": "SetUpKiosk", + "Areas": [ "AreaAccounts" ], + "Type": "AppSettingsApp", + "AltNames": [ "AssignedAccess" ], + "Command": "ms-settings:assignedaccess" + }, + { + "Name": "SignInOptions", + "Areas": [ "AreaAccounts" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:signinoptions" + }, + { + "Name": "SignInOptionsDynamicLock", + "Areas": [ "AreaAccounts" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:signinoptions-dynamiclock" + }, + { + "Name": "SyncYourSettings", + "Areas": [ "AreaAccounts" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:sync" + }, + { + "Name": "WindowsHelloSetupFace", + "Areas": [ "AreaAccounts" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:signinoptions-launchfaceenrollment" + }, + { + "Name": "WindowsHelloSetupFingerprint", + "Areas": [ "AreaAccounts" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:signinoptions-launchfingerprintenrollment" + }, + { + "Name": "YourInfo", + "Areas": [ "AreaAccounts" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:yourinfo" + }, + { + "Name": "AppsAndFeatures", + "Areas": [ "AreaApps" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:appsfeatures" + }, + { + "Name": "AppFeatures", + "Areas": [ "AreaApps" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:appsfeatures-app" + }, + { + "Name": "AppsForWebsites", + "Areas": [ "AreaApps" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:appsforwebsites" + }, + { + "Name": "DefaultApps", + "Areas": [ "AreaApps" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:defaultapps" + }, + { + "Name": "ManageOptionalFeatures", + "Areas": [ "AreaApps" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:optionalfeatures" + }, + { + "Name": "OfflineMaps", + "Areas": [ "AreaApps" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:maps" + }, + { + "Name": "OfflineMapsDownloadMaps", + "Areas": [ "AreaApps" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:maps-downloadmaps" + }, + { + "Name": "StartupApps", + "Areas": [ "AreaApps" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:startupapps" + }, + { + "Name": "VideoPlayback", + "Areas": [ "AreaApps" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:videoplayback" + }, + { + "Name": "Notifications", + "Areas": [ "AreaCortana" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:cortana-notifications" + }, + { + "Name": "MoreDetails", + "Areas": [ "AreaCortana" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:cortana-moredetails" + }, + { + "Name": "PermissionsAndHistory", + "Areas": [ "AreaCortana" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:cortana-permissions" + }, + { + "Name": "WindowsSearch", + "Areas": [ "AreaCortana" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:cortana-windowssearch" + }, + { + "Name": "CortanaLanguage", + "Areas": [ "AreaCortana" ], + "Type": "AppSettingsApp", + "AltNames": [ "Talk" ], + "Command": "ms-settings:cortana-language" + }, + { + "Name": "Cortana", + "Areas": [ "AreaCortana" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:cortana" + }, + { + "Name": "TalkToCortana", + "Areas": [ "AreaCortana" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:cortana-talktocortana" + }, + { + "Name": "AutoPlay", + "Areas": [ "AreaDevices" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:autoplay" + }, + { + "Name": "Bluetooth", + "Areas": [ "AreaDevices" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:bluetooth" + }, + { + "Name": "ConnectedDevices", + "Areas": [ "AreaDevices" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:connecteddevices" + }, + { + "Name": "DefaultCamera", + "Areas": [ "AreaDevices" ], + "Type": "AppSettingsApp", + "DeprecatedInBuild": 17763, + "Note": "NoteDeprecated17763", + "Command": "ms-settings:camera" + }, + { + "Name": "MouseAndTouchpad", + "Areas": [ "AreaDevices" ], + "Type": "AppSettingsApp", + "Note": "NoteTouchpad", + "Command": "ms-settings:mousetouchpad" + }, + { + "Name": "PenAndWindowsInk", + "Areas": [ "AreaDevices" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:pen" + }, + { + "Name": "PrintersAndScanners", + "Areas": [ "AreaDevices" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:printers" + }, + { + "Name": "Touchpad", + "Areas": [ "AreaDevices" ], + "Type": "AppSettingsApp", + "Note": "NoteTouchpad", + "Command": "ms-settings:devices-touchpad" + }, + { + "Name": "Typing", + "Areas": [ "AreaDevices" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:typing" + }, + { + "Name": "Usb", + "Areas": [ "AreaDevices" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:usb" + }, + { + "Name": "Wheel", + "Areas": [ "AreaDevices" ], + "Type": "AppSettingsApp", + "Note": "NoteDialPaired", + "Command": "ms-settings:wheel" + }, + { + "Name": "Phone", + "Areas": [ "AreaPhone" ], + "Type": "AppSettingsApp", + "AltNames": [ "MobileDevices" ], + "Command": "ms-settings:mobile-devices" + }, + { + "Name": "Audio", + "Areas": [ "AreaEaseOfAccess" ], + "Type": "AppSettingsApp", + "AltNames": [ "Mono", "Volume", "AudioAlerts" ], + "Command": "ms-settings:easeofaccess-audio" + }, + { + "Name": "ClosedCaptions", + "Areas": [ "AreaEaseOfAccess" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:easeofaccess-closedcaptioning" + }, + { + "Name": "ColorFilters", + "Areas": [ "AreaEaseOfAccess" ], + "Type": "AppSettingsApp", + "AltNames": [ "InvertedColors", "Grayscale", "RedGreen", "BlueYellow", "GreenWeek", "RedWeek", "deuteranopia", "protanopia", "tritanopia" ], + "Command": "ms-settings:easeofaccess-colorfilter" + }, + { + "Name": "MousePointer", + "Areas": [ "AreaEaseOfAccess" ], + "Type": "AppSettingsApp", + "AltNames": [ "TouchFeedback" ], + "Command": "ms-settings:easeofaccess-MousePointer" + }, + { + "Name": "Display", + "Areas": [ "AreaEaseOfAccess" ], + "Type": "AppSettingsApp", + "AltNames": [ "Transparency", "Animations", "ScrollBars", "Size" ], + "Command": "ms-settings:easeofaccess-display" + }, + { + "Name": "EyeControl", + "Areas": [ "AreaEaseOfAccess" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:easeofaccess-eyecontrol" + }, + { + "Name": "Fonts", + "Areas": [ "AreaEaseOfAccess" ], + "Type": "AppSettingsApp", + "DeprecatedInBuild": 17763, + "Note": "NoteDeprecated17763", + "Command": "ms-settings:fonts" + }, + { + "Name": "HighContrast", + "Areas": [ "AreaEaseOfAccess" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:easeofaccess-highcontrast" + }, + { + "Name": "Keyboard", + "Areas": [ "AreaEaseOfAccess" ], + "Type": "AppSettingsApp", + "AltNames": [ "PrintScreen", "Shortcuts", "OnScreen", "Keys", "ScrollLock", "CapsLock", "NumLock" ], + "Command": "ms-settings:easeofaccess-keyboard" + }, + { + "Name": "Magnifier", + "Areas": [ "AreaEaseOfAccess" ], + "Type": "AppSettingsApp", + "AltNames": [ "Zoom" ], + "Command": "ms-settings:easeofaccess-magnifier" + }, + { + "Name": "Mouse", + "Areas": [ "AreaEaseOfAccess" ], + "Type": "AppSettingsApp", + "AltNames": [ "Keypad", "Touch" ], + "Command": "ms-settings:easeofaccess-mouse" + }, + { + "Name": "Narrator", + "Areas": [ "AreaEaseOfAccess" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:easeofaccess-narrator" + }, + { + "Name": "OtherOptions", + "Areas": [ "AreaEaseOfAccess" ], + "Type": "AppSettingsApp", + "DeprecatedInBuild": 17763, + "Note": "NoteDeprecated17763", + "Command": "ms-settings:easeofaccess-otheroptions" + }, + { + "Name": "Speech", + "Areas": [ "AreaEaseOfAccess" ], + "Type": "AppSettingsApp", + "AltNames": [ "Recognition", "Talk" ], + "Command": "ms-settings:easeofaccess-speechrecognition" + }, + { + "Name": "Extras", + "Areas": [ "AreaExtras" ], + "Type": "AppSettingsApp", + "Note": "NoteThirdParty", + "Command": "ms-settings:extras" + }, + { + "Name": "Broadcasting", + "Areas": [ "AreaGaming" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:gaming-broadcasting" + }, + { + "Name": "GameBar", + "Areas": [ "AreaGaming" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:gaming-gamebar" + }, + { + "Name": "GameDvr", + "Areas": [ "AreaGaming" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:gaming-gamedvr" + }, + { + "Name": "GameMode", + "Areas": [ "AreaGaming" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:gaming-gamemode" + }, + { + "Name": "PlayingGameFullScreen", + "Areas": [ "AreaGaming" ], + "Type": "AppSettingsApp", + "AltNames": [ "QuietMomentsGame" ], + "Command": "ms-settings:quietmomentsgame" + }, + { + "Name": "TruePlay", + "Areas": [ "AreaGaming" ], + "Type": "AppSettingsApp", + "DeprecatedInBuild": 17763, + "Note": "NoteDeprecated17763", + "Command": "ms-settings:gaming-trueplay" + }, + { + "Name": "XboxNetworking", + "Areas": [ "AreaGaming" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:gaming-xboxnetworking" + }, + { + "Name": "AudioAndSpeech", + "Areas": [ "AreaMixedReality" ], + "Type": "AppSettingsApp", + "AltNames": [ "HolographicAudio" ], + "Note": "NoteMixedReality", + "Command": "ms-settings:holographic-audio" + }, + { + "Name": "Environment", + "Areas": [ "AreaMixedReality" ], + "Type": "AppSettingsApp", + "AltNames": [ "HolographicEnvironment" ], + "Note": "NoteMixedReality", + "Command": "ms-settings:privacy-holographic-environment" + }, + { + "Name": "HeadsetDisplay", + "Areas": [ "AreaMixedReality" ], + "Type": "AppSettingsApp", + "AltNames": [ "HolographicHeadset" ], + "Note": "NoteMixedReality", + "Command": "ms-settings:holographic-headset" + }, + { + "Name": "Uninstall", + "Areas": [ "AreaMixedReality" ], + "Type": "AppSettingsApp", + "AltNames": [ "HolographicManagement" ], + "Note": "NoteMixedReality", + "Command": "ms-settings:holographic-management" + }, + { + "Name": "AirplaneMode", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:network-airplanemode" + }, + { + "Name": "Proximity", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:proximity" + }, + { + "Name": "CellularAndSim", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:network-cellular" + }, + { + "Name": "DataUsage", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:datausage" + }, + { + "Name": "DialUp", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:network-dialup" + }, + { + "Name": "DirectAccess", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppSettingsApp", + "Note": "NoteDirectAccess", + "Command": "ms-settings:network-directaccess" + }, + { + "Name": "Ethernet", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppSettingsApp", + "AltNames": [ "DNS", "Sdns", "SecureDNS", "Gateway", "Dhcp", "Ip" ], + "Command": "ms-settings:network-ethernet" + }, + { + "Name": "ManageKnownNetworks", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppSettingsApp", + "AltNames": [ "WiFiSettings", "ShortNameWiFi" ], + "Note": "NoteWiFiAdapter", + "Command": "ms-settings:network-wifisettings" + }, + { + "Name": "MobileHotspot", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:network-mobilehotspot" + }, + { + "Name": "NFC", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppSettingsApp", + "AltNames": [ "NFCTransactions" ], + "Command": "ms-settings:nfctransactions" + }, + { + "Name": "Proxy", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:network-proxy" + }, + { + "Name": "NetworkStatus", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:network-status" + }, + { + "Name": "Network", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppSettingsApp", + "AltNames": [ "DNS", "Sdns", "SecureDNS", "Gateway", "Dhcp", "Ip" ], + "Command": "ms-settings:network" + }, + { + "Name": "Vpn", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:network-vpn" + }, + { + "Name": "WiFi", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppSettingsApp", + "AltNames": [ "Wireless", "MeteredConnection", "ShortNameWiFi" ], + "Note": "NoteWiFiAdapter", + "Command": "ms-settings:network-wifi" + }, + { + "Name": "WiFiCalling", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppSettingsApp", + "AltNames": [ "ShortNameWiFi" ], + "Note": "NoteWiFiAdapter", + "Command": "ms-settings:network-wificalling" + }, + { + "Name": "Background", + "Areas": [ "AreaPersonalization" ], + "Type": "AppSettingsApp", + "AltNames": [ "Wallpaper", "Picture", "Image" ], + "Command": "ms-settings:personalization-background" + }, + { + "Name": "ChooseWhichFoldersAppearOnStart", + "Areas": [ "AreaPersonalization" ], + "Type": "AppSettingsApp", + "AltNames": [ "StartPlaces" ], + "Command": "ms-settings:personalization-start-places" + }, + { + "Name": "Colors", + "Areas": [ "AreaPersonalization" ], + "Type": "AppSettingsApp", + "AltNames": [ "DarkMode", "LightMode", "DarkColor", "LightColor", "AppColor", "TaskbarColor", "WindowBorder" ], + "Command": "ms-settings:colors" + }, + { + "Name": "Glance", + "Areas": [ "AreaPersonalization" ], + "Type": "AppSettingsApp", + "DeprecatedInBuild": 17763, + "Note": "NoteDeprecated17763", + "Command": "ms-settings:personalization-glance" + }, + { + "Name": "LockScreen", + "Areas": [ "AreaPersonalization" ], + "Type": "AppSettingsApp", + "AltNames": [ "Image", "Picture" ], + "Command": "ms-settings:lockscreen" + }, + { + "Name": "NavigationBar", + "Areas": [ "AreaPersonalization" ], + "Type": "AppSettingsApp", + "DeprecatedInBuild": 17763, + "Note": "NoteDeprecated17763", + "Command": "ms-settings:personalization-navbar" + }, + { + "Name": "PersonalizationCategory", + "Type": "AppSettingsApp", + "Command": "ms-settings:personalization" + }, + { + "Name": "Start", + "Areas": [ "AreaPersonalization" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:personalization-start" + }, + { + "Name": "Taskbar", + "Areas": [ "AreaPersonalization" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:taskbar" + }, + { + "Name": "Themes", + "Areas": [ "AreaPersonalization" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:themes" + }, + { + "Name": "AddYourPhone", + "Areas": [ "AreaPhone" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:mobile-devices-addphone", + "Note": "NoteAddYourPhone" + }, + { + "Name": "DirectOpenYourPhone", + "Areas": [ "AreaPhone" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:mobile-devices-addphone-direct", + "Note": "NoteAddYourPhone" + }, + { + "Name": "AccessoryApps", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "DeprecatedInBuild": 17763, + "Note": "NoteDeprecated17763", + "Command": "ms-settings:privacy-accessoryapps" + }, + { + "Name": "AccountInfo", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-accountinfo" + }, + { + "Name": "ActivityHistory", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-activityhistory" + }, + { + "Name": "AdvertisingId", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "DeprecatedInBuild": 17763, + "Note": "NoteDeprecated17763", + "Command": "ms-settings:privacy-advertisingid" + }, + { + "Name": "AppDiagnostics", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-appdiagnostics" + }, + { + "Name": "AutomaticFileDownloads", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-automaticfiledownloads" + }, + { + "Name": "BackgroundApps", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-backgroundapps" + }, + { + "Name": "Calendar", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-calendar" + }, + { + "Name": "CallHistory", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-callhistory" + }, + { + "Name": "Camera", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-webcam" + }, + { + "Name": "Contacts", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-contacts" + }, + { + "Name": "Documents", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-documents" + }, + { + "Name": "Email", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-email" + }, + { + "Name": "EyeTracker", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Note": "NoteEyetrackerHardware", + "Command": "ms-settings:privacy-eyetracker" + }, + { + "Name": "FeedbackAndDiagnostics", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-feedback" + }, + { + "Name": "FileSystem", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-broadfilesystemaccess" + }, + { + "Name": "General", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-general" + }, + { + "Name": "InkingAndTyping", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "AltNames": [ "SpeechTyping" ], + "Command": "ms-settings:privacy-speechtyping" + }, + { + "Name": "Location", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-location" + }, + { + "Name": "Messaging", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-messaging" + }, + { + "Name": "Microphone", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-microphone" + }, + { + "Name": "Motion", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-motion" + }, + { + "Name": "Notifications", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-notifications" + }, + { + "Name": "OtherDevices", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "AltNames": [ "CustomDevices" ], + "Command": "ms-settings:privacy-customdevices" + }, + { + "Name": "PhoneCalls", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-phonecalls" + }, + { + "Name": "Pictures", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-pictures" + }, + { + "Name": "Radios", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-radios" + }, + { + "Name": "Speech", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-speech" + }, + { + "Name": "Tasks", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-tasks" + }, + { + "Name": "Videos", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-videos" + }, + { + "Name": "VoiceActivation", + "Areas": [ "AreaPrivacy" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:privacy-voiceactivation" + }, + { + "Name": "Accounts", + "Areas": [ "AreaSurfaceHub" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:surfacehub-accounts" + }, + { + "Name": "SessionCleanup", + "Areas": [ "AreaSurfaceHub" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:surfacehub-sessioncleanup" + }, + { + "Name": "TeamConferencing", + "Areas": [ "AreaSurfaceHub" ], + "Type": "AppSettingsApp", + "AltNames": [ "calling" ], + "Command": "ms-settings:surfacehub-calling" + }, + { + "Name": "TeamDeviceManagement", + "Areas": [ "AreaSurfaceHub" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:surfacehub-devicemanagenent" + }, + { + "Name": "WelcomeScreen", + "Areas": [ "AreaSurfaceHub" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:surfacehub-welcome" + }, + { + "Name": "About", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "AltNames": [ "Ram", "Processor", "Os", "Id", "Edition", "Version" ], + "Command": "ms-settings:about" + }, + { + "Name": "AdvancedDisplaySettings", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "Note": "NoteDisplayGraphics", + "Command": "ms-settings:display-advanced" + }, + { + "Name": "AppVolumeAndDevicePreferences", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "IntroducedInBuild": 18362, + "Note": "NoteSince18362", + "Command": "ms-settings:apps-volume" + }, + { + "Name": "BatterySaver", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "Note": "NoteBattery", + "Command": "ms-settings:batterysaver" + }, + { + "Name": "BatterySaverSettings", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "Note": "NoteBattery", + "Command": "ms-settings:batterysaver-settings" + }, + { + "Name": "BatteryUse", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "AltNames": [ "BatterySaverUsageDetails" ], + "Note": "NoteBattery", + "Command": "ms-settings:batterysaver-usagedetails" + }, + { + "Name": "Clipboard", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:clipboard" + }, + { + "Name": "Display", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "AltNames": [ "NightLight", "BlueLight", "WarmerColor", "RedEye" ], + "Command": "ms-settings:display" + }, + { + "Name": "DefaultSaveLocations", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:savelocations" + }, + { + "Name": "ScreenRotation", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:screenrotation" + }, + { + "Name": "DuplicatingMyDisplay", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "AltNames": [ "Presentation" ], + "Command": "ms-settings:quietmomentspresentation" + }, + { + "Name": "DuringTheseHours", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "AltNames": [ "Scheduled" ], + "Command": "ms-settings:quietmomentsscheduled" + }, + { + "Name": "Encryption", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:deviceencryption" + }, + { + "Name": "FocusAssistQuietHours", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:quiethours" + }, + { + "Name": "FocusAssistQuietMoments", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:quietmomentshome" + }, + { + "Name": "GraphicsSettings", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "AltNames": [ "AdvancedGraphics" ], + "Note": "NoteAdvancedGraphics", + "Command": "ms-settings:display-advancedgraphics" + }, + { + "Name": "Messaging", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:messaging" + }, + { + "Name": "Multitasking", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "AltNames": [ "Timeline", "Tab", "AltAndTab", "VirtualDesktops" ], + "Command": "ms-settings:multitasking" + }, + { + "Name": "NightLightSettings", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:nightlight" + }, + { + "Name": "PhoneDefaultApps", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:phone-defaultapps" + }, + { + "Name": "ProjectingToThisPc", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:project" + }, + { + "Name": "SharedExperiences", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "AltNames": [ "Crossdevice" ], + "Command": "ms-settings:crossdevice" + }, + { + "Name": "TabletMode", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:tabletmode" + }, + { + "Name": "Taskbar", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:taskbar" + }, + { + "Name": "NotificationsAndActions", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:notifications" + }, + { + "Name": "RemoteDesktop", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:remotedesktop" + }, + { + "Name": "Phone", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "DeprecatedInBuild": 17763, + "Note": "NoteDeprecated17763", + "Command": "ms-settings:phone" + }, + { + "Name": "PowerAndSleep", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:powersleep" + }, + { + "Name": "Sound", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:sound" + }, + { + "Name": "StorageSense", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:storagesense" + }, + { + "Name": "StoragePolicies", + "Areas": [ "AreaSystem" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:storagepolicies" + }, + { + "Name": "DateAndTime", + "Areas": [ "AreaTimeAndLanguage" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:dateandtime" + }, + { + "Name": "JapanImeSettings", + "Areas": [ "AreaTimeAndLanguage" ], + "Type": "AppSettingsApp", + "AltNames": [ "jpnime" ], + "Note": "NoteImeJapan", + "Command": "ms-settings:regionlanguage-jpnime" + }, + { + "Name": "Region", + "Areas": [ "AreaTimeAndLanguage" ], + "Type": "AppSettingsApp", + "AltNames": [ "RegionFormatting" ], + "Command": "ms-settings:regionformatting" + }, + { + "Name": "Keyboard", + "Areas": [ "AreaTimeAndLanguage" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:keyboard" + }, + { + "Name": "RegionalLanguage", + "Areas": [ "AreaTimeAndLanguage" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:regionlanguage" + }, + { + "Name": "BopomofoIme", + "Areas": [ "AreaTimeAndLanguage" ], + "Type": "AppSettingsApp", + "AltNames": [ "bpmf" ], + "Command": "ms-settings:regionlanguage-bpmfime" + }, + { + "Name": "CangjieIme", + "Areas": [ "AreaTimeAndLanguage" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:regionlanguage-cangjieime" + }, + { + "Name": "PinyinImeSettingsDomainLexicon", + "Areas": [ "AreaTimeAndLanguage" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:regionlanguage-chsime-pinyin-domainlexicon" + }, + { + "Name": "PinyinImeSettingsKeyConfiguration", + "Areas": [ "AreaTimeAndLanguage" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:regionlanguage-chsime-pinyin-keyconfig" + }, + { + "Name": "PinyinImeSettingsUdp", + "Areas": [ "AreaTimeAndLanguage" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:regionlanguage-chsime-pinyin-udp" + }, + { + "Name": "WubiImeSettingsUdp", + "Areas": [ "AreaTimeAndLanguage" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:regionlanguage-chsime-wubi-udp" + }, + { + "Name": "Quickime", + "Areas": [ "AreaTimeAndLanguage" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:regionlanguage-quickime" + }, + { + "Name": "PinyinImeSettings", + "Areas": [ "AreaTimeAndLanguage" ], + "Type": "AppSettingsApp", + "Note": "NoteImePinyin", + "Command": "ms-settings:regionlanguage-chsime-pinyin" + }, + { + "Name": "Speech", + "Areas": [ "AreaTimeAndLanguage" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:speech" + }, + { + "Name": "WubiImeSettings", + "Areas": [ "AreaTimeAndLanguage" ], + "Type": "AppSettingsApp", + "Note": "NoteImeWubi", + "Command": "ms-settings:regionlanguage-chsime-wubi" + }, + { + "Name": "Activation", + "Areas": [ "AreaUpdateAndSecurity" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:activation" + }, + { + "Name": "Backup", + "Areas": [ "AreaUpdateAndSecurity" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:backup" + }, + { + "Name": "DeliveryOptimization", + "Areas": [ "AreaUpdateAndSecurity" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:delivery-optimization" + }, + { + "Name": "FindMyDevice", + "Areas": [ "AreaUpdateAndSecurity" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:findmydevice" + }, + { + "Name": "ForDevelopers", + "Areas": [ "AreaUpdateAndSecurity" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:developers" + }, + { + "Name": "Recovery", + "Areas": [ "AreaUpdateAndSecurity" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:recovery" + }, + { + "Name": "Troubleshoot", + "Areas": [ "AreaUpdateAndSecurity" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:troubleshoot" + }, + { + "Name": "WindowsSecurity", + "Areas": [ "AreaUpdateAndSecurity" ], + "Type": "AppSettingsApp", + "AltNames": [ "WindowsDefender", "Firewall", "Virus", "CoreIsolation", "SecurityProcessor", "IsolatedBrowsing", "ExploitProtection" ], + "Command": "ms-settings:windowsdefender" + }, + { + "Name": "WindowsInsiderProgram", + "Areas": [ "AreaUpdateAndSecurity" ], + "Type": "AppSettingsApp", + "Note": "NoteEnrolledWIP", + "Command": "ms-settings:windowsinsider" + }, + { + "Name": "WindowsUpdate", + "Areas": [ "AreaUpdateAndSecurity" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:windowsupdate" + }, + { + "Name": "WindowsUpdateCheckForUpdates", + "Areas": [ "AreaUpdateAndSecurity" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:windowsupdate-action" + }, + { + "Name": "WindowsUpdateAdvancedOptions", + "Areas": [ "AreaUpdateAndSecurity" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:windowsupdate-options" + }, + { + "Name": "WindowsUpdateRestartOptions", + "Areas": [ "AreaUpdateAndSecurity" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:windowsupdate-restartoptions" + }, + { + "Name": "WindowsUpdateViewUpdateHistory", + "Areas": [ "AreaUpdateAndSecurity" ], + "Type": "AppSettingsApp", + "Command": "ms-settings:windowsupdate-history" + }, + { + "Name": "WindowsUpdateViewOptionalUpdates", + "Areas": [ "AreaUpdateAndSecurity" ], + "Type": "AppSettingsApp", + "IntroducedInBuild": 19041, + "Note": "NoteSince19041", + "Command": "ms-settings:windowsupdate-optionalupdates" + }, + { + "Name": "WorkplaceProvisioning", + "Areas": [ "AreaUserAccounts" ], + "Type": "AppSettingsApp", + "Note": "NoteWorkplaceProvisioning", + "Command": "ms-settings:workplace-provisioning" + }, + { + "Name": "Provisioning", + "Areas": [ "AreaUserAccounts" ], + "Type": "AppSettingsApp", + "Note": "NoteMobileProvisioning", + "Command": "ms-settings:provisioning" + }, + { + "Name": "WindowsAnywhere", + "Areas": [ "AreaUserAccounts" ], + "Type": "AppSettingsApp", + "Note": "NoteWindowsAnywhere", + "Command": "ms-settings:windowsanywhere" + }, + { + "Name": "AccessibilityOptions", + "Areas": [ "AreaEaseOfAccess" ], + "Type": "AppControlPanel", + "AltNames": [ "access.cpl" ], + "Command": "control access.cpl" + }, + { + "Name": "ActionCenter", + "Areas": [ "AreaSystemAndSecurity" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.ActionCenter" + }, + { + "Name": "AddHardware", + "Areas": [ "AreaHardwareAndSound" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.AddHardware" + }, + { + "Name": "AddRemovePrograms", + "Areas": [ "AreaHardwareAndSound" ], + "Type": "AppControlPanel", + "AltNames": [ "appwiz.cpl" ], + "Command": "control appwiz.cpl" + }, + { + "Name": "AdministrativeTools", + "Areas": [ "AreaSystemAndSecurity" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.AdministrativeTools" + }, + { + "Name": "AutoPlay", + "Areas": [ "AreaPrograms" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.AutoPlay" + }, + { + "Name": "BackupAndRestore", + "Areas": [ "AreaSystemAndSecurity" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.BackupAndRestore" + }, + { + "Name": "BiometricDevices", + "Areas": [ "AreaHardwareAndSound" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.BiometricDevices" + }, + { + "Name": "BitLockerDriveEncryption", + "Areas": [ "AreaSystemAndSecurity" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.BitLockerDriveEncryption" + }, + { + "Name": "BluetoothDevices", + "Areas": [ "AreaHardwareAndSound" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.BluetoothDevices" + }, + { + "Name": "ColorManagement", + "Areas": [ "AreaAppearanceAndPersonalization" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.ColorManagement" + }, + { + "Name": "CredentialManager", + "Areas": [ "AreaUserAccounts" ], + "Type": "AppControlPanel", + "AltNames": [ "Password" ], + "Command": "control /name Microsoft.CredentialManager" + }, + { + "Name": "ClientServiceForNetWare", + "Areas": [ "AreaPrograms" ], + "Type": "AppControlPanel", + "AltNames": [ "nwc.cpl" ], + "Command": "control nwc.cpl" + }, + { + "Name": "DateAndTime", + "Areas": [ "AreaClockAndRegion" ], + "Type": "AppControlPanel", + "AltNames": [ "timedate.cpl" ], + "Command": "control /name Microsoft.DateAndTime" + }, + { + "Name": "DefaultLocation", + "Areas": [ "AreaClockAndRegion" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.DefaultLocation" + }, + { + "Name": "DefaultPrograms", + "Areas": [ "AreaPrograms" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.DefaultPrograms" + }, + { + "Name": "DeviceManager", + "Areas": [ "AreaHardwareAndSound" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.DeviceManager" + }, + { + "Name": "DevicesAndPrinters", + "Areas": [ "AreaHardwareAndSound" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.DevicesAndPrinters" + }, + { + "Name": "EaseOfAccessCenter", + "Areas": [ "AreaEaseOfAccess" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.EaseOfAccessCenter" + }, + { + "Name": "FolderOptions", + "Areas": [ "AreaAppearanceAndPersonalization" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.FolderOptions" + }, + { + "Name": "Fonts", + "Areas": [ "AreaAppearanceAndPersonalization" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.Fonts" + }, + { + "Name": "GameControllers", + "Areas": [ "AreaHardwareAndSound" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.GameControllers" + }, + { + "Name": "GetPrograms", + "Areas": [ "AreaPrograms" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.GetPrograms" + }, + { + "Name": "GettingStarted", + "Type": "AppControlPanel", + "Command": "control /name Microsoft.GettingStarted" + }, + { + "Name": "HomeGroup", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.HomeGroup" + }, + { + "Name": "IndexingOptions", + "Areas": [ "AreaSystemAndSecurity" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.IndexingOptions" + }, + { + "Name": "Infrared", + "Areas": [ "AreaHardwareAndSound" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.Infrared" + }, + { + "Name": "InternetOptions", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppControlPanel", + "AltNames": [ "inetcpl.cpl" ], + "Command": "control /name Microsoft.InternetOptions" + }, + { + "Name": "MailMicrosoftExchangeOrWindowsMessaging", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppControlPanel", + "AltNames": [ "mlcfg32.cpl" ], + "Command": "control mlcfg32.cpl" + }, + { + "Name": "Mouse", + "Areas": [ "AreaHardwareAndSound" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.Mouse" + }, + { + "Name": "NetworkAndSharingCenter", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.NetworkAndSharingCenter" + }, + { + "Name": "NetworkConnection", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppControlPanel", + "Command": "control netconnections" + }, + { + "Name": "NetworkSetupWizard", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppControlPanel", + "AltNames": [ "netsetup.cpl" ], + "Command": "control netsetup.cpl" + }, + { + "Name": "OdbcDataSourceAdministrator32Bit", + "Areas": [ "AreaSystemAndSecurity", "AreaAdministrativeTools" ], + "Type": "AppControlPanel", + "AltNames": [ "odbccp32.cpl" ], + "Command": "%windir%/syswow64/odbcad32.exe" + }, + { + "Name": "OdbcDataSourceAdministrator64Bit", + "Areas": [ "AreaSystemAndSecurity", "AreaAdministrativeTools" ], + "Type": "AppControlPanel", + "Command": "%windir%/system32/odbcad32.exe" + }, + { + "Name": "OfflineFiles", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.OfflineFiles" + }, + { + "Name": "ParentalControls", + "Areas": [ "AreaUserAccounts" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.ParentalControls" + }, + { + "Name": "PenAndInputDevices", + "Areas": [ "AreaHardwareAndSound" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.PenAndInputDevices" + }, + { + "Name": "PenAndTouch", + "Areas": [ "AreaHardwareAndSound" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.PenAndTouch" + }, + { + "Name": "PeopleNearMe", + "Areas": [ "AreaUserAccounts" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.PeopleNearMe" + }, + { + "Name": "PerformanceInformationAndTools", + "Areas": [ "AreaSystemAndSecurity" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.PerformanceInformationAndTools" + }, + { + "Name": "PhoneAndModemOptions", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.PhoneAndModemOptions" + }, + { + "Name": "PhoneAndModem", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppControlPanel", + "AltNames": [ "modem.cpl" ], + "Command": "control /name Microsoft.PhoneAndModem" + }, + { + "Name": "PowerOptions", + "Areas": [ "AreaSystemAndSecurity" ], + "Type": "AppControlPanel", + "AltNames": [ "powercfg.cpl" ], + "Command": "control /name Microsoft.PowerOptions" + }, + { + "Name": "Printers", + "Areas": [ "AreaHardwareAndSound" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.Printers" + }, + { + "Name": "ProblemReportsAndSolutions", + "Areas": [ "AreaSystemAndSecurity" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.ProblemReportsAndSolutions" + }, + { + "Name": "ProgramsAndFeatures", + "Areas": [ "AreaPrograms" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.ProgramsAndFeatures" + }, + { + "Name": "Recovery", + "Areas": [ "AreaSystemAndSecurity" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.Recovery" + }, + { + "Name": "RegionAndLanguage", + "Areas": [ "AreaClockAndRegion" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.RegionAndLanguage" + }, + { + "Name": "RemoteAppAndDesktopConnections", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.RemoteAppAndDesktopConnections" + }, + { + "Name": "ScannersAndCameras", + "Areas": [ "AreaHardwareAndSound" ], + "Type": "AppControlPanel", + "AltNames": [ "sticpl.cpl" ], + "Command": "control /name Microsoft.ScannersAndCameras" + }, + { + "Name": "ScheduledTasks", + "Areas": [ "AreaSystemAndSecurity" ], + "Type": "AppControlPanel", + "AltNames": [ "schedtasks" ], + "Command": "control schedtasks" + }, + { + "Name": "SecurityCenter", + "Areas": [ "AreaSystemAndSecurity" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.SecurityCenter" + }, + { + "Name": "Sound", + "Areas": [ "AreaHardwareAndSound" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.Sound" + }, + { + "Name": "SpeechRecognition", + "Areas": [ "AreaEaseOfAccess" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.SpeechRecognition" + }, + { + "Name": "SyncCenter", + "Areas": [ "AreaSystemAndSecurity" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.SyncCenter" + }, + { + "Name": "System", + "Areas": [ "AreaSystemAndSecurity" ], + "Type": "AppControlPanel", + "AltNames": [ "sysdm.cpl" ], + "Command": "control sysdm.cpl" + }, + { + "Name": "TabletPcSettings", + "Areas": [ "AreaSystemAndSecurity" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.TabletPCSettings" + }, + { + "Name": "TextToSpeech", + "Areas": [ "AreaEaseOfAccess" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.TextToSpeech" + }, + { + "Name": "UserAccounts", + "Areas": [ "AreaUserAccounts" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.UserAccounts" + }, + { + "Name": "WelcomeCenter", + "Areas": [ "AreaSystemAndSecurity" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.WelcomeCenter" + }, + { + "Name": "WindowsAnytimeUpgrade", + "Areas": [ "AreaSystemAndSecurity" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.WindowsAnytimeUpgrade" + }, + { + "Name": "WindowsCardSpace", + "Areas": [ "AreaHardwareAndSound" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.CardSpace" + }, + { + "Name": "WindowsDefender", + "Areas": [ "AreaSystemAndSecurity" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.WindowsDefender" + }, + { + "Name": "WindowsFirewall", + "Areas": [ "AreaSystemAndSecurity" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.WindowsFirewall" + }, + { + "Name": "WindowsMobilityCenter", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppControlPanel", + "Command": "control /name Microsoft.MobilityCenter" + }, + { + "Name": "DisplayProperties", + "Areas": [ "AreaHardwareAndSound" ], + "Type": "AppControlPanel", + "AltNames": [ "desk.cpl" ], + "Command": "control Desk.cpl" + }, + { + "Name": "FindFast", + "Areas": [ "AreaSystemAndSecurity" ], + "Type": "AppControlPanel", + "AltNames": [ "findfast.cpl" ], + "Command": "control FindFast.cpl" + }, + { + "Name": "RegionalSettingsProperties", + "Areas": [ "AreaEaseOfAccess" ], + "Type": "AppControlPanel", + "AltNames": [ "intl.cpl" ], + "Command": "control Intl.cpl" + }, + { + "Name": "JoystickProperties", + "Areas": [ "AreaHardwareAndSound" ], + "Type": "AppControlPanel", + "AltNames": [ "joy.cpl" ], + "Command": "control Joy.cpl" + }, + { + "Name": "MouseFontsKeyboardAndPrintersProperties", + "Areas": [ "AreaHardwareAndSound" ], + "Type": "AppControlPanel", + "AltNames": [ "main.cpl" ], + "Command": "control Main.cpl" + }, + { + "Name": "MultimediaProperties", + "Areas": [ "AreaHardwareAndSound" ], + "Type": "AppControlPanel", + "AltNames": [ "mmsys.cpl" ], + "Command": "control Mmsys.cpl" + }, + { + "Name": "NetworkProperties", + "Areas": [ "AreaNetworkAndInternet" ], + "Type": "AppControlPanel", + "AltNames": [ "netcpl.cpl" ], + "Command": "control Netcpl.cpl" + }, + { + "Name": "PasswordProperties", + "Areas": [ "AreaUserAccounts" ], + "Type": "AppControlPanel", + "AltNames": [ "password.cpl" ], + "Command": "control Password.cpl" + }, + { + "Name": "SystemPropertiesAndAddNewHardwareWizard", + "Areas": [ "AreaHardwareAndSound" ], + "Type": "AppControlPanel", + "AltNames": [ "sysdm.cpl" ], + "Command": "control Sysdm.cpl" + }, + { + "Name": "DesktopThemes", + "Areas": [ "AreaAppearanceAndPersonalization" ], + "Type": "AppControlPanel", + "AltNames": [ "themes.cpl" ], + "Command": "control Themes.cpl" + }, + { + "Name": "MicrosoftMailPostOffice", + "Areas": [ "AreaPrograms" ], + "Type": "AppControlPanel", + "AltNames": [ "wgpocpl.cpl" ], + "Command": "control Wgpocpl.cpl" + }, + { + "Name": "ChangeUACSettings", + "Areas": [ "AreaSystemAndSecurity", "AreaSecurityAndMaintenance" ], + "Type": "AppControlPanel", + "AltNames": [ "UserAccountControlSettings.exe", "UserAccounts", "UAC" ], + "Command": "UserAccountControlSettings.exe", + "Note": "NoteEditingRequireAdminPrivileges" + }, + { + "Name": "EditSystemEnvironmentVariables", + "Areas": [ "AreaSystemAndSecurity", "AreaSystemPropertiesAdvanced" ], + "Type": "AppControlPanel", + "AltNames": [ "EditEnvironmentVariables", "SystemVariables", "EnvVars", "SystemEnvVars", "sysdm.cpl" ], + "Command": "SystemPropertiesAdvanced.exe", + "Note": "NoteEditingRequireAdminPrivileges" + }, + { + "Name": "EditUserEnvironmentVariables", + "Areas": [ "AreaSystemAndSecurity", "AreaSystemPropertiesAdvanced" ], + "Type": "AppControlPanel", + "AltNames": [ "UserEnvironmentVariables", "UserVariables", "EnvVars", "UserEnvVars", "sysdm.cpl" ], + "Command": "rundll32.exe sysdm.cpl,EditEnvironmentVariables" + } + ] +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/WindowsSettings.schema.json b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/WindowsSettings.schema.json index 2e7a281e1..a60e5c5ff 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/WindowsSettings.schema.json +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/WindowsSettings.schema.json @@ -1,65 +1,73 @@ { - "$schema": "http://json-schema.org/draft-04/schema", - "additionalProperties": false, - "required": [ "Settings" ], - "properties": { - "Settings": { - "description": "A list with all possible windows settings.", + "$schema": "http://json-schema.org/draft-04/schema", + "additionalProperties": false, + "required": [ "Settings" ], + "properties": { + "$schema": { + "description": "Path to the schema file.", + "type": "string" + }, + "Settings": { + "description": "A list with all possible windows settings.", + "type": "array", + "items": { + "additionalProperties": false, + "required": [ "Name", "Command", "Type" ], + "type": "object", + "properties": { + "Name": { + "description": "The name of this setting.", + "type": "string" + }, + "Areas": { + "description": "A list of areas of this setting", "type": "array", "items": { - "additionalProperties": false, - "required": [ "Name", "Command", "Type" ], - "type": "object", - "properties": { - "Name": { - "description": "The name of this setting.", - "type": "string" - }, - "Areas": { - "description": "A list of areas of this setting", - "type": "array", - "items": { - "description": "A area of this setting", - "type": "string", - "pattern": "^Area" - } - }, - "Type": { - "description": "The type of this setting.", - "type": "string", - "pattern": "^App" - }, - "AltNames": { - "description": "A list with alternative names for this setting", - "type": "array", - "items": { - "description": "A alternative name for this setting", - "type": "string" - } - }, - "Command": { - "description": "The command for this setting.", - "type": "string" - }, - "Note": { - "description": "A additional note for this setting.", - "type": "string", - "pattern": "^Note" - }, - "DeprecatedInBuild": { - "description": "The Windows build since this settings is not longer present.", - "type": "integer", - "minimum": 0, - "maximum": 4294967295 - }, - "IntroducedInBuild": { - "description": "The minimum need Windows build for this setting.", - "type": "integer", - "minimum": 0, - "maximum": 4294967295 - } - } + "description": "A area of this setting", + "type": "string", + "pattern": "^Area" } + }, + "Type": { + "description": "The type of this setting.", + "type": "string", + "pattern": "^App" + }, + "AltNames": { + "description": "A list with alternative names for this setting", + "type": "array", + "items": { + "description": "A alternative name for this setting", + "type": "string" + } + }, + "Command": { + "description": "The command for this setting.", + "type": "string" + }, + "Note": { + "description": "A additional note for this setting.", + "type": "string", + "pattern": "^Note" + }, + "DeprecatedInBuild": { + "description": "The Windows build since this settings is not longer present.", + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "IntroducedInBuild": { + "description": "The minimum need Windows build for this setting.", + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "ShowAsFirstResult": { + "description": "Use a higher score as normal for this setting to show it as one of the first results.", + "type": "boolean" + } } + } } + } } diff --git a/src/modules/launcher/PowerLauncher/Helper/EnvironmentHelper.cs b/src/modules/launcher/PowerLauncher/Helper/EnvironmentHelper.cs index e033538f7..484da6b40 100644 --- a/src/modules/launcher/PowerLauncher/Helper/EnvironmentHelper.cs +++ b/src/modules/launcher/PowerLauncher/Helper/EnvironmentHelper.cs @@ -5,88 +5,219 @@ using System; using System.Collections; using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; using System.Linq; using System.Security.Principal; -using Microsoft.Win32.SafeHandles; using Wox.Plugin.Logger; +using Stopwatch = Wox.Infrastructure.Stopwatch; namespace PowerLauncher.Helper { + /// + /// On Windows operating system the name of environment variables is case insensitive. This means if we have a user and machine variable with differences in their name casing (eg. test vs Test), the name casing from machine level is used and won't be overwritten by the user var. + /// Example for Window's behavior: test=ValueMachine (Machine level) + TEST=ValueUser (User level) => test=ValueUser (merged) + /// To get the same behavior we use "StringComparer.OrdinalIgnoreCase" as compare property for the HashSet and Dictionaries where we merge machine and user variable names. + /// public static class EnvironmentHelper { - private const string Username = "USERNAME"; - private const string ProcessorArchitecture = "PROCESSOR_ARCHITECTURE"; - private const string Path = "PATH"; + // The HashSet will contain the list of environment variables that will be skipped on update. + private const string PathVariable = "Path"; + private static readonly HashSet _protectedProcessVariables = new HashSet(StringComparer.OrdinalIgnoreCase); + /// + /// This method is called from to initialize a list of protected environment variables right after the PT Run process has been invoked. + /// Protected variables are environment variables that must not be changed on process level when updating the environment variables with changes on machine and/or user level. + /// We cache the relevant variable names in the private, static and readonly variable of this class. + /// + internal static void GetProtectedEnvironmentVariables() + { + IDictionary processVars; + var machineAndUserVars = new Dictionary(StringComparer.OrdinalIgnoreCase); + + Stopwatch.Normal("EnvironmentHelper.GetProtectedEnvironmentVariables - Duration cost", () => + { + // Adding some well known variables that must kept unchanged on process level. + // Changes of this variables may lead to incorrect values + _protectedProcessVariables.Add("USERNAME"); + _protectedProcessVariables.Add("PROCESSOR_ARCHITECTURE"); + + // Getting environment variables + processVars = GetEnvironmentVariablesWithErrorLog(EnvironmentVariableTarget.Process); + GetMergedMachineAndUserVariables(machineAndUserVars); + + // Adding names of variables that are different on process level or existing only on process level + foreach (DictionaryEntry pVar in processVars) + { + string pVarKey = (string)pVar.Key; + string pVarValue = (string)pVar.Value; + + if (machineAndUserVars.ContainsKey(pVarKey)) + { + if (machineAndUserVars[pVarKey] != pVarValue) + { + // Variable value for this process differs form merged machine/user value. + _protectedProcessVariables.Add(pVarKey); + } + } + else + { + // Variable exists only for this process + _protectedProcessVariables.Add(pVarKey); + } + } + }); + } + + /// + /// This method updates the environment of PT Run's process when called. It is called when we receive a special WindowMessage. + /// internal static void UpdateEnvironment() { - // Username and process architecture are set by the machine vars, this - // may lead to incorrect values so save off the current values to restore. - string originalUsername = Environment.GetEnvironmentVariable(Username, EnvironmentVariableTarget.Process); - string originalArch = Environment.GetEnvironmentVariable(ProcessorArchitecture, EnvironmentVariableTarget.Process); + Stopwatch.Normal("EnvironmentHelper.UpdateEnvironment - Duration cost", () => + { + // Caching existing process environment and getting updated environment variables + IDictionary oldProcessEnvironment = GetEnvironmentVariablesWithErrorLog(EnvironmentVariableTarget.Process); + var newEnvironment = new Dictionary(StringComparer.OrdinalIgnoreCase); + GetMergedMachineAndUserVariables(newEnvironment); - var environment = new Dictionary(); - MergeTargetEnvironmentVariables(environment, EnvironmentVariableTarget.Process); - MergeTargetEnvironmentVariables(environment, EnvironmentVariableTarget.Machine); + // Determine deleted variables and add them with a "string.Empty" value as marker to the dictionary + foreach (DictionaryEntry pVar in oldProcessEnvironment) + { + // We must compare case insensitive (see dictionary assignment) to avoid false positives when the variable name has changed (Example: "path" -> "Path") + if (!newEnvironment.ContainsKey((string)pVar.Key) & !_protectedProcessVariables.Contains((string)pVar.Key)) + { + newEnvironment.Add((string)pVar.Key, string.Empty); + } + } + // Remove unchanged variables from the dictionary + // Later we only like to recreate the changed ones + foreach (string varName in newEnvironment.Keys.ToList()) + { + // To be able to detect changed names correctly we have to compare case sensitive + if (oldProcessEnvironment.Contains(varName)) + { + if (oldProcessEnvironment[varName].Equals(newEnvironment[varName])) + { + newEnvironment.Remove(varName); + } + } + } + + // Update PT Run's process environment now + foreach (KeyValuePair kv in newEnvironment) + { + // Initialize variables for length of environment variable name and value. Using this variables prevent us from null value exceptions. + // => We added this because of the issue #13172 where a user reported System.ArgumentNullException from "Environment.SetEnvironmentVariable()". + int varNameLength = kv.Key == null ? 0 : kv.Key.Length; + int varValueLength = kv.Value == null ? 0 : kv.Value.Length; + + // The name of environment variables must not be null, empty or have a length of zero. + // But if the value of the environment variable is null or an empty string then the variable is explicit defined for deletion. => Here we don't need to check anything. + // => We added the if statement (next line) because of the issue #13172 where a user reported System.ArgumentNullException from "Environment.SetEnvironmentVariable()". + if (!string.IsNullOrEmpty(kv.Key) & varNameLength > 0) + { + try + { + // If the variable is not listed as protected/don't override on process level, then update it. (See method "GetProtectedEnvironmentVariables" of this class.) + if (!_protectedProcessVariables.Contains(kv.Key)) + { + // We have to delete the variables first that we can update their name if changed by the user. (Example: "path" => "Path") + // The machine and user variables that have been deleted by the user having an empty string as variable value. Because of this we check the values of the variables in our dictionary against "null" and "string.Empty". This check prevents us from invoking a (second) delete command. + // The dotnet method doesn't throw an exception if the variable which should be deleted doesn't exist. + Environment.SetEnvironmentVariable(kv.Key, null, EnvironmentVariableTarget.Process); + if (!string.IsNullOrEmpty(kv.Value)) + { + Environment.SetEnvironmentVariable(kv.Key, kv.Value, EnvironmentVariableTarget.Process); + } + } + else + { + // Don't log for the variable "USERNAME" if the variable's value is "System". (Then it is a false positive because per default the variable only exists on machine level with the value "System".) + if (!kv.Key.Equals("USERNAME", StringComparison.OrdinalIgnoreCase) & !kv.Value.Equals("System", StringComparison.Ordinal)) + { + Log.Warn($"Skipping update of the environment variable [{kv.Key}] for the PT Run process. This variable is listed as protected process variable and changing them can cause unexpected behavior. (The variable value has a length of [{varValueLength}].)", typeof(PowerLauncher.Helper.EnvironmentHelper)); + } + } + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + // The dotnet method "System.Environment.SetEnvironmentVariable" has it's own internal method to check the input parameters. Here we catch the exceptions that we don't check before updating the environment variable and log it to avoid crashes of PT Run. + Log.Exception($"Unhandled exception while updating the environment variable [{kv.Key}] for the PT Run process. (The variable value has a length of [{varValueLength}].)", ex, typeof(PowerLauncher.Helper.EnvironmentHelper)); + } + } + else + { + // Log the error when variable name is null, empty or has a length of zero. + Log.Error($"Failed to update the environment variable [{kv.Key}] for the PT Run process. Their name is null or empty. (The variable value has a length of [{varValueLength}].)", typeof(PowerLauncher.Helper.EnvironmentHelper)); + } + } + }); + } + + /// + /// This method returns a Dictionary with a merged set of machine and user environment variables. If we run as "system" only machine variables are returned. + /// + /// The dictionary that should be filled with the merged variables. + private static void GetMergedMachineAndUserVariables(Dictionary environment) + { + // Getting machine variables + IDictionary machineVars = GetEnvironmentVariablesWithErrorLog(EnvironmentVariableTarget.Machine); + foreach (DictionaryEntry mVar in machineVars) + { + environment[(string)mVar.Key] = (string)mVar.Value; + } + + // Getting user variables and merge it if (!IsRunningAsSystem()) { - MergeTargetEnvironmentVariables(environment, EnvironmentVariableTarget.User); - - // Special handling for PATH - merge Machine & User instead of override - var pathTargets = new[] { EnvironmentVariableTarget.Machine, EnvironmentVariableTarget.User }; - var paths = pathTargets - .Select(t => Environment.GetEnvironmentVariable(Path, t)) - .Where(e => e != null) - .SelectMany(e => e.Split(';', StringSplitOptions.RemoveEmptyEntries)) - .Distinct(); - - environment[Path] = string.Join(';', paths); - } - - environment[Username] = originalUsername; - environment[ProcessorArchitecture] = originalArch; - - foreach (KeyValuePair kv in environment) - { - // Initialize variables for length of environment variable name and value. Using this variables prevent us from null value exceptions. - int varNameLength = kv.Key == null ? 0 : kv.Key.Length; - int varValueLength = kv.Value == null ? 0 : kv.Value.Length; - - // The name of environment variables must not be null, empty or have a length of zero. - // But if the value of the environment variable is null or empty then the variable is explicit defined for deletion. => Here we don't need to check anything. - if (!string.IsNullOrEmpty(kv.Key) & varNameLength > 0) + IDictionary userVars = GetEnvironmentVariablesWithErrorLog(EnvironmentVariableTarget.User); + foreach (DictionaryEntry uVar in userVars) { - try + string uVarKey = (string)uVar.Key; + string uVarValue = (string)uVar.Value; + + // The variable name of the path variable can be upper case, lower case ore mixed case. So we have to compare case insensitive. + if (!uVarKey.Equals(PathVariable, StringComparison.OrdinalIgnoreCase)) { - Environment.SetEnvironmentVariable(kv.Key, kv.Value, EnvironmentVariableTarget.Process); + environment[uVarKey] = uVarValue; } - catch (ArgumentException ex) + else { - // The dotnet method has it's own internal method to check the input parameters. Here we catch the exceptions that we don't check before updating the environment variable and log it to avoid crashes of PT Run. - Log.Exception($"Unexpected exception while updating the environment variable [{kv.Key}] for the PT Run process. (The variable value has a length of [{varValueLength}].)", ex, typeof(PowerLauncher.Helper.EnvironmentHelper)); + // When we merging the PATH variables we can't simply overwrite machine layer's value. The path variable must be joined by appending the user value to the machine value. + // This is the official behavior and checked by trying it out on the physical machine. + string newPathValue = environment[uVarKey].EndsWith(';') ? environment[uVarKey] + uVarValue : environment[uVarKey] + ';' + uVarValue; + environment[uVarKey] = newPathValue; } } - else - { - // Log the error when variable value is null, empty or has a length of zero. - Log.Error($"Failed to update the environment variable [{kv.Key}] for the PT Run process. Their name is null or empty. (The variable value has a length of [{varValueLength}].)", typeof(PowerLauncher.Helper.EnvironmentHelper)); - } } } - private static void MergeTargetEnvironmentVariables( - Dictionary environment, EnvironmentVariableTarget target) + /// + /// Returns the variables for the specified target. Errors that occurs will be catched and logged. + /// + /// The target variable source of the type + /// A dictionary with the variable or an empty dictionary on errors. + private static IDictionary GetEnvironmentVariablesWithErrorLog(EnvironmentVariableTarget target) { - IDictionary variables = Environment.GetEnvironmentVariables(target); - foreach (DictionaryEntry entry in variables) + try { - environment[(string)entry.Key] = (string)entry.Value; + return Environment.GetEnvironmentVariables(target); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + Log.Exception($"Unhandled exception while getting the environment variables for target '{target}'.", ex, typeof(PowerLauncher.Helper.EnvironmentHelper)); + return new Hashtable(); } } + /// + /// Checks wether this process is running under the system user/account. + /// + /// A boolean value that indicates wether this process is running under system account (true) or not (false). private static bool IsRunningAsSystem() { using (var identity = WindowsIdentity.GetCurrent()) diff --git a/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs b/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs index 2d3f4cd1d..9f479f799 100644 --- a/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs +++ b/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs @@ -109,7 +109,7 @@ namespace PowerLauncher string changeType = Marshal.PtrToStringUni(lparam); if (changeType == EnvironmentChangeType) { - Log.Info("Reload environment", typeof(EnvironmentHelper)); + Log.Info("Reload environment: Updating environment variables for PT Run's process", typeof(EnvironmentHelper)); EnvironmentHelper.UpdateEnvironment(); handled = true; } @@ -125,6 +125,9 @@ namespace PowerLauncher private void OnSourceInitialized(object sender, EventArgs e) { + // Initialize protected environment variables before register the WindowMessage + EnvironmentHelper.GetProtectedEnvironmentVariables(); + _hwndSource = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle); _hwndSource.AddHook(ProcessWindowMessages); diff --git a/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs b/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs index 409144a7b..f89518a9f 100644 --- a/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs +++ b/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs @@ -574,11 +574,12 @@ namespace PowerLauncher.ViewModel Results.Sort(); Results.SelectedItem = Results.Results.FirstOrDefault(); } + + currentCancellationToken.ThrowIfCancellationRequested(); + UpdateResultsListViewAfterQuery(queryText); } - currentCancellationToken.ThrowIfCancellationRequested(); - - UpdateResultsListViewAfterQuery(queryText); + bool noInitialResults = numResults == 0; // Run the slower query of the DelayedExecution plugins currentCancellationToken.ThrowIfCancellationRequested(); @@ -611,10 +612,10 @@ namespace PowerLauncher.ViewModel numResults = Results.Results.Count; Results.Sort(); } - } - currentCancellationToken.ThrowIfCancellationRequested(); - UpdateResultsListViewAfterQuery(queryText, true); + currentCancellationToken.ThrowIfCancellationRequested(); + UpdateResultsListViewAfterQuery(queryText, noInitialResults, true); + } } } catch (OperationCanceledException) @@ -655,7 +656,7 @@ namespace PowerLauncher.ViewModel } } - private void UpdateResultsListViewAfterQuery(string queryText, bool isDelayedInvoke = false) + private void UpdateResultsListViewAfterQuery(string queryText, bool noInitialResults = false, bool isDelayedInvoke = false) { Application.Current.Dispatcher.BeginInvoke(new Action(() => { @@ -668,9 +669,13 @@ namespace PowerLauncher.ViewModel if (Results.Results.Count > 0) { Results.Visibility = Visibility.Visible; - if (!isDelayedInvoke) + if (!isDelayedInvoke || noInitialResults) { Results.SelectedIndex = 0; + if (noInitialResults) + { + Results.SelectedItem = Results.Results.FirstOrDefault(); + } } } else diff --git a/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.cpp b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.cpp index 28c85ab5a..f47cad755 100644 --- a/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.cpp +++ b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.cpp @@ -5,13 +5,22 @@ #include "PowerRenameUIHost.h" #include #include + +#include #include #include #include + +#include +#include +#include #include #define MAX_LOADSTRING 100 +// Non-localizable +const std::wstring moduleName = L"PowerRename"; +const std::wstring internalPath = L""; const wchar_t c_WindowClass[] = L"PowerRename"; HINSTANCE g_hostHInst; @@ -19,6 +28,8 @@ int AppWindow::Show(HINSTANCE hInstance, std::vector files) { auto window = AppWindow(hInstance, files); window.CreateAndShowWindow(); + Logger::debug(L"PowerRename UI created. Starting the message loop."); + return window.MessageLoop(window.m_accelerators.get()); } @@ -40,38 +51,54 @@ LRESULT AppWindow::MessageHandler(UINT message, WPARAM wParam, LPARAM lParam) no AppWindow::AppWindow(HINSTANCE hInstance, std::vector files) noexcept : m_instance{ hInstance }, m_managerEvents{ this } { - HRESULT hr = CPowerRenameManager::s_CreateInstance(&m_prManager); - // Create the factory for our items - CComPtr prItemFactory; - hr = CPowerRenameItem::s_CreateInstance(nullptr, IID_PPV_ARGS(&prItemFactory)); - hr = m_prManager->PutRenameItemFactory(prItemFactory); - hr = m_prManager->Advise(&m_managerEvents, &m_cookie); - - if (SUCCEEDED(hr)) + if (SUCCEEDED(CPowerRenameManager::s_CreateInstance(&m_prManager))) { - CComPtr shellItemArray; - // To test PowerRenameUIHost uncomment this line and update the path to - // your local (absolute or relative) path which you want to see in PowerRename - //files.push_back(L""); - - if (!files.empty()) + // Create the factory for our items + CComPtr prItemFactory; + if (SUCCEEDED(CPowerRenameItem::s_CreateInstance(nullptr, IID_PPV_ARGS(&prItemFactory)))) { - hr = CreateShellItemArrayFromPaths(files, &shellItemArray); - if (SUCCEEDED(hr)) + if(SUCCEEDED(m_prManager->PutRenameItemFactory(prItemFactory))) { - CComPtr enumShellItems; - hr = shellItemArray->EnumItems(&enumShellItems); - if (SUCCEEDED(hr)) + if (SUCCEEDED(m_prManager->Advise(&m_managerEvents, &m_cookie))) { - EnumerateShellItems(enumShellItems); + CComPtr shellItemArray; + // To test PowerRenameUIHost uncomment this line and update the path to + // your local (absolute or relative) path which you want to see in PowerRename + //files.push_back(L""); + + if (!files.empty()) + { + if (SUCCEEDED(CreateShellItemArrayFromPaths(files, &shellItemArray))) + { + CComPtr enumShellItems; + if (SUCCEEDED(shellItemArray->EnumItems(&enumShellItems))) + { + EnumerateShellItems(enumShellItems); + } + } + } + else + { + Logger::warn(L"No items selected to be renamed."); + } } } } + else + { + Logger::error(L"Error creating PowerRenameItemFactory"); + } + } + else + { + Logger::error(L"Error creating PowerRenameManager"); } } void AppWindow::CreateAndShowWindow() { + _TRACER_; + m_accelerators.reset(LoadAcceleratorsW(m_instance, MAKEINTRESOURCE(IDC_POWERRENAMEUIHOST))); WNDCLASSEXW wcex = { sizeof(wcex) }; @@ -88,7 +115,8 @@ void AppWindow::CreateAndShowWindow() wchar_t title[64]; LoadStringW(m_instance, IDS_APP_TITLE, title, ARRAYSIZE(title)); - m_window = CreateWindowW(c_WindowClass, title, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, m_instance, this); + // hardcoded width and height (1200 x 600) - with WinUI 3, it should auto-scale to the content + m_window = CreateWindowW(c_WindowClass, title, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 1200, 600, nullptr, nullptr, m_instance, this); THROW_LAST_ERROR_IF(!m_window); ShowWindow(m_window, SW_SHOWNORMAL); @@ -98,12 +126,22 @@ void AppWindow::CreateAndShowWindow() bool AppWindow::OnCreate(HWND, LPCREATESTRUCT) noexcept { + _TRACER_; + m_mainUserControl = winrt::PowerRenameUILib::MainWindow(); m_xamlIsland = CreateDesktopWindowsXamlSource(WS_TABSTOP, m_mainUserControl); - PopulateExplorerItems(); - SetHandlers(); - ReadSettings(); + try + { + PopulateExplorerItems(); + UpdateCounts(); + SetHandlers(); + ReadSettings(); + } + catch (std::exception e) + { + Logger::error("Exception thrown during explorer items population: {}", std::string{ e.what() }); + } m_mainUserControl.UIUpdatesItem().ButtonRenameEnabled(false); InitAutoComplete(); @@ -142,11 +180,15 @@ void AppWindow::OnCommand(HWND, int id, HWND hwndControl, UINT codeNotify) noexc void AppWindow::OnDestroy(HWND hwnd) noexcept { + _TRACER_; + base_type::OnDestroy(hwnd); } void AppWindow::OnResize(HWND, UINT state, int cx, int cy) noexcept { + _TRACER_; + SetWindowPos(m_xamlIsland, NULL, 0, 0, cx, cy, SWP_SHOWWINDOW); } @@ -154,6 +196,8 @@ HRESULT AppWindow::CreateShellItemArrayFromPaths( std::vector files, IShellItemArray** shellItemArray) { + _TRACER_; + *shellItemArray = nullptr; PIDLIST_ABSOLUTE* itemList = nullptr; itemList = new (std::nothrow) PIDLIST_ABSOLUTE[files.size()]; @@ -173,14 +217,21 @@ HRESULT AppWindow::CreateShellItemArrayFromPaths( if (SUCCEEDED(hr) && itemsCnt > 0) { hr = SHCreateShellItemArrayFromIDLists(itemsCnt, const_cast(itemList), shellItemArray); - - for (UINT i = 0; i < itemsCnt; i++) + if (SUCCEEDED(hr)) { - CoTaskMemFree(itemList[i]); + for (UINT i = 0; i < itemsCnt; i++) + { + CoTaskMemFree(itemList[i]); + } + } + else + { + Logger::error(L"Creating ShellItemArray from path list failed."); } } else { + Logger::error(L"Parsing path list display names failed."); hr = E_FAIL; } @@ -190,13 +241,11 @@ HRESULT AppWindow::CreateShellItemArrayFromPaths( void AppWindow::PopulateExplorerItems() { + _TRACER_; + UINT count = 0; m_prManager->GetVisibleItemCount(&count); - - UINT currDepth = 0; - std::stack parents{}; - UINT prevId = 0; - parents.push(0); + Logger::debug(L"Number of visible items: {}", count); for (UINT i = 0; i < count; ++i) { @@ -221,29 +270,16 @@ void AppWindow::PopulateExplorerItems() bool isSubFolderContent = false; winrt::check_hresult(renameItem->GetIsFolder(&isFolder)); - if (depth > currDepth) - { - parents.push(prevId); - currDepth = depth; - } - else - { - while (currDepth > depth) - { - parents.pop(); - currDepth--; - } - currDepth = depth; - } m_mainUserControl.AddExplorerItem( - id, originalName, newName == nullptr ? hstring{} : hstring{ newName }, isFolder ? 0 : 1, parents.top(), selected); - prevId = id; + id, originalName, newName == nullptr ? hstring{} : hstring{ newName }, isFolder ? 0 : 1, depth, selected); } } } HRESULT AppWindow::InitAutoComplete() { + _TRACER_; + HRESULT hr = S_OK; if (CSettingsInstance().GetMRUEnabled()) { @@ -280,6 +316,8 @@ HRESULT AppWindow::InitAutoComplete() HRESULT AppWindow::EnumerateShellItems(_In_ IEnumShellItems* enumShellItems) { + _TRACER_; + HRESULT hr = S_OK; // Enumerate the data object and populate the manager if (m_prManager) @@ -302,6 +340,9 @@ HRESULT AppWindow::EnumerateShellItems(_In_ IEnumShellItems* enumShellItems) void AppWindow::SearchReplaceChanged(bool forceRenaming) { + _TRACER_; + + Logger::debug(L"Forced renaming - {}", forceRenaming); // Pass updated search and replace terms to the IPowerRenameRegEx handler CComPtr prRegEx; if (m_prManager && SUCCEEDED(m_prManager->GetRenameRegEx(&prRegEx))) @@ -358,6 +399,8 @@ void AppWindow::ValidateFlags(PowerRenameFlags flag) void AppWindow::UpdateFlag(PowerRenameFlags flag, UpdateFlagCommand command) { + _TRACER_; + DWORD flags{}; m_prManager->GetFlags(&flags); @@ -370,6 +413,8 @@ void AppWindow::UpdateFlag(PowerRenameFlags flag, UpdateFlagCommand command) flags &= ~flag; } + Logger::debug(L"Flag {} " + std::wstring{ command == UpdateFlagCommand::Set ? L"set" : L"reset" }, flag); + // Ensure we update flags if (m_prManager) { @@ -379,6 +424,8 @@ void AppWindow::UpdateFlag(PowerRenameFlags flag, UpdateFlagCommand command) void AppWindow::SetHandlers() { + _TRACER_; + m_mainUserControl.UIUpdatesItem().PropertyChanged([&](winrt::Windows::Foundation::IInspectable const& sender, Data::PropertyChangedEventArgs const& e) { std::wstring property{ e.PropertyName() }; if (property == L"ShowAll") @@ -537,14 +584,21 @@ void AppWindow::SetHandlers() void AppWindow::ToggleItem(int32_t id, bool checked) { + _TRACER_; + Logger::debug(L"Toggling item with id = {}", id); CComPtr spItem; - m_prManager->GetItemById(id, &spItem); - spItem->PutSelected(checked); + + if (SUCCEEDED(m_prManager->GetItemById(id, &spItem))) + { + spItem->PutSelected(checked); + } UpdateCounts(); } void AppWindow::ToggleAll() { + _TRACER_; + UINT itemCount = 0; m_prManager->GetItemCount(&itemCount); bool selected = m_mainUserControl.CheckBoxSelectAll().IsChecked().GetBoolean(); @@ -561,12 +615,17 @@ void AppWindow::ToggleAll() void AppWindow::SwitchView() { + _TRACER_; + m_prManager->SwitchFilter(0); PopulateExplorerItems(); + UpdateCounts(); } void AppWindow::Rename(bool closeWindow) { + _TRACER_; + if (m_prManager) { m_prManager->Rename(m_window, closeWindow); @@ -580,10 +639,15 @@ void AppWindow::Rename(bool closeWindow) HRESULT AppWindow::ReadSettings() { + _TRACER_; + + bool persistState{ CSettingsInstance().GetPersistState() }; + Logger::debug(L"ReadSettings with persistState = {}", persistState); + // Check if we should read flags from settings // or the defaults from the manager. DWORD flags = 0; - if (CSettingsInstance().GetPersistState()) + if (persistState) { flags = CSettingsInstance().GetFlags(); @@ -603,6 +667,8 @@ HRESULT AppWindow::ReadSettings() HRESULT AppWindow::WriteSettings() { + _TRACER_; + // Check if we should store our settings if (CSettingsInstance().GetPersistState()) { @@ -767,11 +833,12 @@ void AppWindow::UpdateCounts() m_selectedCount = selectedCount; m_renamingCount = renamingCount; - // Update counts UI elements if/when added - // Update Rename button state m_mainUserControl.UIUpdatesItem().ButtonRenameEnabled(renamingCount > 0); } + + m_mainUserControl.UIUpdatesItem().OriginalCount(std::to_wstring(m_mainUserControl.ExplorerItems().Size())); + m_mainUserControl.UIUpdatesItem().RenamedCount(std::to_wstring(m_renamingCount)); } HRESULT AppWindow::OnItemAdded(_In_ IPowerRenameItem* renameItem) @@ -794,7 +861,6 @@ HRESULT AppWindow::OnUpdate(_In_ IPowerRenameItem* renameItem) } } - UpdateCounts(); return S_OK; } @@ -834,6 +900,8 @@ HRESULT AppWindow::OnRegExCanceled(_In_ DWORD threadId) HRESULT AppWindow::OnRegExCompleted(_In_ DWORD threadId) { + _TRACER_; + if (m_flagValidationInProgress) { m_flagValidationInProgress = false; @@ -849,6 +917,7 @@ HRESULT AppWindow::OnRegExCompleted(_In_ DWORD threadId) } } + UpdateCounts(); return S_OK; } @@ -859,6 +928,9 @@ HRESULT AppWindow::OnRenameStarted() HRESULT AppWindow::OnRenameCompleted(bool closeUIWindowAfterRenaming) { + _TRACER_; + + Logger::debug(L"Renaming completed. Close UI window - {}", closeUIWindowAfterRenaming); if (closeUIWindowAfterRenaming) { // Close the window @@ -877,11 +949,17 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { + LoggerHelpers::init_logger(moduleName, internalPath, LogSettings::powerRenameLoggerName); + #define BUFSIZE 4096 * 4 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); if (hStdin == INVALID_HANDLE_VALUE) + { + Logger::error(L"Invalid input handle."); ExitProcess(1); + } + BOOL bSuccess; WCHAR chBuf[BUFSIZE]; DWORD dwRead; @@ -908,10 +986,19 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance, break; } - g_hostHInst = hInstance; - winrt::init_apartment(winrt::apartment_type::single_threaded); + Logger::debug(L"Starting PowerRename with {} files selected", files.size()); - winrt::PowerRenameUILib::App app; - const auto result = AppWindow::Show(hInstance, files); - app.Close(); + g_hostHInst = hInstance; + try + { + winrt::init_apartment(winrt::apartment_type::single_threaded); + + winrt::PowerRenameUILib::App app; + const auto result = AppWindow::Show(hInstance, files); + app.Close(); + } + catch (std::exception e) + { + Logger::error("Exception thrown during PowerRename UI initialization: {}", std::string{ e.what() }); + } } diff --git a/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.rc b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.rc index c2ed5aca3..aea555825 100644 --- a/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.rc +++ b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.rc @@ -1,6 +1,7 @@ //Microsoft Visual C++ generated resource script. // #include "resource.h" +#include "../../../common/version/version.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// @@ -30,70 +31,6 @@ IDI_POWERRENAMEUIHOST ICON "PowerRenameUIHost.ico" IDI_SMALL ICON "small.ico" -///////////////////////////////////////////////////////////////////////////// -// -// Menu -// - -IDC_POWERRENAMEUIHOST MENU -BEGIN - POPUP "&File" - BEGIN - MENUITEM "E&xit", IDM_EXIT - END - POPUP "&Help" - BEGIN - MENUITEM "&About ...", IDM_ABOUT - END -END - - -///////////////////////////////////////////////////////////////////////////// -// -// Accelerator -// - -IDC_POWERRENAMEUIHOST ACCELERATORS -BEGIN - "?", IDM_ABOUT, ASCII, ALT - "/", IDM_ABOUT, ASCII, ALT -END - - -///////////////////////////////////////////////////////////////////////////// -// -// Dialog -// - -IDD_ABOUTBOX DIALOGEX 0, 0, 170, 62 -STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "About PowerRenameUIHost" -FONT 8, "MS Shell Dlg" -BEGIN - ICON IDR_MAINFRAME,IDC_STATIC,14,14,21,20 - LTEXT "PowerRenameUIHost, Version 1.0",IDC_STATIC,42,14,114,8,SS_NOPREFIX - LTEXT "Copyright (c) 2021",IDC_STATIC,42,26,114,8 - DEFPUSHBUTTON "OK",IDOK,113,41,50,14,WS_GROUP -END - -///////////////////////////////////////////////////////////////////////////// -// -// DESIGNINFO -// - -#ifdef APSTUDIO_INVOKED -GUIDELINES DESIGNINFO -BEGIN - IDD_ABOUTBOX, DIALOG - BEGIN - LEFTMARGIN, 7 - RIGHTMARGIN, 163 - TOPMARGIN, 7 - BOTTOMMARGIN, 55 - END -END -#endif // APSTUDIO_INVOKED - #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // @@ -137,14 +74,41 @@ #endif ///////////////////////////////////////////////////////////////////////////// - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////// // -// Generated from the TEXTINCLUDE resource. +// Version // -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED +VS_VERSION_INFO VERSIONINFO + FILEVERSION FILE_VERSION + PRODUCTVERSION PRODUCT_VERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", COMPANY_NAME + VALUE "FileDescription", "PowerToys PowerRename" + VALUE "FileVersion", FILE_VERSION_STRING + VALUE "InternalName", "PowerRename" + VALUE "LegalCopyright", COPYRIGHT_NOTE + VALUE "OriginalFilename", "PowerRename.exe" + VALUE "ProductName", PRODUCT_NAME + VALUE "ProductVersion", PRODUCT_VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END \ No newline at end of file diff --git a/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.vcxproj b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.vcxproj index 483078fe1..4d911a29c 100644 --- a/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.vcxproj +++ b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.vcxproj @@ -38,8 +38,10 @@ false - - + + + + @@ -126,6 +128,7 @@ + @@ -158,6 +161,9 @@ + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + {51920f1f-c28c-4adf-8660-4238766796c2} diff --git a/src/modules/powerrename/PowerRenameUILib/App.xaml b/src/modules/powerrename/PowerRenameUILib/App.xaml index 7795f1b7b..a869c4477 100644 --- a/src/modules/powerrename/PowerRenameUILib/App.xaml +++ b/src/modules/powerrename/PowerRenameUILib/App.xaml @@ -625,6 +625,7 @@ + diff --git a/src/modules/powerrename/PowerRenameUILib/ExplorerItem.cpp b/src/modules/powerrename/PowerRenameUILib/ExplorerItem.cpp index 8897dc903..2e6b1d7e2 100644 --- a/src/modules/powerrename/PowerRenameUILib/ExplorerItem.cpp +++ b/src/modules/powerrename/PowerRenameUILib/ExplorerItem.cpp @@ -2,15 +2,17 @@ #include "ExplorerItem.h" #include "ExplorerItem.g.cpp" +namespace { + const wchar_t fileImagePath[] = L"ms-appx:///Assets/file.png"; + const wchar_t folderImagePath[] = L"ms-appx:///Assets/folder.png"; +} + namespace winrt::PowerRenameUILib::implementation { - ExplorerItem::ExplorerItem(int32_t id, hstring const& original, hstring const& renamed, int32_t type, bool checked) : - m_id{ id }, m_idStr{ std::to_wstring(id) }, m_original{ original }, m_renamed{ renamed }, m_type{ type }, m_checked{ checked } + ExplorerItem::ExplorerItem(int32_t id, hstring const& original, hstring const& renamed, int32_t type, uint32_t depth, bool checked) : + m_id{ id }, m_idStr{ std::to_wstring(id) }, m_original{ original }, m_renamed{ renamed }, m_type{ type }, m_depth{ depth }, m_checked{ checked } { - if (m_type == static_cast(ExplorerItemType::Folder)) - { - m_children = winrt::single_threaded_observable_vector(); - } + m_imagePath = (m_type == static_cast(ExplorerItemType::Folder)) ? folderImagePath : fileImagePath; } int32_t ExplorerItem::Id() @@ -51,6 +53,15 @@ namespace winrt::PowerRenameUILib::implementation } } + double ExplorerItem::Indentation() { + return static_cast(m_depth) * 12; + } + + hstring ExplorerItem::ImagePath() + { + return m_imagePath; + } + int32_t ExplorerItem::Type() { return m_type; @@ -79,20 +90,6 @@ namespace winrt::PowerRenameUILib::implementation } } - winrt::Windows::Foundation::Collections::IObservableVector ExplorerItem::Children() - { - return m_children; - } - - void ExplorerItem::Children(Windows::Foundation::Collections::IObservableVector const& value) - { - if (m_children != value) - { - m_children = value; - m_propertyChanged(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Children" }); - } - } - winrt::event_token ExplorerItem::PropertyChanged(winrt::Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler) { return m_propertyChanged.add(handler); diff --git a/src/modules/powerrename/PowerRenameUILib/ExplorerItem.h b/src/modules/powerrename/PowerRenameUILib/ExplorerItem.h index f1d97d80e..b16f0c642 100644 --- a/src/modules/powerrename/PowerRenameUILib/ExplorerItem.h +++ b/src/modules/powerrename/PowerRenameUILib/ExplorerItem.h @@ -11,15 +11,17 @@ namespace winrt::PowerRenameUILib::implementation File = 1 }; - ExplorerItem() = delete; + ExplorerItem() = default; - ExplorerItem(int32_t id, hstring const& original, hstring const& renamed, int32_t type, bool checked); + ExplorerItem(int32_t id, hstring const& original, hstring const& renamed, int32_t type, uint32_t depth, bool checked); int32_t Id(); hstring IdStr(); hstring Original(); void Original(hstring const& value); hstring Renamed(); void Renamed(hstring const& value); + double Indentation(); + hstring ImagePath(); int32_t Type(); void Type(int32_t value); bool Checked(); @@ -34,6 +36,8 @@ namespace winrt::PowerRenameUILib::implementation hstring m_idStr; winrt::hstring m_original; winrt::hstring m_renamed; + uint32_t m_depth; + hstring m_imagePath; winrt::Windows::Foundation::Collections::IObservableVector m_children; int32_t m_type; bool m_checked; diff --git a/src/modules/powerrename/PowerRenameUILib/ExplorerItem.idl b/src/modules/powerrename/PowerRenameUILib/ExplorerItem.idl index c628b2af6..c83dbf7ad 100644 --- a/src/modules/powerrename/PowerRenameUILib/ExplorerItem.idl +++ b/src/modules/powerrename/PowerRenameUILib/ExplorerItem.idl @@ -2,13 +2,15 @@ namespace PowerRenameUILib { runtimeclass ExplorerItem : Windows.UI.Xaml.Data.INotifyPropertyChanged { - ExplorerItem(Int32 id, String original, String renamed, Int32 type, Boolean checked); + ExplorerItem(); + ExplorerItem(Int32 id, String original, String renamed, Int32 type, UInt32 depth, Boolean checked); Int32 Id { get; }; String IdStr { get; }; String Original; String Renamed; + Double Indentation { get; }; + String ImagePath { get; }; Int32 Type; Boolean Checked; - Windows.Foundation.Collections.IObservableVector Children; } } diff --git a/src/modules/powerrename/PowerRenameUILib/ExplorerItemTemplateSelector.cpp b/src/modules/powerrename/PowerRenameUILib/ExplorerItemTemplateSelector.cpp deleted file mode 100644 index 500c7eb3d..000000000 --- a/src/modules/powerrename/PowerRenameUILib/ExplorerItemTemplateSelector.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "pch.h" -#include "ExplorerItemTemplateSelector.h" -#include "ExplorerItemTemplateSelector.g.cpp" - -namespace winrt::PowerRenameUILib::implementation -{ - Windows::UI::Xaml::DataTemplate ExplorerItemTemplateSelector::SelectTemplateCore(IInspectable const& item) - { - ExplorerItem explorerItem = (ExplorerItem&)item; - return explorerItem.Type() == 0 ? m_folderTemplate : m_fileTemplate; - } - - Windows::UI::Xaml::DataTemplate ExplorerItemTemplateSelector::SelectTemplateCore(IInspectable const&, Windows::UI::Xaml::DependencyObject const&) - { - return Windows::UI::Xaml::DataTemplate(); - } - - winrt::Windows::UI::Xaml::DataTemplate ExplorerItemTemplateSelector::FolderTemplate() - { - return m_folderTemplate; - } - - void ExplorerItemTemplateSelector::FolderTemplate(winrt::Windows::UI::Xaml::DataTemplate const& value) - { - m_folderTemplate = value; - } - - winrt::Windows::UI::Xaml::DataTemplate ExplorerItemTemplateSelector::FileTemplate() - { - return m_fileTemplate; - } - - void ExplorerItemTemplateSelector::FileTemplate(winrt::Windows::UI::Xaml::DataTemplate const& value) - { - m_fileTemplate = value; - } -} diff --git a/src/modules/powerrename/PowerRenameUILib/ExplorerItemTemplateSelector.h b/src/modules/powerrename/PowerRenameUILib/ExplorerItemTemplateSelector.h deleted file mode 100644 index 35c46638a..000000000 --- a/src/modules/powerrename/PowerRenameUILib/ExplorerItemTemplateSelector.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#include "ExplorerItemTemplateSelector.g.h" - -namespace winrt::PowerRenameUILib::implementation -{ - struct ExplorerItemTemplateSelector : ExplorerItemTemplateSelectorT - { - ExplorerItemTemplateSelector() = default; - - Windows::UI::Xaml::DataTemplate SelectTemplateCore(IInspectable const&); - Windows::UI::Xaml::DataTemplate SelectTemplateCore(IInspectable const&, Windows::UI::Xaml::DependencyObject const&); - - winrt::Windows::UI::Xaml::DataTemplate FolderTemplate(); - void FolderTemplate(winrt::Windows::UI::Xaml::DataTemplate const& value); - winrt::Windows::UI::Xaml::DataTemplate FileTemplate(); - void FileTemplate(winrt::Windows::UI::Xaml::DataTemplate const& value); - - private: - Windows::UI::Xaml::DataTemplate m_folderTemplate{ nullptr }; - Windows::UI::Xaml::DataTemplate m_fileTemplate{ nullptr }; - }; -} -namespace winrt::PowerRenameUILib::factory_implementation -{ - struct ExplorerItemTemplateSelector : ExplorerItemTemplateSelectorT - { - }; -} diff --git a/src/modules/powerrename/PowerRenameUILib/ExplorerItemTemplateSelector.idl b/src/modules/powerrename/PowerRenameUILib/ExplorerItemTemplateSelector.idl deleted file mode 100644 index f31cf9429..000000000 --- a/src/modules/powerrename/PowerRenameUILib/ExplorerItemTemplateSelector.idl +++ /dev/null @@ -1,11 +0,0 @@ -namespace PowerRenameUILib -{ - [bindable] - [default_interface] runtimeclass ExplorerItemTemplateSelector : Windows.UI.Xaml.Controls.DataTemplateSelector - { - ExplorerItemTemplateSelector(); - - Windows.UI.Xaml.DataTemplate FolderTemplate; - Windows.UI.Xaml.DataTemplate FileTemplate; - } -} diff --git a/src/modules/powerrename/PowerRenameUILib/MainWindow.cpp b/src/modules/powerrename/PowerRenameUILib/MainWindow.cpp index d6c82b9fd..8ca0d2e6a 100644 --- a/src/modules/powerrename/PowerRenameUILib/MainWindow.cpp +++ b/src/modules/powerrename/PowerRenameUILib/MainWindow.cpp @@ -163,18 +163,11 @@ namespace winrt::PowerRenameUILib::implementation return m_uiUpdatesItem; } - void MainWindow::AddExplorerItem(int32_t id, hstring const& original, hstring const& renamed, int32_t type, int32_t parentId, bool checked) + void MainWindow::AddExplorerItem(int32_t id, hstring const& original, hstring const& renamed, int32_t type, uint32_t depth, bool checked) { - auto newItem = winrt::make(id, original, renamed, type, checked); - if (parentId == 0) - { - m_explorerItems.Append(newItem); - } - else - { - auto parent = FindById(parentId); - parent.Children().Append(newItem); - } + auto newItem = winrt::make(id, original, renamed, type, depth, checked); + m_explorerItems.Append(newItem); + m_explorerItemsMap[id] = newItem; } void MainWindow::UpdateExplorerItem(int32_t id, hstring const& newName) @@ -208,43 +201,12 @@ namespace winrt::PowerRenameUILib::implementation PowerRenameUILib::ExplorerItem MainWindow::FindById(int32_t id) { - auto fakeRoot = winrt::make(0, L"Fake", L"", 0, false); - fakeRoot.Children(m_explorerItems); - return FindById(fakeRoot, id); + return m_explorerItemsMap.contains(id) ? m_explorerItemsMap[id] : NULL; } - PowerRenameUILib::ExplorerItem MainWindow::FindById(PowerRenameUILib::ExplorerItem& root, int32_t id) + void MainWindow::ToggleAll(bool checked) { - if (root.Id() == id) - return root; - - if (root.Type() == static_cast(ExplorerItem::ExplorerItemType::Folder)) - { - for (auto c : root.Children()) - { - auto result = FindById(c, id); - if (result != NULL) - return result; - } - } - - return NULL; - } - - void MainWindow::ToggleAll(PowerRenameUILib::ExplorerItem node, bool checked) - { - if (node == NULL) - return; - - node.Checked(checked); - - if (node.Type() == static_cast(ExplorerItem::ExplorerItemType::Folder)) - { - for (auto c : node.Children()) - { - ToggleAll(c, checked); - } - } + std::for_each(m_explorerItems.begin(), m_explorerItems.end(), [checked](auto item) { item.Checked(checked); }); } void MainWindow::Checked_ids(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const&) @@ -252,7 +214,7 @@ namespace winrt::PowerRenameUILib::implementation auto checkbox = sender.as(); auto id = std::stoi(std::wstring{ checkbox.Name() }); auto item = FindById(id); - if (checkbox.IsChecked().GetBoolean() != item.Checked()) + if (item != NULL && checkbox.IsChecked().GetBoolean() != item.Checked()) { m_uiUpdatesItem.Checked(checkbox.IsChecked().GetBoolean()); m_uiUpdatesItem.ChangedExplorerItemId(id); @@ -263,9 +225,7 @@ namespace winrt::PowerRenameUILib::implementation { if (checkBox_selectAll().IsChecked().GetBoolean() != m_allSelected) { - auto fakeRoot = winrt::make(0, L"Fake", L"", 0, false); - fakeRoot.Children(m_explorerItems); - ToggleAll(fakeRoot, checkBox_selectAll().IsChecked().GetBoolean()); + ToggleAll(checkBox_selectAll().IsChecked().GetBoolean()); m_uiUpdatesItem.ToggleAll(); m_allSelected = !m_allSelected; } @@ -278,6 +238,7 @@ namespace winrt::PowerRenameUILib::implementation if (!m_uiUpdatesItem.ShowAll()) { m_explorerItems.Clear(); + m_explorerItemsMap.clear(); m_uiUpdatesItem.ShowAll(true); } } @@ -289,6 +250,7 @@ namespace winrt::PowerRenameUILib::implementation if (m_uiUpdatesItem.ShowAll()) { m_explorerItems.Clear(); + m_explorerItemsMap.clear(); m_uiUpdatesItem.ShowAll(false); } } diff --git a/src/modules/powerrename/PowerRenameUILib/MainWindow.h b/src/modules/powerrename/PowerRenameUILib/MainWindow.h index b017ca3ac..15e3bd05f 100644 --- a/src/modules/powerrename/PowerRenameUILib/MainWindow.h +++ b/src/modules/powerrename/PowerRenameUILib/MainWindow.h @@ -7,7 +7,7 @@ #include "MainWindow.g.h" #include "PatternSnippet.h" #include "ExplorerItem.h" -#include "ExplorerItemTemplateSelector.h" +#include namespace winrt::PowerRenameUILib::implementation { @@ -47,7 +47,7 @@ namespace winrt::PowerRenameUILib::implementation PowerRenameUILib::UIUpdates UIUpdatesItem(); - void AddExplorerItem(int32_t id, hstring const& original, hstring const& renamed, int32_t type, int32_t parentId, bool checked); + void AddExplorerItem(int32_t id, hstring const& original, hstring const& renamed, int32_t type, uint32_t depth, bool checked); void UpdateExplorerItem(int32_t id, hstring const& newName); void UpdateRenamedExplorerItem(int32_t id, hstring const& newOriginalName); void AppendSearchMRU(hstring const& value); @@ -61,13 +61,13 @@ namespace winrt::PowerRenameUILib::implementation private: bool m_allSelected; PowerRenameUILib::UIUpdates m_uiUpdatesItem; - PowerRenameUILib::ExplorerItem FindById(int32_t id); - PowerRenameUILib::ExplorerItem FindById(PowerRenameUILib::ExplorerItem& root, int32_t id); - void ToggleAll(PowerRenameUILib::ExplorerItem node, bool checked); + inline PowerRenameUILib::ExplorerItem FindById(int32_t id); + void ToggleAll(bool checked); winrt::Windows::Foundation::Collections::IObservableVector m_searchMRU; winrt::Windows::Foundation::Collections::IObservableVector m_replaceMRU; winrt::Windows::Foundation::Collections::IObservableVector m_explorerItems; + std::map m_explorerItemsMap; winrt::Windows::Foundation::Collections::IObservableVector m_searchRegExShortcuts; winrt::Windows::Foundation::Collections::IObservableVector m_dateTimeShortcuts; diff --git a/src/modules/powerrename/PowerRenameUILib/MainWindow.idl b/src/modules/powerrename/PowerRenameUILib/MainWindow.idl index 344ccf8eb..00c65a531 100644 --- a/src/modules/powerrename/PowerRenameUILib/MainWindow.idl +++ b/src/modules/powerrename/PowerRenameUILib/MainWindow.idl @@ -14,6 +14,8 @@ namespace PowerRenameUILib void CloseUIWindow(Boolean closeUIWindow); Boolean ButtonRenameEnabled; void Rename(); + String OriginalCount; + String RenamedCount; } [default_interface] runtimeclass MainWindow : Windows.UI.Xaml.Controls.UserControl @@ -50,7 +52,7 @@ namespace PowerRenameUILib Windows.UI.Xaml.Controls.Primitives.ToggleButton ToggleButtonTitleCase { get; }; Windows.UI.Xaml.Controls.Primitives.ToggleButton ToggleButtonCapitalize { get; }; - void AddExplorerItem(Int32 id, String original, String renamed, Int32 type, Int32 parentId, Boolean checked); + void AddExplorerItem(Int32 id, String original, String renamed, Int32 type, UInt32 depth, Boolean checked); void UpdateExplorerItem(Int32 id, String newName); void UpdateRenamedExplorerItem(Int32 id, String newOriginalName); void AppendSearchMRU(String value); diff --git a/src/modules/powerrename/PowerRenameUILib/MainWindow.xaml b/src/modules/powerrename/PowerRenameUILib/MainWindow.xaml index a7c8866b6..ae272846a 100644 --- a/src/modules/powerrename/PowerRenameUILib/MainWindow.xaml +++ b/src/modules/powerrename/PowerRenameUILib/MainWindow.xaml @@ -1,78 +1,6 @@  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -82,27 +10,45 @@ - + - + - - - - + - - + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + @@ -239,51 +218,59 @@ Extension only - - - + + + - + - - - - + + + + - - + - + + - + + +