From 8e0af3fc27e6d2fbe36f1534850d45e73ceab1ef Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Tue, 26 Oct 2021 10:30:31 +0200 Subject: [PATCH 01/64] [ColorPicker] HEX format tweak (#13989) * Added HEX2 * Revert "Added HEX2" This reverts commit 6d7b23fd74328f965a2ca58bed03c50b890b4c2e. * Lowercase HEX without hash * Update ColorRepresentationHelper.cs * Update expect.txt Co-authored-by: Laute --- .github/actions/spell-check/expect.txt | 1 + .../ColorPickerUI/Helpers/ColorRepresentationHelper.cs | 2 +- .../ColorPickerUI/ViewModels/ColorEditorViewModel.cs | 4 +++- .../Helpers/ColorRepresentationHelperTest.cs | 2 +- .../ViewModels/ColorPickerViewModel.cs | 4 ++-- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 09f024255..5417a6744 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -655,6 +655,7 @@ finalizer findfast findstr FIXEDFILEINFO +FFAA FLASHZONES FLASHZONESONQUICKSWITCH flt diff --git a/src/modules/colorPicker/ColorPickerUI/Helpers/ColorRepresentationHelper.cs b/src/modules/colorPicker/ColorPickerUI/Helpers/ColorRepresentationHelper.cs index e7bbbf1f9..8fafa440c 100644 --- a/src/modules/colorPicker/ColorPickerUI/Helpers/ColorRepresentationHelper.cs +++ b/src/modules/colorPicker/ColorPickerUI/Helpers/ColorRepresentationHelper.cs @@ -77,7 +77,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)}"; diff --git a/src/modules/colorPicker/ColorPickerUI/ViewModels/ColorEditorViewModel.cs b/src/modules/colorPicker/ColorPickerUI/ViewModels/ColorEditorViewModel.cs index 03966566b..87ed3edae 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( diff --git a/src/modules/colorPicker/UnitTest-ColorPickerUI/Helpers/ColorRepresentationHelperTest.cs b/src/modules/colorPicker/UnitTest-ColorPickerUI/Helpers/ColorRepresentationHelperTest.cs index 4030de319..5b1f26f91 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%)")] diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/ColorPickerViewModel.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/ColorPickerViewModel.cs index ba4a75673..e69324ea4 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/ColorPickerViewModel.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/ColorPickerViewModel.cs @@ -45,7 +45,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels SelectableColorRepresentations = new Dictionary { { ColorRepresentationType.CMYK, "CMYK - cmyk(100%, 50%, 75%, 0%)" }, - { ColorRepresentationType.HEX, "HEX - #FFAA00" }, + { ColorRepresentationType.HEX, "HEX - ffaa00" }, { ColorRepresentationType.HSB, "HSB - hsb(100, 50%, 75%)" }, { ColorRepresentationType.HSI, "HSI - hsi(100, 50%, 75%)" }, { ColorRepresentationType.HSL, "HSL - hsl(100, 50%, 75%)" }, @@ -199,7 +199,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels var cielabFormatName = ColorRepresentationType.CIELAB.ToString(); var ciexyzFormatName = ColorRepresentationType.CIEXYZ.ToString(); - formatsUnordered.Add(new ColorFormatModel(hexFormatName, "#EF68FF", visibleFormats.ContainsKey(hexFormatName) && visibleFormats[hexFormatName])); + formatsUnordered.Add(new ColorFormatModel(hexFormatName, "ef68ff", visibleFormats.ContainsKey(hexFormatName) && visibleFormats[hexFormatName])); formatsUnordered.Add(new ColorFormatModel(rgbFormatName, "rgb(239, 104, 255)", visibleFormats.ContainsKey(rgbFormatName) && visibleFormats[rgbFormatName])); formatsUnordered.Add(new ColorFormatModel(hslFormatName, "hsl(294, 100%, 70%)", visibleFormats.ContainsKey(hslFormatName) && visibleFormats[hslFormatName])); formatsUnordered.Add(new ColorFormatModel(hsvFormatName, "hsv(294, 59%, 100%)", visibleFormats.ContainsKey(hsvFormatName) && visibleFormats[hsvFormatName])); From 2c608c5a92626c99ded9d1fab54c078e0a12dd39 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Tue, 26 Oct 2021 11:34:04 +0200 Subject: [PATCH 02/64] Update ImageResizerPage.xaml (#13995) Co-authored-by: Laute --- .../Views/ImageResizerPage.xaml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/ImageResizerPage.xaml b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/ImageResizerPage.xaml index e88eac564..71ba7551c 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/ImageResizerPage.xaml +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/ImageResizerPage.xaml @@ -45,13 +45,10 @@ x:Uid="ImagesSizesListView" ItemsSource="{x:Bind ViewModel.Sizes, Mode=TwoWay}" SelectionMode="None" - ScrollViewer.HorizontalScrollMode="Enabled" - ScrollViewer.HorizontalScrollBarVisibility="Auto" - ScrollViewer.IsHorizontalRailEnabled="True" ContainerContentChanging="ImagesSizesListView_ContainerContentChanging"> - - - - + - + From a5edc29be7481ea336ba1aa3f0184a9237228d76 Mon Sep 17 00:00:00 2001 From: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com> Date: Wed, 27 Oct 2021 09:42:17 +0200 Subject: [PATCH 03/64] Announce delete dialog (#13994) --- .../editor/FancyZonesEditor/MainWindow.xaml.cs | 2 ++ .../FancyZonesEditor/Properties/Resources.Designer.cs | 9 +++++++++ .../editor/FancyZonesEditor/Properties/Resources.resx | 3 +++ 3 files changed, 14 insertions(+) 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? From c4951dc6055e9ff5982fcfd090700dc2b646e301 Mon Sep 17 00:00:00 2001 From: Jaime Bernardo Date: Wed, 27 Oct 2021 09:05:09 +0100 Subject: [PATCH 04/64] [File Explorer] Fix restart as admin notification (#14004) --- .../previewpane/powerpreview/powerpreview.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/modules/previewpane/powerpreview/powerpreview.cpp b/src/modules/previewpane/powerpreview/powerpreview.cpp index 751be1f60..e7c8768ad 100644 --- a/src/modules/previewpane/powerpreview/powerpreview.cpp +++ b/src/modules/previewpane/powerpreview/powerpreview.cpp @@ -180,11 +180,7 @@ void PowerPreviewModule::apply_settings(const PowerToysSettings::PowerToyValues& { const bool isElevated = is_process_elevated(false); bool notifyShell = false; - if (!isElevated) - { - show_update_warning_message(); - return; - } + bool updatesNeeded = false; for (auto& fileExplorerModule : m_fileExplorerModules) { @@ -195,6 +191,11 @@ void PowerPreviewModule::apply_settings(const PowerToysSettings::PowerToyValues& { continue; } + else + { + // Mark that updates were to the registry were needed + updatesNeeded = true; + } // (Un)Apply registry changes depending on the new setting value const bool updated = *toggle ? fileExplorerModule.registryChanges.apply() : fileExplorerModule.registryChanges.unApply(); @@ -209,6 +210,10 @@ void PowerPreviewModule::apply_settings(const PowerToysSettings::PowerToyValues& Trace::PowerPreviewSettingsUpdateFailed(fileExplorerModule.settingName.c_str(), !*toggle, *toggle, true); } } + if (!isElevated && updatesNeeded) + { + show_update_warning_message(); + } if (notifyShell) { SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); From df8aa42ee4bc56b9657f45c418d9fa669324e19a Mon Sep 17 00:00:00 2001 From: Jaime Bernardo Date: Wed, 27 Oct 2021 09:05:23 +0100 Subject: [PATCH 05/64] [OOBE] Link to VCM settings page (#14006) --- .../OOBE/Views/OobeVideoConference.xaml.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeVideoConference.xaml.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeVideoConference.xaml.cs index 056eb1035..d927ec223 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeVideoConference.xaml.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeVideoConference.xaml.cs @@ -5,6 +5,7 @@ using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.PowerToys.Settings.UI.OOBE.Enums; using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel; +using Microsoft.PowerToys.Settings.UI.Views; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Navigation; @@ -26,6 +27,11 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views private void SettingsLaunchButton_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e) { + if (OobeShellPage.OpenMainWindowCallback != null) + { + OobeShellPage.OpenMainWindowCallback(typeof(VideoConferencePage)); + } + ViewModel.LogOpeningSettingsEvent(); } From 4dc0a4c8c6d32bb9704cbe121155974859804192 Mon Sep 17 00:00:00 2001 From: Franky Chen Date: Wed, 27 Oct 2021 17:39:25 +0800 Subject: [PATCH 06/64] [Logs]Fix typo found by bot (#14009) --- installer/PowerToysSetupCustomActions/CustomAction.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/installer/PowerToysSetupCustomActions/CustomAction.cpp b/installer/PowerToysSetupCustomActions/CustomAction.cpp index f1b43265e..972f1d2df 100644 --- a/installer/PowerToysSetupCustomActions/CustomAction.cpp +++ b/installer/PowerToysSetupCustomActions/CustomAction.cpp @@ -51,7 +51,7 @@ UINT __stdcall ApplyModulesRegistryChangeSetsCA(MSIHANDLE hInstall) 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()) @@ -76,7 +76,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(); From fe8dfc7da3d38523cfab1199c1a70ae387fe1bc4 Mon Sep 17 00:00:00 2001 From: Jaime Bernardo Date: Thu, 28 Oct 2021 19:40:03 +0100 Subject: [PATCH 07/64] [meta]Add mouse utilities to issue template (#14046) --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 08087a4fb..72915697a 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 From 32a8936fc65c93d76f35fba8b7a8807fdbaae47c Mon Sep 17 00:00:00 2001 From: Deondre Davis Date: Thu, 28 Oct 2021 12:14:39 -0700 Subject: [PATCH 08/64] V0.49 Readme update (#14025) * V0.49 Readme update v0.49 Update * Minor updates * Update README.md * Update README.md * Update README.md Co-authored-by: Franky Chen * Update README.md Co-authored-by: Franky Chen * Update README.md Co-authored-by: Franky Chen * Update README.md Co-authored-by: Jay <65828559+Jay-o-Way@users.noreply.github.com> * Update README.md * Update README.md Co-authored-by: Jay <65828559+Jay-o-Way@users.noreply.github.com> * Update README.md Co-authored-by: Franky Chen Co-authored-by: Jay <65828559+Jay-o-Way@users.noreply.github.com> Co-authored-by: Clint Rutkas --- .github/actions/spell-check/expect.txt | 1 + README.md | 139 ++++++++++--------------- 2 files changed, 57 insertions(+), 83 deletions(-) diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 5417a6744..d3d79443f 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -1660,6 +1660,7 @@ prgms pri PRINTCLIENT printf +pritudev prm proactively PROCESSKEY diff --git a/README.md b/README.md index 1f4708bc0..75e2a7afd 100644 --- a/README.md +++ b/README.md @@ -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.15 Desktop Runtime](https://dotnet.microsoft.com/download/dotnet/thank-you/runtime-desktop-3.1.15-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 From 522e6b8001e9f7bdc28ce0973be310dd640ec43a Mon Sep 17 00:00:00 2001 From: Aaron Junker Date: Fri, 29 Oct 2021 07:32:18 +0200 Subject: [PATCH 09/64] Fixed typo in readme (#14062) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 75e2a7afd..c906be092 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ Additional work in this release include stability updates and optimizations, ins **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! +- 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)! From 3720d559a330903faa6a2bfacb1f55a0b74bb993 Mon Sep 17 00:00:00 2001 From: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com> Date: Fri, 29 Oct 2021 10:52:41 +0200 Subject: [PATCH 10/64] Fix ColorPickerEditor keyboard navigation (#14033) --- .../colorPicker/ColorPickerUI/Views/ColorEditorView.xaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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" From ebad5364b975442994b6b33867d28592f0ef6329 Mon Sep 17 00:00:00 2001 From: Jaime Bernardo Date: Fri, 29 Oct 2021 12:45:04 +0100 Subject: [PATCH 11/64] [FindMyMouse]Minimum delay and left ctrl exit (#14045) * [FindMyMouse]Minimum delay and left ctrl exit * Update mouse snooping as well. --- .../MouseUtils/FindMyMouse/FindMyMouse.cpp | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp index 33e182e43..1726014ae 100644 --- a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp @@ -54,6 +54,9 @@ protected: HWND m_hwnd; POINT m_sonarPos = ptNowhere; + // Only consider double left control click if at least 100ms passed between the clicks, to avoid keyboards that might be sending rapid clicks. + static const int MIN_DOUBLE_CLICK_TIME = 100; + static constexpr int SonarRadius = 100; static constexpr int SonarZoomFactor = 9; static constexpr DWORD FadeDuration = 500; @@ -304,23 +307,39 @@ 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{}; if (GetCursorPos(&ptCursor) && - now - m_lastKeyTime <= GetDoubleClickTime() && + doubleClickInterval >= MIN_DOUBLE_CLICK_TIME && + doubleClickInterval <= GetDoubleClickTime() && 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) { From 3a48fa03a51a9d4e7ded27329189cbb54a8cf60a Mon Sep 17 00:00:00 2001 From: Franky Chen Date: Fri, 29 Oct 2021 21:16:43 +0800 Subject: [PATCH 12/64] [Settings] Capitalize English word (#14060) --- .../Properties/Resources.Designer.cs | 2 +- .../Properties/Resources.resx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 From b87e51567e395e755a0b956caf7ad0186db81922 Mon Sep 17 00:00:00 2001 From: Franky Chen Date: Fri, 29 Oct 2021 21:27:53 +0800 Subject: [PATCH 13/64] [meta]Add Video Conference Mute to the issue template and remove link to 6246 (#14059) --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + .github/ISSUE_TEMPLATE/config.yml | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 72915697a..36e0737e0 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -48,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..8457f886d 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,5 @@ blank_issues_enabled: true 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 From 24d853966f9cb6218182f0a21c8600277a243c2b Mon Sep 17 00:00:00 2001 From: yuyoyuppe Date: Fri, 29 Oct 2021 17:22:27 +0300 Subject: [PATCH 14/64] [Settings] Fix localization resource include path --- .../Microsoft.PowerToys.Settings.UI.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj index 3858a2132..19cb24073 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj @@ -314,7 +314,7 @@ - + From e62df46c6181551e80b7ce2cf85f03beccbfadf2 Mon Sep 17 00:00:00 2001 From: "R. de Veen" Date: Sat, 30 Oct 2021 16:49:02 +0200 Subject: [PATCH 15/64] Update image of file explorer settings page with pdf settings (#14051) --- doc/images/settingsv2/file-explorer.png | Bin 102475 -> 73787 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/images/settingsv2/file-explorer.png b/doc/images/settingsv2/file-explorer.png index 7e5bc1090e51988f2d7ec8abe08b9dc9eac25a0e..bb83381ce05e754c370557a52d62da0aae8200fa 100644 GIT binary patch literal 73787 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ͦ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 /// 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}"/> Date: Wed, 3 Nov 2021 21:06:29 +0300 Subject: [PATCH 28/64] [Settings.UI.UnitTests][FancyZones] Uncomment test in CI (#14230) * uncomment test * fix typo --- .../ViewModelTests/FancyZones.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/FancyZones.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/FancyZones.cs index 18be72bac..0babee53a 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/FancyZones.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/FancyZones.cs @@ -230,10 +230,7 @@ namespace ViewModelTests Assert.AreEqual(expected, actual); } - /* - * Temporarily commented out - * - [TestMethod] + [TestMethod] public void MakeDraggedWindowsTransparentShouldSetValue2TrueWhenSuccessful() { Mock mockSettingsUtils = new Mock(); @@ -247,9 +244,9 @@ namespace ViewModelTests // assert var expected = viewModel.MakeDraggedWindowsTransparent; - var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesShiftDrag.Value; + var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesMakeDraggedWindowTransparent.Value; Assert.AreEqual(expected, actual); - }*/ + } [TestMethod] public void MouseSwitchShouldSetValue2TrueWhenSuccessful() From e19ecd2ba1e0ff53244cf0c411ab1167ce518098 Mon Sep 17 00:00:00 2001 From: Davide Giacometti Date: Thu, 4 Nov 2021 15:30:06 +0100 Subject: [PATCH 29/64] [FancyZones] Use accent color and theme (#14158) * use accent color and theme * typo * Updated FZ color UX (#14171) Co-authored-by: Laute * fix resources * rebase fix * label updated Co-authored-by: Niels Laute Co-authored-by: Laute --- .../fancyzones/FancyZonesLib/FancyZones.cpp | 59 ++++++++++++++-- .../fancyzones/FancyZonesLib/Resources.resx | 5 +- .../fancyzones/FancyZonesLib/Settings.cpp | 4 +- .../fancyzones/FancyZonesLib/Settings.h | 1 + .../fancyzones/FancyZonesLib/ZoneColors.h | 1 + .../FancyZonesLib/ZoneWindowDrawing.cpp | 17 +++-- .../FancyZonesLib/ZoneWindowDrawing.h | 1 + .../FZConfigProperties.cs | 4 ++ .../ViewModels/FancyZonesViewModel.cs | 20 ++++++ .../Strings/en-us/Resources.resw | 44 +++++++----- .../Views/FancyZonesPage.xaml | 68 ++++++++++++------- .../Views/FancyZonesPage.xaml.cs | 5 ++ 12 files changed, 172 insertions(+), 57 deletions(-) diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp index 38645b656..39e5ad66a 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp @@ -28,6 +28,7 @@ #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)); } @@ -173,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{}; @@ -214,6 +223,8 @@ private: }; std::function FancyZones::disableModuleCallback = {}; +COLORREF currentAccentColor; +COLORREF currentBackgroundColor; // IFancyZones IFACEMETHODIMP_(void) @@ -1332,14 +1343,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/Resources.resx b/src/modules/fancyzones/FancyZonesLib/Resources.resx index 1575de113..2d073710b 100644 --- a/src/modules/fancyzones/FancyZonesLib/Resources.resx +++ b/src/modules/fancyzones/FancyZonesLib/Resources.resx @@ -219,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 @@ -263,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 5b4b932fb..84ff97376 100644 --- a/src/modules/fancyzones/FancyZonesLib/Settings.cpp +++ b/src/modules/fancyzones/FancyZonesLib/Settings.cpp @@ -28,6 +28,7 @@ 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"; @@ -80,7 +81,7 @@ private: PCWSTR name; bool* value; int resourceId; - } m_configBools[17] = { + } 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 }, @@ -98,6 +99,7 @@ private: { 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 }, }; }; diff --git a/src/modules/fancyzones/FancyZonesLib/Settings.h b/src/modules/fancyzones/FancyZonesLib/Settings.h index 1aa5f4b38..033f4e4c9 100644 --- a/src/modules/fancyzones/FancyZonesLib/Settings.h +++ b/src/modules/fancyzones/FancyZonesLib/Settings.h @@ -32,6 +32,7 @@ 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"; 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/ZoneWindowDrawing.cpp b/src/modules/fancyzones/FancyZonesLib/ZoneWindowDrawing.cpp index caabd0cad..f23193843 100644 --- a/src/modules/fancyzones/FancyZonesLib/ZoneWindowDrawing.cpp +++ b/src/modules/fancyzones/FancyZonesLib/ZoneWindowDrawing.cpp @@ -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/settings-ui/Microsoft.PowerToys.Settings.UI.Library/FZConfigProperties.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/FZConfigProperties.cs index f873c4ff5..78aed832e 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/FZConfigProperties.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/FZConfigProperties.cs @@ -46,6 +46,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library FancyzonesExcludedApps = new StringProperty(); FancyzonesInActiveColor = new StringProperty(ConfigDefaults.DefaultFancyZonesInActiveColor); FancyzonesBorderColor = new StringProperty(ConfigDefaults.DefaultFancyzonesBorderColor); + FancyzonesSystemTheme = new BoolProperty(true); } [JsonPropertyName("fancyzones_shiftDrag")] @@ -126,6 +127,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library [JsonPropertyName("fancyzones_zoneColor")] public StringProperty FancyzonesInActiveColor { get; set; } + [JsonPropertyName("fancyzones_systemTheme")] + public BoolProperty FancyzonesSystemTheme { get; set; } + // converts the current to a json string. public string ToJsonString() { diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs index 7e163d198..29d365044 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs @@ -88,6 +88,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels _makeDraggedWindowTransparent = Settings.Properties.FancyzonesMakeDraggedWindowTransparent.Value; _highlightOpacity = Settings.Properties.FancyzonesHighlightOpacity.Value; _excludedApps = Settings.Properties.FancyzonesExcludedApps.Value; + _systemTheme = Settings.Properties.FancyzonesSystemTheme.Value; EditorHotkey = Settings.Properties.FancyzonesEditorHotkey.Value; _windowSwitching = Settings.Properties.FancyzonesWindowSwitching.Value; NextTabHotkey = Settings.Properties.FancyzonesNextTabHotkey.Value; @@ -126,6 +127,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels private bool _useCursorPosEditorStartupScreen; private bool _showOnAllMonitors; private bool _makeDraggedWindowTransparent; + private bool _systemTheme; private int _highlightOpacity; private string _excludedApps; @@ -520,6 +522,24 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels } } + public bool SystemTheme + { + get + { + return _systemTheme; + } + + set + { + if (value != _systemTheme) + { + _systemTheme = value; + Settings.Properties.FancyzonesSystemTheme.Value = value; + NotifyPropertyChanged(); + } + } + } + // For the following setters we use OrdinalIgnoreCase string comparison since // we expect value to be a hex code. public string ZoneHighlightColor diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw index 252a8fa8c..1395c4ea3 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw @@ -408,7 +408,7 @@ Use centralized keyboard hook - + Try this if there are issues with the shortcut @@ -444,13 +444,13 @@ Excludes an application from snapping to zones and will only react to Windows Snap - add one application name per line - Zone opacity + Opacity Open layout editor Shortcut to launch the FancyZones layout editor application - + Window switching @@ -520,10 +520,10 @@ When using multiple displays - + Where the mouse pointer is - + With active focus @@ -536,7 +536,7 @@ Zones - Zone highlight color + Highlight color During zone layout changes, windows assigned to a zone will match new size/positions @@ -595,13 +595,13 @@ Show PowerRename in - + Press shift + right-click on files to open the extended menu - + Default and extended context menu - + Extended context menu only @@ -646,10 +646,10 @@ Enable auto-complete for the search and replace fields - Zone border color + Border color - Zone inactive color + Inactive color Shows a help overlay with Windows shortcuts. @@ -939,10 +939,10 @@ Used as the 'modified timestamp' in the file properties - + Original file timestamp - + Timestamp of resize action @@ -1015,9 +1015,6 @@ Made with 💗 by Microsoft and the PowerToys community. Filename parameters - - App theme - Dark Dark refers to color, not weight @@ -1724,4 +1721,19 @@ From there, simply click on a Markdown file, PDF file or SVG icon in the File Ex Do not activate when Game Mode is on "Game mode" is the Windows feature to prevent notification when playing a game. + + Custom colors + + + Windows default + + + App theme + + + Customize the way zones look + + + Zone appearance + \ No newline at end of file diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml index 9a83f979b..bacad57cb 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml @@ -13,6 +13,7 @@ + + +

Yd{4H~?_A`s9A<2ju8|SpOo5 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 From a881e6b3d5aa99b5d6cb40e981509a87f465e3a2 Mon Sep 17 00:00:00 2001 From: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com> Date: Mon, 1 Nov 2021 17:16:57 +0100 Subject: [PATCH 16/64] Put UpdateResultsListViewAfterQuery calls logic with inside addResultLock blocks (#14078) --- .../PowerLauncher/ViewModel/MainViewModel.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs b/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs index 409144a7b..07573ec38 100644 --- a/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs +++ b/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs @@ -574,12 +574,11 @@ namespace PowerLauncher.ViewModel Results.Sort(); Results.SelectedItem = Results.Results.FirstOrDefault(); } + + currentCancellationToken.ThrowIfCancellationRequested(); + UpdateResultsListViewAfterQuery(queryText); } - currentCancellationToken.ThrowIfCancellationRequested(); - - UpdateResultsListViewAfterQuery(queryText); - // Run the slower query of the DelayedExecution plugins currentCancellationToken.ThrowIfCancellationRequested(); Parallel.ForEach(plugins, (plugin) => @@ -611,10 +610,10 @@ namespace PowerLauncher.ViewModel numResults = Results.Results.Count; Results.Sort(); } - } - currentCancellationToken.ThrowIfCancellationRequested(); - UpdateResultsListViewAfterQuery(queryText, true); + currentCancellationToken.ThrowIfCancellationRequested(); + UpdateResultsListViewAfterQuery(queryText, true); + } } } catch (OperationCanceledException) From fa3a5f80a113568155d9c2dbbcea8af16e15afa1 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Mon, 1 Nov 2021 12:21:47 -0500 Subject: [PATCH 17/64] Prepare for renaming master -> main (#13235) This commit replaces all references to PowerToys' master branch with "main" in documentation, code and build rules. - [x] **Linked issue:** #5433 - [x] **Communication:** I've discussed this with core contributors in the issue. - [ ] **Tests:** Added/updated and all pass (not applicable) - [ ] **Installer:** Added/updated and all pass (not applicable) - [ ] **Localization:** All end user facing strings can be localized (not applicable) - [x] **Docs:** Updated - [x] **Binaries:** Any new files are added to WXS / YML - [x] No new binaries --- .github/ISSUE_TEMPLATE/config.yml | 2 +- .github/pull_request_template.md | 4 ++-- .pipelines/ci/ci.yml | 4 ++-- .pipelines/restore-dependencies.ps1 | 2 +- CONTRIBUTING.md | 2 +- README.md | 6 +++--- SUPPORT.md | 2 +- doc/devdocs/akaLinks.md | 18 +++++++++--------- doc/devdocs/localization.md | 14 +++++++------- .../keyboardmanager/keyboardeventhandlers.md | 6 +++--- .../modules/keyboardmanager/keyboardmanager.md | 2 +- .../keyboardmanager/keyboardmanagercommon.md | 12 ++++++------ .../keyboardmanager/keyboardmanagerui.md | 8 ++++---- doc/devdocs/modules/launcher/debugging.md | 2 +- doc/devdocs/settings.md | 2 +- doc/devdocs/style.md | 4 ++-- installer/README.md | 2 +- .../Views/GeneralPage.xaml | 2 +- 18 files changed, 47 insertions(+), 47 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 8457f886d..f935103e2 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -7,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/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/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/README.md b/README.md index c906be092..895cce646 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 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..1e87d0e14 100644 --- a/doc/devdocs/akaLinks.md +++ b/doc/devdocs/akaLinks.md @@ -9,16 +9,16 @@ | 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/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/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/settings-ui/Microsoft.PowerToys.Settings.UI/Views/GeneralPage.xaml b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/GeneralPage.xaml index 84e9780e0..8beb9b602 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/GeneralPage.xaml +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/GeneralPage.xaml @@ -237,7 +237,7 @@ - + \ No newline at end of file From fe85ee5307a4a02cab3d01487ecbd0437797bff0 Mon Sep 17 00:00:00 2001 From: Davide Giacometti Date: Tue, 2 Nov 2021 11:16:03 +0100 Subject: [PATCH 18/64] [Settings] Fix default settings window size with zoom (#14199) --- src/settings-ui/PowerToys.Settings/MainWindow.xaml.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/settings-ui/PowerToys.Settings/MainWindow.xaml.cs b/src/settings-ui/PowerToys.Settings/MainWindow.xaml.cs index 328357628..1ea96c4b2 100644 --- a/src/settings-ui/PowerToys.Settings/MainWindow.xaml.cs +++ b/src/settings-ui/PowerToys.Settings/MainWindow.xaml.cs @@ -32,6 +32,8 @@ namespace PowerToys.Settings this.InitializeComponent(); + Utils.FitToScreen(this); + ResourceLoader loader = ResourceLoader.GetForViewIndependentUse(); Title = loader.GetString("SettingsWindow_Title"); @@ -61,8 +63,6 @@ namespace PowerToys.Settings { base.OnSourceInitialized(e); - Utils.FitToScreen(this); - var handle = new WindowInteropHelper(this).Handle; NativeMethods.GetWindowPlacement(handle, out var startupPlacement); var placement = Utils.DeserializePlacementOrDefault(handle); @@ -72,8 +72,8 @@ namespace PowerToys.Settings var screenRect = new Rectangle((int)SystemParameters.VirtualScreenLeft, (int)SystemParameters.VirtualScreenTop, (int)SystemParameters.VirtualScreenWidth, (int)SystemParameters.VirtualScreenHeight); var intersection = Rectangle.Intersect(windowRect, screenRect); - // Restore default position if 1/4 of width or height of the window is offscreen - if (intersection.Width < (Width * 0.75) || intersection.Height < (Height * 0.75)) + // Restore default position if 5% of width or height of the window is offscreen + if (intersection.Width < (Width * 0.95) || intersection.Height < (Height * 0.95)) { NativeMethods.SetWindowPlacement(handle, ref startupPlacement); } From cb4f4ff89c215dceb3053abda1ec29a6b5f3a986 Mon Sep 17 00:00:00 2001 From: Davide Giacometti Date: Tue, 2 Nov 2021 11:16:38 +0100 Subject: [PATCH 19/64] [Chore] Fix preview pane build warnings (#14049) --- .../UnitTests-PdfPreviewHandler.csproj | 5 ++--- .../UnitTests-SvgPreviewHandler/SvgPreviewControlTests.cs | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/modules/previewpane/UnitTests-PdfPreviewHandler/UnitTests-PdfPreviewHandler.csproj b/src/modules/previewpane/UnitTests-PdfPreviewHandler/UnitTests-PdfPreviewHandler.csproj index 44958107c..c2ba7b72c 100644 --- a/src/modules/previewpane/UnitTests-PdfPreviewHandler/UnitTests-PdfPreviewHandler.csproj +++ b/src/modules/previewpane/UnitTests-PdfPreviewHandler/UnitTests-PdfPreviewHandler.csproj @@ -16,8 +16,7 @@ {ECC20689-002A-4354-95A6-B58DF089C6FF} - PreviewPaneUnitTests - PreviewPaneUnitTests + PdfPreviewHandlerUnitTests netcoreapp3.1 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) @@ -49,7 +48,7 @@ - + Always diff --git a/src/modules/previewpane/UnitTests-SvgPreviewHandler/SvgPreviewControlTests.cs b/src/modules/previewpane/UnitTests-SvgPreviewHandler/SvgPreviewControlTests.cs index c9e0eef9a..5ad0f1db8 100644 --- a/src/modules/previewpane/UnitTests-SvgPreviewHandler/SvgPreviewControlTests.cs +++ b/src/modules/previewpane/UnitTests-SvgPreviewHandler/SvgPreviewControlTests.cs @@ -91,6 +91,8 @@ namespace SvgPreviewHandlerUnitTests } // ToDo: fix unit test + [Ignore] + [TestMethod] public void SvgPreviewControlShouldSetScrollBarsEnabledPropertyWhenDoPreviewCalled() { // Arrange From 3dc85595b1ec0c2800529ed5c36bfd3d0ba8137c Mon Sep 17 00:00:00 2001 From: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com> Date: Tue, 2 Nov 2021 11:47:34 +0100 Subject: [PATCH 20/64] Installer - Add PowerRename Runtime dependency (#14183) --- installer/PowerToysSetup/Product.wxs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index aaab0b125..16d21dd23 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -630,6 +630,10 @@ + + + + From e6a7f9193bc3bbb395165aba3ca45d1406b7298e Mon Sep 17 00:00:00 2001 From: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com> Date: Tue, 2 Nov 2021 14:04:17 +0100 Subject: [PATCH 21/64] [PT Run] If only delayed results available, select first (#14209) --- .../PowerLauncher/ViewModel/MainViewModel.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs b/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs index 07573ec38..f89518a9f 100644 --- a/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs +++ b/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs @@ -579,6 +579,8 @@ namespace PowerLauncher.ViewModel UpdateResultsListViewAfterQuery(queryText); } + bool noInitialResults = numResults == 0; + // Run the slower query of the DelayedExecution plugins currentCancellationToken.ThrowIfCancellationRequested(); Parallel.ForEach(plugins, (plugin) => @@ -612,7 +614,7 @@ namespace PowerLauncher.ViewModel } currentCancellationToken.ThrowIfCancellationRequested(); - UpdateResultsListViewAfterQuery(queryText, true); + UpdateResultsListViewAfterQuery(queryText, noInitialResults, true); } } } @@ -654,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(() => { @@ -667,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 From e2c8880363aea7945c10cf210eaca33d328a2ef8 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Tue, 2 Nov 2021 15:08:50 +0100 Subject: [PATCH 22/64] [PowerRename] Added KeyboardAccelerator + set default width/height (#14157) * Added acceleratorkey (enter) and fixed width/height * Update PowerRenameUIHost.cpp * Added additional modifier Co-authored-by: Laute --- .../powerrename/PowerRenameUIHost/PowerRenameUIHost.cpp | 5 +++-- src/modules/powerrename/PowerRenameUILib/App.xaml | 1 + src/modules/powerrename/PowerRenameUILib/MainWindow.xaml | 5 ++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.cpp b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.cpp index 28c85ab5a..214e7445a 100644 --- a/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.cpp +++ b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.cpp @@ -52,7 +52,7 @@ AppWindow::AppWindow(HINSTANCE hInstance, std::vector files) noexc 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""); + // files.push_back(L"path"); if (!files.empty()) { @@ -88,7 +88,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); 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/MainWindow.xaml b/src/modules/powerrename/PowerRenameUILib/MainWindow.xaml index a7c8866b6..88336778b 100644 --- a/src/modules/powerrename/PowerRenameUILib/MainWindow.xaml +++ b/src/modules/powerrename/PowerRenameUILib/MainWindow.xaml @@ -258,7 +258,6 @@ - @@ -304,6 +303,10 @@ + + + + From 992833bdc933d0dee7d05bd82663aa39f13d7b40 Mon Sep 17 00:00:00 2001 From: yuyoyuppe Date: Fri, 29 Oct 2021 13:25:58 +0300 Subject: [PATCH 23/64] [Setup] Update dotnet to 3.1.20 --- README.md | 2 +- .../PowerToysBootstrapper/bootstrapper/DotnetInstallation.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 895cce646..2f1333a9e 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline ### Requirements - Windows 11 or Windows 10 v1903 (18362) or newer. -- [.NET Core 3.1.15 Desktop Runtime](https://dotnet.microsoft.com/download/dotnet/thank-you/runtime-desktop-3.1.15-windows-x64-installer) or a newer 3.1.x runtime. The installer will handle this if not present. +- [.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] 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; From 39a03d876ea513beccfafea0ae4be3f9f8682881 Mon Sep 17 00:00:00 2001 From: Clint Rutkas Date: Tue, 2 Nov 2021 17:04:41 -0700 Subject: [PATCH 24/64] Update config.yml --- .github/ISSUE_TEMPLATE/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index f935103e2..28fc2643c 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1,4 @@ -blank_issues_enabled: true +blank_issues_enabled: false contact_links: - name: "\U0001F6A8 Microsoft Security Response Center (MSRC)" url: https://msrc.microsoft.com/create-report From cd5c22aaa13945468c45a36441dc35a4aad93adc Mon Sep 17 00:00:00 2001 From: Franky Chen Date: Wed, 3 Nov 2021 21:01:08 +0800 Subject: [PATCH 25/64] Resolved #14236 (#14243) --- .../OOBE/Views/OobeVideoConference.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeVideoConference.xaml b/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeVideoConference.xaml index ba60ae686..d27e572af 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeVideoConference.xaml +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeVideoConference.xaml @@ -20,7 +20,7 @@ - + From 9d9df949efebae1f8131482fc53cbb17e8b87725 Mon Sep 17 00:00:00 2001 From: FLOAT4 <17330977+FLOAT4@users.noreply.github.com> Date: Wed, 3 Nov 2021 17:11:42 +0200 Subject: [PATCH 26/64] Add keyboard shortcuts (without GUI) for switching windows in the same zone (tabs) (#13973) Authored-by: float4 --- .../fancyzones/FancyZonesLib/FancyZones.cpp | 81 ++++++++--- .../FancyZonesWindowProperties.h | 27 ++++ .../fancyzones/FancyZonesLib/Resources.resx | 9 ++ .../fancyzones/FancyZonesLib/Settings.cpp | 20 ++- .../fancyzones/FancyZonesLib/Settings.h | 3 + .../FancyZonesLib/WindowMoveHandler.cpp | 15 +- .../FancyZonesLib/WindowMoveHandler.h | 2 +- .../fancyzones/FancyZonesLib/WorkArea.cpp | 17 ++- .../fancyzones/FancyZonesLib/WorkArea.h | 10 +- .../fancyzones/FancyZonesLib/ZoneSet.cpp | 132 +++++++++++++++++- .../fancyzones/FancyZonesLib/ZoneSet.h | 18 ++- .../fancyzones/FancyZonesLib/trace.cpp | 30 ++-- .../UnitTests/FancyZones.Spec.cpp | 3 + .../UnitTests/FancyZonesSettings.Spec.cpp | 29 +++- .../FZConfigProperties.cs | 22 ++- .../ViewModels/FancyZonesViewModel.cs | 89 +++++++++++- .../ViewModelTests/FancyZones.cs | 3 + .../Strings/en-us/Resources.resw | 18 +++ .../Views/FancyZonesPage.xaml | 25 ++++ 19 files changed, 507 insertions(+), 46 deletions(-) diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp index cf4df48a5..38645b656 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp @@ -147,7 +147,8 @@ protected: private: void UpdateZoneWindows() noexcept; - void UpdateWindowsPositions() 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; @@ -155,6 +156,7 @@ private: 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; @@ -201,6 +203,14 @@ 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 = {}; @@ -224,7 +234,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(); @@ -639,10 +654,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; @@ -787,6 +807,7 @@ void FancyZones::OnDisplayChange(DisplayChangeType changeType) noexcept UpdateZoneWindows(); + if ((changeType == DisplayChangeType::WorkArea) || (changeType == DisplayChangeType::DisplayChange)) { if (m_settings->GetSettings()->displayChange_moveWindows) @@ -897,7 +918,7 @@ void FancyZones::UpdateZoneWindows() noexcept } } -void FancyZones::UpdateWindowsPositions() noexcept +void FancyZones::UpdateWindowsPositions(bool suppressMove) noexcept { for (const auto [window, desktopId] : m_virtualDesktop.GetWindowsRelatedToDesktops()) { @@ -905,11 +926,23 @@ void FancyZones::UpdateWindowsPositions() noexcept auto zoneWindow = m_workAreaHandler.GetWorkArea(window, desktopId); if (zoneWindow) { - m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, zoneIndexSet, zoneWindow); + m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, zoneIndexSet, zoneWindow, 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_; @@ -1135,21 +1168,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 +1225,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 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/Resources.resx b/src/modules/fancyzones/FancyZonesLib/Resources.resx index 44f77e9a4..1575de113 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 diff --git a/src/modules/fancyzones/FancyZonesLib/Settings.cpp b/src/modules/fancyzones/FancyZonesLib/Settings.cpp index e9f95ea58..5b4b932fb 100644 --- a/src/modules/fancyzones/FancyZonesLib/Settings.cpp +++ b/src/modules/fancyzones/FancyZonesLib/Settings.cpp @@ -32,6 +32,9 @@ namespace NonLocalizable 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 +80,7 @@ private: PCWSTR name; bool* value; int resourceId; - } m_configBools[16] = { + } m_configBools[17] = { { 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 +97,7 @@ 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 }, }; }; @@ -116,6 +120,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 +188,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 +262,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..1aa5f4b38 100644 --- a/src/modules/fancyzones/FancyZonesLib/Settings.h +++ b/src/modules/fancyzones/FancyZonesLib/Settings.h @@ -38,6 +38,9 @@ struct Settings 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..87e12404c 100644 --- a/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.cpp +++ b/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.cpp @@ -123,6 +123,17 @@ void WindowMoveHandler::MoveSizeStart(HWND window, HMONITOR monitor, POINT const } } } + + auto zoneWindow = zoneWindowMap.find(monitor); + if (zoneWindow != zoneWindowMap.end()) + { + const auto zoneWindowPtr = zoneWindow->second; + const auto activeZoneSet = zoneWindowPtr->ActiveZoneSet(); + if (activeZoneSet) + { + activeZoneSet->DismissWindow(window); + } + } } void WindowMoveHandler::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, const std::unordered_map>& zoneWindowMap) noexcept @@ -273,11 +284,11 @@ void WindowMoveHandler::MoveSizeEnd(HWND window, POINT const& ptScreen, const st } } -void WindowMoveHandler::MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, winrt::com_ptr zoneWindow) noexcept +void WindowMoveHandler::MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, winrt::com_ptr zoneWindow, bool suppressMove) noexcept { if (window != m_windowMoveSize) { - zoneWindow->MoveWindowIntoZoneByIndexSet(window, indexSet); + zoneWindow->MoveWindowIntoZoneByIndexSet(window, indexSet, suppressMove); } } diff --git a/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.h b/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.h index 48eeaae2f..2677e9a24 100644 --- a/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.h +++ b/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.h @@ -18,7 +18,7 @@ public: 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 MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, winrt::com_ptr zoneWindow) noexcept; + void MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, winrt::com_ptr zoneWindow, bool suppressMove = false) 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; diff --git a/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp b/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp index 409819293..160d380b6 100644 --- a/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp +++ b/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp @@ -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) @@ -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; @@ -311,11 +313,11 @@ 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) { - m_activeZoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, indexSet); + m_activeZoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, indexSet, suppressMove); } } @@ -431,6 +433,15 @@ WorkArea::UpdateActiveZoneSet() noexcept } } +IFACEMETHODIMP_(void) +WorkArea::CycleTabs(HWND window, bool reverse) noexcept +{ + if (m_activeZoneSet) + { + m_activeZoneSet->CycleTabs(window, reverse); + } +} + IFACEMETHODIMP_(void) WorkArea::ClearSelectedZones() noexcept { diff --git a/src/modules/fancyzones/FancyZonesLib/WorkArea.h b/src/modules/fancyzones/FancyZonesLib/WorkArea.h index 85b42744b..b0dbc47ea 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. @@ -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/ZoneSet.cpp b/src/modules/fancyzones/FancyZonesLib/ZoneSet.cpp index c89ba458d..c0995a44a 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,11 +310,13 @@ 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) { @@ -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,14 @@ ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const Zo if (!sizeEmpty) { - SaveWindowSizeAndOrigin(window); - SizeWindowToRect(window, size); + if (!suppressMove) + { + SaveWindowSizeAndOrigin(window); + SizeWindowToRect(window, size); + } + StampWindow(window, bitmask); + InsertTabIntoZone(window, tabSortKeyWithinZone, indexSet); } } @@ -546,6 +560,110 @@ 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); + if (next == NULL) + { + break; + } + + auto success = SetForegroundWindow(next); + if (!success && GetLastError() == ERROR_INVALID_WINDOW_HANDLE) + { + // Dismiss the encountered window since it was probably closed + DismissWindow(next); + continue; + } + + 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/trace.cpp b/src/modules/fancyzones/FancyZonesLib/trace.cpp index 12148b04b..8cb3619bc 100644 --- a/src/modules/fancyzones/FancyZonesLib/trace.cpp +++ b/src/modules/fancyzones/FancyZonesLib/trace.cpp @@ -52,7 +52,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" @@ -244,15 +247,21 @@ void Trace::FancyZones::QuickLayoutSwitched(bool shortcutUsed) noexcept TraceLoggingBoolean(shortcutUsed, QuickLayoutSwitchedWithShortcutUsed)); } +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 +290,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)); } 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/settings-ui/Microsoft.PowerToys.Settings.UI.Library/FZConfigProperties.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/FZConfigProperties.cs index bb5caa0af..f873c4ff5 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/FZConfigProperties.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/FZConfigProperties.cs @@ -10,7 +10,13 @@ namespace Microsoft.PowerToys.Settings.UI.Library public class FZConfigProperties { // in reality, this file needs to be kept in sync currently with src\modules\fancyzones\lib\Settings.h - public static readonly HotkeySettings DefaultHotkeyValue = new HotkeySettings(true, false, false, true, 0xc0); + public const int VkOem3 = 0xc0; + public const int VkNext = 0x22; + public const int VkPrior = 0x21; + + public static readonly HotkeySettings DefaultEditorHotkeyValue = new HotkeySettings(true, false, false, true, VkOem3); + public static readonly HotkeySettings DefaultNextTabHotkeyValue = new HotkeySettings(true, false, false, false, VkNext); + public static readonly HotkeySettings DefaultPrevTabHotkeyValue = new HotkeySettings(true, false, false, false, VkPrior); public FZConfigProperties() { @@ -32,7 +38,10 @@ namespace Microsoft.PowerToys.Settings.UI.Library FancyzonesSpanZonesAcrossMonitors = new BoolProperty(); FancyzonesZoneHighlightColor = new StringProperty(ConfigDefaults.DefaultFancyZonesZoneHighlightColor); FancyzonesHighlightOpacity = new IntProperty(50); - FancyzonesEditorHotkey = new KeyboardKeysProperty(DefaultHotkeyValue); + FancyzonesEditorHotkey = new KeyboardKeysProperty(DefaultEditorHotkeyValue); + FancyzonesWindowSwitching = new BoolProperty(true); + FancyzonesNextTabHotkey = new KeyboardKeysProperty(DefaultNextTabHotkeyValue); + FancyzonesPrevTabHotkey = new KeyboardKeysProperty(DefaultPrevTabHotkeyValue); FancyzonesMakeDraggedWindowTransparent = new BoolProperty(); FancyzonesExcludedApps = new StringProperty(); FancyzonesInActiveColor = new StringProperty(ConfigDefaults.DefaultFancyZonesInActiveColor); @@ -99,6 +108,15 @@ namespace Microsoft.PowerToys.Settings.UI.Library [JsonPropertyName("fancyzones_editor_hotkey")] public KeyboardKeysProperty FancyzonesEditorHotkey { get; set; } + [JsonPropertyName("fancyzones_windowSwitching")] + public BoolProperty FancyzonesWindowSwitching { get; set; } + + [JsonPropertyName("fancyzones_nextTab_hotkey")] + public KeyboardKeysProperty FancyzonesNextTabHotkey { get; set; } + + [JsonPropertyName("fancyzones_prevTab_hotkey")] + public KeyboardKeysProperty FancyzonesPrevTabHotkey { get; set; } + [JsonPropertyName("fancyzones_excluded_apps")] public StringProperty FancyzonesExcludedApps { get; set; } diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs index c990b1376..7e163d198 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs @@ -89,6 +89,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels _highlightOpacity = Settings.Properties.FancyzonesHighlightOpacity.Value; _excludedApps = Settings.Properties.FancyzonesExcludedApps.Value; EditorHotkey = Settings.Properties.FancyzonesEditorHotkey.Value; + _windowSwitching = Settings.Properties.FancyzonesWindowSwitching.Value; + NextTabHotkey = Settings.Properties.FancyzonesNextTabHotkey.Value; + PrevTabHotkey = Settings.Properties.FancyzonesPrevTabHotkey.Value; // set the callback functions value to hangle outgoing IPC message. SendConfigMSG = ipcMSGCallBackFunc; @@ -127,6 +130,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels private int _highlightOpacity; private string _excludedApps; private HotkeySettings _editorHotkey; + private bool _windowSwitching; + private HotkeySettings _nextTabHotkey; + private HotkeySettings _prevTabHotkey; private string _zoneInActiveColor; private string _zoneBorderColor; private string _zoneHighlightColor; @@ -152,6 +158,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels OnPropertyChanged(nameof(IsEnabled)); OnPropertyChanged(nameof(SnapHotkeysCategoryEnabled)); OnPropertyChanged(nameof(QuickSwitchEnabled)); + OnPropertyChanged(nameof(WindowSwitchingCategoryEnabled)); } } } @@ -172,6 +179,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels } } + public bool WindowSwitchingCategoryEnabled + { + get + { + return _isEnabled && _windowSwitching; + } + } + public bool ShiftDrag { get @@ -604,7 +619,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels { if (value == null || value.IsEmpty()) { - _editorHotkey = FZConfigProperties.DefaultHotkeyValue; + _editorHotkey = FZConfigProperties.DefaultEditorHotkeyValue; } else { @@ -617,6 +632,78 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels } } + public bool WindowSwitching + { + get + { + return _windowSwitching; + } + + set + { + if (value != _windowSwitching) + { + _windowSwitching = value; + + Settings.Properties.FancyzonesWindowSwitching.Value = _windowSwitching; + NotifyPropertyChanged(); + OnPropertyChanged(nameof(WindowSwitchingCategoryEnabled)); + } + } + } + + public HotkeySettings NextTabHotkey + { + get + { + return _nextTabHotkey; + } + + set + { + if (value != _nextTabHotkey) + { + if (value == null || value.IsEmpty()) + { + _nextTabHotkey = FZConfigProperties.DefaultNextTabHotkeyValue; + } + else + { + _nextTabHotkey = value; + } + + Settings.Properties.FancyzonesNextTabHotkey.Value = _nextTabHotkey; + NotifyPropertyChanged(); + } + } + } + + public HotkeySettings PrevTabHotkey + { + get + { + return _prevTabHotkey; + } + + set + { + if (value != _prevTabHotkey) + { + if (value == null || value.IsEmpty()) + { + _prevTabHotkey = FZConfigProperties.DefaultPrevTabHotkeyValue; + } + else + { + _prevTabHotkey = value; + } + + Settings.Properties.FancyzonesPrevTabHotkey.Value = _prevTabHotkey; + NotifyPropertyChanged(); + } + } + } + public string ExcludedApps { get diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/FancyZones.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/FancyZones.cs index 19895b3bc..18be72bac 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/FancyZones.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/FancyZones.cs @@ -51,6 +51,9 @@ namespace ViewModelTests Assert.AreEqual(originalSettings.Properties.FancyzonesBorderColor.Value, viewModel.ZoneBorderColor); Assert.AreEqual(originalSettings.Properties.FancyzonesDisplayChangeMoveWindows.Value, viewModel.DisplayChangeMoveWindows); Assert.AreEqual(originalSettings.Properties.FancyzonesEditorHotkey.Value.ToString(), viewModel.EditorHotkey.ToString()); + Assert.AreEqual(originalSettings.Properties.FancyzonesWindowSwitching.Value, viewModel.WindowSwitching); + Assert.AreEqual(originalSettings.Properties.FancyzonesNextTabHotkey.Value.ToString(), viewModel.NextTabHotkey.ToString()); + Assert.AreEqual(originalSettings.Properties.FancyzonesPrevTabHotkey.Value.ToString(), viewModel.PrevTabHotkey.ToString()); Assert.AreEqual(originalSettings.Properties.FancyzonesExcludedApps.Value, viewModel.ExcludedApps); Assert.AreEqual(originalSettings.Properties.FancyzonesHighlightOpacity.Value, viewModel.HighlightOpacity); Assert.AreEqual(originalSettings.Properties.FancyzonesInActiveColor.Value, viewModel.ZoneInActiveColor); diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw index d43d39859..252a8fa8c 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw @@ -449,6 +449,24 @@ Open layout editor Shortcut to launch the FancyZones layout editor application + + + Window switching + + + Shortcuts for switching between windows in the current zone + + + Next window + + + Shortcut for switching to the next window in the current zone + + + Previous window + + + Shortcut for switching to the previous window in the current zone Shortcut setting diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml index 13f7c8e28..9a83f979b 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml @@ -148,6 +148,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + From 9ca32aa3eaad74c0111fa6d0fc36f8483f10fa6d Mon Sep 17 00:00:00 2001 From: CleanCodeDeveloper <16760760+CleanCodeDeveloper@users.noreply.github.com> Date: Wed, 3 Nov 2021 19:05:35 +0100 Subject: [PATCH 27/64] [Image Resizer] Add option to remove metadata (#14176) * Implements option to remove metadata (see #1928) * Add unit test * renamed settings switch, update ui text * Fix exception type, add justification for swallowing exception * Add unit test to check handling if no metadata is there in targetfile * Reordered the checkboxes as suggested by @htcfreek * Reduced size of test image --- .../tests/ImageResizerUITest.csproj | 10 ++++ .../tests/Models/ResizeOperationTests.cs | 45 ++++++++++++++ .../tests/TestMetadataIssue1928.jpg | Bin 0 -> 40675 bytes .../TestMetadataIssue1928_NoMetadata.jpg | Bin 0 -> 19519 bytes .../ui/Extensions/BitmapMetadataExtension.cs | 55 ++++++++++++++++++ .../imageresizer/ui/Models/ResizeOperation.cs | 14 ++++- .../ui/Properties/Resources.Designer.cs | 9 +++ .../imageresizer/ui/Properties/Resources.resx | 3 + .../imageresizer/ui/Properties/Settings.cs | 24 ++++++++ .../imageresizer/ui/Views/InputPage.xaml | 10 +++- 10 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 src/modules/imageresizer/tests/TestMetadataIssue1928.jpg create mode 100644 src/modules/imageresizer/tests/TestMetadataIssue1928_NoMetadata.jpg create mode 100644 src/modules/imageresizer/ui/Extensions/BitmapMetadataExtension.cs 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..fa5f3882c 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,50 @@ 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"))); + } + 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 0000000000000000000000000000000000000000..4d3f266b62101ad1460788ec1ae40be464caed78 GIT binary patch literal 40675 zcmeF4Wl$Vj*Y6t!4-yC(G? z=dHKyhd1}bt-5vDQ(d#B``^1~{d!H$>fOC}KTJO?15afoWh4O@7#Khr`T`z)ku^wo zSegNVygWb$000tz3_Jl~p%yjtLkJAUUp4~NE`@pemyH3nt6{JJ5L5#LeE~2m|LDKN zu>NIZLp58WmH^ye8UX1wjOyPumf8*xfCycV3SH&}09a>4J5UP=Iz5(00*?e92|N;b zB=AV!|4RazmUbrg?#^UN7WOXoWD+KpF7{4jEX<6|j4aH|EG&#HoNV0efQW;GjVWLW z71^1Rv9OWZTH09x!2fJrR%TWfUKSQ!W)?DL7G5@1UUn`3covurz(avUvl+~jz|4Q_ z>oD@a^BibK6h`y6O$z|ApnvB%Sg^5h|JbcOImhy8awSgi4&e{9}(xPLUF z@rZxd3Eg@;^56DTsEzWEUN%1Nulhk8Jka^^|FJ*8{H=$6>palkHgw~8@PFIS0pLHm z7V^gLfO^ceep7fXj|3hGJQ8>$@JQhQYXZ>B0Z4;Rzgu76|66ACJ4FKick=)kw*vrR z%LM>#djNQu2>|cR0RV^UZ<4?3WP?t>^B`ns7W4b_?+E}<|H@rp<$l{}|KvA40LDM| zFz~N7CRnxK8q9y@PXc)VTKBK{`2Wmb1c?8d{{-5O^jCiKdo^hJ*Y}5S0Uikv5fK>~2?Z4!4fV+rR06DL7}&(mNlA#G6A_V7GSZNd zzoH-_qUCz|ikXF-ot>11M}V7^pOKB7^>-yO$jHd3Pf+pE(C}Gb5WQgiQ$8;Njt* zjd?IW(B%LeCOp;)77>JJ%0`Ifj@YaLaXCm7qE#I@DnE}X+1@$@BBS8q;S)TkqNbsJ zNypB?$;HjXD<&=>DJ3l=I-I?74$whBs45MB0eE8 zDLExIE&X$DUVcGgQSq1RuQj!G^$m?p-#fdyd%(SY{i9>!6O&WZGqb-|R@c@yHn+BS zj!#a{&Mz*nAlJYB`t9ex%fB4^Z+>Ay{ep#q1HmEw_6r8q9Xf$9;ox7eAYh3oBN{nA zBWDdj!WNCosp>$cU{g85dF%8O1(%Zj7uE4^*Z%VCf96==|B+|^IQAdEW&u3S;|x+2piuN!?s;|N_jVq!j$#1Q!!oF1=Q?x;3% z#AMRJ2Ot<0XAE_d^gH57(VLdQ6>-ce8hZj!zGw4F41MD-rKfG@vf7iR@lbEkwfxwG zoBRyd7?r8$9FP@Kce|yrZDRt)9{?8N2cTkCBwiks@BMpFTtJK}cvFSGr>pXf7`2pu zB%8GHm^H7z$TjKd_O%LeF}@j}x`K>A5q|GRglTN?O;kcF%4$W?1qb!S8*K+ExTlu> zk)N>Ger%>&MmjlQ3<>vAS|m7{&0_YxRa9WR4J+7J>gNJPcn|_`qOo5x`U5gFk$xF! zz6R=&YJtviul=T!-@_!2EUrd#7xqYs8V7W+yD(A)BO8{9Z}*j^nR>k9FPh~{abPZ$ zLP2oM@@=_Kq^gYml_x`L&TY^+S!^ZVjCmKXZ5%02dorwDpU{GFlrJxUOWfu%Qr|4- z94%79+Q?rg!;7>aqu(mM3tpceKbi}B5lyK_NBsk%lD!->NG>kHjn5Z=7%S3yuhv^i ze#6`Gq92R(3p0TK?b`8r6pnnmNYwYLXb%7%1s?#3$kS#Osse+UNOSV`9im;2d>#TYn0_Qx2ijxS1;GN+6d%QF!-Gi)< zfW(3^EV%^6OUXriBjrlR$f$nlt(Pg!UGSbkD&nYnBDoyU*Me2MCh{YXXN0qQ~HdK&>6_4G07_hu30&4DaPt71 z7q`g;&LYr3qFdM zZ8ce1C)Xsojc4q^W$nMZr}=wsmYm10QItNM_#C$0t?mk2-|6X>)SU0|WqY|zoD!1G zpI=}jypIUq08gD>vj4)LMrY+FR) zjfd*DotLPR;lB2H^6)dKSo0F3$rXKqt^^I<4>iEb7x4 z`_B*V<6h-XTyL;2mYwJ%2n4*!{X~SxU5GF~UMZgEn~M`>)h3@% z95z1BIrwUHnUPh^RCsfI_15CqeS4WyKrFh~i+ibC%Ynd>;i z9zTcv7IDMz@QJb;x8S_}V}99vn(#!J zcr~MhNW+gJN-zC$9Wfo@g3rnKLVzw|UMF?peuCut^gO;XHzGv+#ph-@^mP&kCK@^z z0;^h(tqnZ4@?vywS4=p<6W8s!N|O-D=2LRjMkf8~FPm_>v20fvN@uLPG00!K*+^^j z+Q#b=5I8#7WWr$}Uho89n^A_zl8f;Evi>gPHBYKY;~{lEUy+2$s&5Np?G9d?Xx_!x zNer6}9x7{vsmDpKhIE|33afA*|Nhx#mMfD{L-y-R&FRyS4}%QICd0!r2Iy>A4d89* z*L{)`Hp{wvYRuS64Y3`lJ(5(yCv$6CYNj%(Q)H{+BiQiVj-u98bnm*hBHT`{#f(?Sm;^IRvQeZ zejXjz)9UHNhs?+Kqx-7y8)rd7Fr=s#Wzl3Udd1P&6X(m!z6{x2r(i-Mp{`JxBt6Qkvq&M8gJQ)Vz8{Vcvk(8dLW~ z?UeN=7L)RgIgQEE?@Oy9{hgZw(J`j-&E!4H;l~%WUd47gSD~y|etB1?esU!9owP-y zZ?#f5e#b)UE|>Ur(5^6<+nziMGxs$w=R#L34EOweq%n>GVVU;+#A@>P#!~|GXld9l z()k$4?M!}|URlh}JEh1MxF?soZ!17DSi=>syyMzbo7Xg8IfdNeqLHZ@SEClh_MHtn z(OSok#NQ-ku1E+mpIk(XfK|^wm7x~29K*R4sc$+n)RtCPeL$f0V;fZe&hm{e2>&hU z*`b1JaW}vF*VeUDpAb`t5YEo0n-)AVA#*|fCsM#!o({3C;FnI*IjbjDG_jsP=Bi@- z!_?jsxE-)M*EoGr*UF#R|26t1g77MWswfseEh$JEE)B*ZkI^~d3{)&E;l}YqfYsXj zMG-;UPp|%ay=ZqxS|lpE7${a|Zi=JyUQes1Drc#a8C6Qb8*|?HI1jbhF+af8 z+NOJ+aceE{B|}ZmYMY{0k&efrT!_@0ht zq3A&bX50`hXE3RY*Tj)%R;hxQ>PwcHuE_SzbY**#SJjl#6%x$tcY_cd)pgOgt^ zGO=-iO9`6nOQD`jO3Wdcs42Q$wm_*%Mdi_?b4EuQWLtZ*JPd3kOvh{$#bTOxpCQ?` z4F&H9;8<-Y!&U5hQ>aV9PbV$$-DXx>`=P1{SZS8aWvBgCo>kX_QM8kvW#CyQYCNr} zQDalk8FsmCZ!q0w9%i9Xp{wPh{JIeBxCIysy%uvJm%F94E?*AI)c%(*{K7qmF|6zm z6X!8nW7g=!qb+3$9)RB1lc88%FBNx#?37l%XT$ps01VP69Der);CU=Lq%dY#-~pJy z`S@cyzhvBu*<)|sZ%J)Fn@RA@1$pa?MuRUq`!qd^^>qaDPVTkZSLwWo0a{=62f(Qi z1;Z{e@H%Ridv)r1oUz+6=-#uKAsUfvGfP5T&31dPmTX5oF3iUKDp+hn(z}c~dfieO ztg!S;?UHi~vMX<=AM-Ht@QY>3!o|gbmx;;FnbF9^{;esavAr#mhmivl3nMcVz%S(C zU}S7<>O%I`)ZEfefab8dorcWPM1V$f6!=1eR+JUmRytW2z|3{VAwv!|Vlkq3jFGsWK(h?_baJ6SroSlZi>{jSjHt-Y&@ z0J*EHr3tT@kr~HZ6B7=Gw?=Fx3@j`rMhr$KY%B~WY-}c694y?dW-M>X|NRAR9sb$; z{}u&wdr+5n#hgryTujBGli=^uU}&uj%q(iZFM|9&)o0}3VdiK0Z@T}iXYZtDZ*L>` zKf1>9U#j_gHGZbw4*nO11^+b%$`ES(LmY~q?EgY}+{hn6e#G^N>yHThk@zvX9&!B< zfj<&IM%N>*KO*o);>YNE#Pvr6{z&{7U5~i_h`=9-AEWCL*B=r1Bk^N&J>vQ!0)Hfa zjIKvqe?;Jq#E;SSi0h9C{E_%Ex*l=;5rIDvKStLhu0JC1N8-omdc^fd1pY|;7+sIJ z{)oUIi65iu5!W9P_#^RSbUotwBLaUUevGb1Tz^F1kHnAB^@!__2>g-wF}fac{Skpb z5p#6VOzoguHQb?nGXCmC4+jhFHwo>B4g$eJ3p_FcJhVJP zLP9`BdxD0B_5>9b{V6skI>s{$R8%Z{tYiocRE>fcq5*x?lfe~G^P6%Yo zi=l^-QhtT4dI=^+;S{r!s36+2iw6KDJgKXH6(-5UcNp3&>-ya4*p0cw*LCR2Hq}^P zJlGcPdtHxK0T}MgH)EXh$B07(QCRO={-6mhm*Rs68LUeEFU&;fGaVz4^E_vc$jSuj zU)*)ji>>ras5wABP36=h&_m6biTS($~a=a8X#fD&~Fg% z>3R88FE~OgzjnTwabr(aXdq5yW&pMBC6W{s(6@@pEX5)4Q>Tx+Z)pOp4w;hcX-%3M zC4jLA=Goj<{5t5)%*#z;rfx{FPV8BHR_kxjcR47*j2+NvOmJDWBCWCV6ohC7Icc#a zi^LGS=MV_cmAW@J52GC0KbfYF7g1t6MNfJQ;qNoD=M)(9hpGQG?3m7h9kh?tEZX1R z5e){xU7y`qb7>t5rr5ewb{hHl)^+9WTUwbQxtqg>I`En&1iYp(O@ zOOb8lRGFQQA3gxdX>X+T%=3vc)2r*x=jBy+5c0lt*IS|W;@`G2*?)`h&Bv}l`xuX~ zEDP&RS~hBf>{Ad$xk=p(QW|1)Qj(zz*g1<FL8afPkjz@D|FuVf5@SXR@iWiM^xX$zx;M*=}!CrC=UGa zO+oP~d0PNTyb8VY7$Hq9D*GgnWWo}Rpd6Il!gnkYJTGL4sv??4Yp8sZ)t=AWE8d$g z^CF)x$X7rJCEo`j|MPv57e-sXm@CPyn|u+g%uTE1C1&yHDdp?Ax|qbfL&HKVx!#`# zt0EL@5*-3phYAVi7s8NChuzG=Xz!TOgqet)r75G?cgo>)MiE8$JszvA64uWtKYnN* zzM{;M8ZBA0Zjuc3A=1gpC$379lO%481=wN=G*3&fZVi9cG={KM=p?%XI0iP8rv^MY4G8_*4(XKHz62 zX&n$cZbv7EmZcG~RMndDF~48K9NB(7_OoTBO*SslpYT)hJ~p9eYWaTS0BG!XP4!hD z9>B`2pKBHAa$lpXR0S(lciiqW_im8HQ%E*J2@}#8GCUmYr>Yp2vR^7_-v&c-ty)32 z0heS`&a+$#+IHg05BpX+ST3@*S|BN0$c}PtiSc|n65!49B2}QL(K|LvuvjvYK5%_f z;!fhCfm^{R*5m7x3q-mM`+V69b+x6}8Qt(k=I?pGqHb!jCh8X)=TEjenbqyiAYvE7 zrTB5Z7w%aw{}TxTlZ@?%rrr$WRDXMF}h{l(`MNvZb;~&(Y4YO zr`j5o(!r{IQfH z@fS&X(rxovWFv5E4sDye@(mGe9?=p0tkJ@k+hD6k=XFSb|F0N;NR`PbszAe37-^N5 z_jp&L!zMzru82Cy-^8{MMK{9q{HtBmq^8jbVg>1Ng`qA`70mP_wp}(zYY9HQ$&%|8 z(x9}0LrWIl@oNcvQ34^ixuiWd8Dq}{t)IIy zQXKM`QLN&!Ww!m-#<()0B&zsH`RX1*DrCY+$FZR7{-oK4%%;RcVQY`UxcXE|d_pO~ z&bbKFWyQVYB(+XdR2NDYoE-Kaigrcym|XEUl)o^6< zxOsSCp82k`9yHEN;?nGM2sUBAHJ%CPmq6s#4u$2uwZxzI>-=6~bB@Nu4Y>8)Mj14R z(2OA*H)=Ohv=hLkmk`d(TdW*2;!2yL46vKn#*#>mL3~}kF+OTmtn_QJ&}8)o5PYQJ zUb`goLWNP*quw21=REC;1CLQ+7)<07SBu#l9OSM;q_0;Au0iViwkz6cBiF6%VZ;hP zTbe)NKkwHs_Fo5mvVEUMNWj1RLcsZOGC@w;cJ+JtMi2VEJIjG@(hJN1X8ll?ciD-i z4I@HPyw{p19lw-}oN`g`+egmw7Luk{W_5hwZ5esscWL<2LQZ@Ss{?f8&}Y&S9p*5M zznyQkY7Caxjiv$f4&a}S!-&p7S8B2ysk~U24(iTJ(KY#mTT!_MAt7Y)b6Q6ZC3mZ5 z1@Fp`fcT-M%EH$_+kWKK#k7I1Vy@Iq7{^)BiUR^VF7K?fly_|NBT$16++bo0(?eYN zzQ*nAkk1!EQehSS6uruibM+!gle{AaQLrRiUe2AOC#s!%Vm%vPO{WD7pXq}{o3Gqm zi#P_;#tzD}xa0!cxoG|O!RpgePSX4v&4(yM7fa|`jj1y9vy$3hvzhk^Hg}c9ehtx0 zNql@A*hBTTk#YcCflv@KEQuh%%%bDwR;e}HZEtIXHH%pz8FT=%WwnsTB;gy1`O8O6 z<%3+^_y*pX7`w*t$VIWQA*b&9NPe++K+`URG^JI2;+=?Vkw=8|sChKKw$99R9%PIU z)2fb_5G@~M;@yVv(wgnFi9lK-&faEaIO|%+pV}y z^Z5+t=71C)P_abI(Mx<|( zdk=Wm2XgQujKGRauawPGI?1k2{4^1!pg^mM3yRy06{6F;)Qn?F>K1IX%pGp=%iX~! z*PoyBXIJ!P~g~Lws!M-yXuA(F%Z*yTs#@2>8&uSI>!gk;>FHY`Y2bMiuS5~#*kz? z4e3D`{C9%`?QVzMJ&iN|*r$EvXS&_Cc{ATaxQ%0iqa4G)#E7y9eW5qF5-yThim?Dh zdLbc;_gHaX&s}eDg+0?;r8z--TvOv~E!%v>ESXmLNst7~H1t$v?Er8gfYTdIYB8pwkdcp&f@g+c9#6=N9lD9rNQSM5JLjE>sWc- zp5cy)GMv4#0o@WA($pXezOUJ3f<<~$b)k5!EA3A?W*Q?1xo*tzT`lTPUWlc3MFywV z@vyv!L~phJ{DVX0oR1^Rk{~ZCod$sdGdwY)e|R|H|N zO-CWU?8$=0=qHxz_0`eXQO}U7K1tZ8{BYT^*b+~--e8!#7_;pe>h9|-egL!s=1vPm zOJ7w;Qza=(j@*9TLJ%6Ua{JU@IV&zkS1v}o6-p7D`)XD#FLQ9zq2Gg)OYE>)uvEvn zefNy}PFJpN{GMXA)9M&Z={D((=xnPJw~%a;tq1Bh^+=Iz94p6SJyo9}G)Vi$m7pJ;@@J71JsyJcTajMmwx3%p~Dcm+LQkDp|@k9ZGEGsm_ zm%XPx_$=(y9yQPmr90Nl6u2$R`ebr^D5Zg-V++a9zl>mFx1I|>G>+Jj=NdF-D5);} zrrpOl%0sXUM|6%@rkHmXs+n~^r)ce=A!Dab-V{$j@xCamS6Z_3Ss$1`Abp#?6@64d zNe-3ed!5DLt6$+N>}HY0@6ZdanQtwsM_taV>uPz`)D*eq07*^bO2s7W#h zWYvBth~BnJO{JR^=WLH!;gPt-ya8Kz=9DGw&jv`@eS4!mqssg0$$E)Y10B)bJckLnHm0_9O3SJ0#YI?S~*z zS`(Oo+N>c?)iO)ie}I8S7_wX-C=zYpP) zljl7xp5Nplb6LC2%IasNfb{?Jq^YRoe8Ts#%+NL)nxFZIqi?0VoLMx|F= z)s;lWKiN`{WXl$o7`GL|m;0oHSAaI_5AwSE7}gjU&I2DyG#NU(TET+^O+=`hks5X% zR)wC;Q))VajjgeJKRBJTOWg>hD>a(PJOQxdPJ=tq9oP(V3cBx;Hoizvengl-Iq?ZC zl$G=7pLay5TUnwN^4>j^xS+dc5US`pHGJv;{Tq~Tn0OO?J^U-)8nd8@Y-n_VRt6t% zm?jL7B2s5i)tPeTqvtt@V82ZE`bt?f2k-5OPmeFTw(d`G&arc{kEg3c1bj<~&hqyj zJJct6(;W_R?myccjrPy;yyd<4d`~%cD`f9Xyyku^;f}V`9b5M$yA>{)%QNEH{Vay; z0aF=f8Zn+l2N~$cdBITrG-DNKrJ(1=dvT&VQSyvnVQ!#`@;YIU)-XtVKAih#)fg)iECwnC?^Bs@dGQD4}TqOD&x<{FH7}X6OYro=GS47e~E7R}u zAGoJ`O1UX4!8U%fnh2g8tvxaB`P);@SNo+Ew)-zaCiy(osJ?`KbF~;2VjCh) z-F&eGNn$#G>TGGgk_lgrXyB}niCTzmPH2kkP9(9xigqeVqma(ZNB7=xI5C8xpP6Z3O3YE{42 z@t9DQ`XwUO_^H13c^FeEu63p?VK_%({SC9mQ+^TyDLw}$9vYqz;t~W~M8gl?yfm`< zN?J(Yd~HZ4YTGK1NpPYC1=S?fXE$N!oLin*b06fiN*kZH^w_h6&!f*{@qvOO&x`F> zHLyV^*$BLl!e0owOguqQEM5rY zq4=6?B37G<%6*fURZy{r_f%3}nt@sj)`6R7!W%r4H3MSYm=(P|8^%zOSFOt#JfFJ0 ziO|43C3v0Zdm@xJyx7)wD=)FuJ4Lw8_unJMe zcBJ^}e5LGF4CQw$%}evDUy6F^B}DS@O$z!r;S|Koj#=n|jYWaenk-gPy_(?elh;&N(LQ#_S z2ng!w1lUBT1Q^@9I@PRl)Z>IE4?vd5-9e@R--LE9Tj$yUVbkW=M$BSebHQ!&%aNo- z-U`O7qOucty4EIXoR~{=W3m{##J=40Ma9|yxUiYds+jeJC7Q^yl60>;Cr@Y24FZ(a zjOdA14E$wOADL2-Df)Ngz+~zJik{D?EWIU4pv@Z4U$!aBVC-O3T&=%?+Ho)Tu3Nf* z^tX>KDV>PGZD#||t<^@>kCC?*$KV(W9-CNAdoSt}{MiZzA_gihOIFIT1Ry-A(rauo_Mw!vjj-?e~fU^Gpy zuJTWpRd2|cXO7te}MoiN&)ApoJ10Oe!GpcX&rX)D(EuJ^vRDC=j+LWEOl2T zQR*H{ERa*h2&x~QX?4XML%70oNwR3^5&gwZp1iEEOqT~BP^HD43o9ggg;fuu8LS-Z zyqKbbPE9vC3Xf@8Eoi#De9u$j)H~wPEo?glR8kzeZk`o$P@6TTDfN~%Ol!FsMEg9BxIy)7Sl&a5F0xwM(}z~`}QF7nK_AW;0+H{Y!w;CX8~ z^|ZeZmzLObT|d+gap?NIaNeo);{(8&CsWtIpJrkUwo20ISOYc3$m%l~AA>a(=usTXR-n@tWGA&snvN6+TX4SVtZ%PXnb+a*>m+c~ zl^@3pTU%&|b<55MNPHUE_eF)LG(r7l@L}I`&OxBj8unhz{p4lF^-cm+qQ+rCLxb@? zIMnLKSCOYcwfW}*ei(>Sop_m=?VV3=;I$&%!GsnwW)JW}u|lzeeL8vw!#;&BB~3By2LvTVz|HI8UZy<> z8Ozu|1m{axlgChcs@(0#oMek#`tgpBb!fJB5ROu8axxdv@nU=%e_B6arJodF(_LT0 zeW>aaAac{>w!OZi-gYvlxIlW6Vdy4j1F~Obk_{aDqJmHeU0)MM)9cy=+0<}6Vf3@0rWh(~cQ0e;? z7DEXej$byd)GsIWyrXwbwXAM!J9z_p^24n<5=h+|9ZP(D3U9Pc8{g4E@ zGaR`0<3(TE<9qWn`EIKZdTh%ed8zBv`~Y<9X^fiq)#z1fZV38{ojOK(yB683y_@qV zbco&mPQr(JpHx_w#D(Xnn9|o@;lk&Aqi*er*gMm!8Gf>j8q=5TU+;?VWa;NPOy>jHpn`jdGnLT=$sv^-Vj! zn5+jNlTQKTVhbtuU~RemSqVgXyc z-}n5FUXYAPWH6{*z-s+6l`%<1|2v82D$%iw4eprz?wWah_FZzLjGzk3&DWEgvC>Ip z)EzRPM#}70n>%!~A-wD~10y5#1czIGbm`-D3X6u@I$qv!4go=5JWT)|nu)f=t7+s=9ziz+DHM#t zc7zXmPjXWDV=l^VH-|;mdHv3Jc)p{1?&@fo`uCazG`|kX&+pXM|3C&zF=^ zb$w@T8VsHunNJ`dfphL=uhJFU5Ld4nUQF%XJkRSlV~-J{Mtr;m%4rx|DPEg(vEu1z-n8t2e? z(6rjdcWsu_RM(Dc^_iyUxWi>cvCOWulG6?+Q_v{V329g;t|%}1m(4|K#GDD~J15?l zU70HOH$%9e8OW&@@DC_SnbCvF*_0QgJjncOQ}|LNWtlxT>hjp`qFz_okt>N6@AhEq z-hSZ=eF8^f!sw5S|0H8gwQa?sv*q+XGi@lJcANg4$3{+zwg$fONrHv(dxlfY$koDP zjz)(cr4D$p!JQ^thy~nVF5|k%LO1jq-s%Poa|`tsH_!VJgt}Bekq!itj9!rnQ93Zw zwe_Va+Qt=O5xqNcv1`xfX!OD2iYFwji`RhuuprdOz8)1|6weY_GQnUD~+g#M+5B=vPExZq3&`-sd4}g#3Kyh8L=z)T(3o@ zO+(rmbMPhdjVoA4r@W-sZNjgE)!b~MQj$_@{6zZu^2Iq%am}%#U9--7a>9XW$NXYp zmTwXpT)b(7bf>LN!W1Gmfxw8YZgFZ=cm_hSv>MoPJYn`j!AC|eol6MTcHQNh)xGk$ zs#81z&;0}qlloTcfTwk~RgrDP%)IUk=S62TnYK@cHytPga}f&xU)XX>W9HN~OzM3ivw2$zn6JFPKl4r3HU-yQl($=SI02#jk0!9ugYo8 zso(&4n5o^cuULAC<7T3(1Y?AtMa)o-S48|40$hJ{ED7OR-Y@H1$z8ATm07;a<*pAY1Z|wZIL!{{wF=GPUc2kq z_O1paMerm6g{kt-zTKtl!dmi9inLcc0>@df0nO|DCeI@}KwGD};D#2&6n@MY-~fcf7AHR)XW~;`-VCM} zQxs_PZ*Ioxm7C<;nnkOq5~2ZU_5z#WMHX43aMyhOY+j6BPpp1}wR0X6*V{4T6+Md8 zB+w{13zw?0nzVXIYBPTFl2w;;?6i>56?(Qa`a#V(cMcL@a<_fx4<-Q1D!Qr9wclbM z&B52U*}Mr4o8^@j1I4LmlPT6p6S^NrjSS1o^4@?X@)TU=s>`(nf51EdCY#*XYIP30 zU|%hg2m&cl2Uf&QXKlydU+k(C&ZA)+Bo$SByinMW@h;xugZu{fGhk?ld=TJy zd#Gf}pHxx`!&WY{^au-0Z7WoYJgdRB3||NW zPtB$K%=hRvrtZb9ecXUU%FJ?xkKH>h(Dh1l-E!a=B&#js z=QP*X2SDfwcBkS#i5!F3ch8n-J7DVm`i@Snf?eLKj6^XZrSE2GfAu7<-I-xx0JfS< z7CR^=!{p5!cm6o(9l2=p3G^@B_)2l@T&9>b^qbBEvq;XZtia&dFbWWT;-Q`D(Eiqp`~GQ$SeH#uzSz zZ_x|}tu#|^g&h5*%#J<3*j7lV zx(C-`#eDQT)N<)r`UQQg0ZM|34E*`!$NKe1$Va~gB8F#@3Mp@zkMgP_nQvOq6}P1v zxwjdFO1z@SR^}Kk^*^&>DpL6xl&SCkz^hk#fv~!P+e5le!~{81o$iqtkKTlPfyIV# zp3P)`Un};!dc=&tecwr`T*&>~di(X6t8i|q%PpS0@>FlOZ6lQ~xBV+O!xRFcGQ6sw z!f&J-Jc7nB&{408UEyOF4c0@Rxu-L;t}$6LCZ#BT#XK-$~biHzB5fr!OUGNyIey5gHEz=yxZpj32Qu`-?i#MMp0m*x?3) zR}Aq)yH>Y$(XT>;1SAp&-^0|`Z>n0}TQoEQXuIfCdM<=s6{9)&!*z2ZKR4d8B5)D6 zZlCO-2%%>-2`=VGiM$)wCNML;X+wA1c$c}YE%WmY>Kq#_YU7F^hEVcL4ot3#_j}5k zCMJCW_Z4efFP*?rwKLBfPK7ln?#vN`&hBf?mxC<$h&5fOX zImUYcBrsmS8Fc#+bN&vbGF`={U!T%!V@`*0-JVi$`sHH%ee&i25?2OM%%bDiNp6Z7 z2UDowNR_j)Ow{?7xdF^lz-5@hcs;DDv2i8Spcb{`^@R7PkWC{jH4%~dmE__|esi2e zS>67g!<;{~RkKG3CAr3ExY4v?b$v^#9+km9^-IF=E$HNQT(y+axJWnX=}5}=(^J$< z9Qcxz$-Os4^KOn)S)oF~GRAaeXAE1Rln(%gfTNU}PJEN$`ZTrC%}7$!CZkrC6iq}U zA8nIfUfN01v!!uw^_O{u3{xIvr#(`aAzl-6q0}dwr^X#0*2&I@ZVMMvB#B9~V}GpP zFmf_{A zOQf46Uj~oK+=%n(Yhos$i{Nh-Le6tUA@j?=(1#olYgXvU0R=nro}Tu!M~HCdF+CX+ z8gB;z(vI*iFN*j0peZO^g0lLJczzC-isWfZiq$FG?fkUwSxpi^kf3CRK@CWjV8~d_+43yT z^*i(A`hN2@5`C3GXfz&q?9!iyu!mKu{sH(BTEx&xZjrUnqq%_?f;~|m{_CC8-1@X* zOqoudR-WxA$?imd41Dfx3E~!AEqGHZg@wX7q0!`ZoR$zRN;PPcwZG>GMVUBx1E-Tq zf+Z0Ny7Rps-B)Z@I+<#lsKP^cVvpN)9b<;?owP4ZrU5?1Hi;K@8u604LW|#LuwZ3L z5Opfp3KO34CqZeY zvCC59uDeq)-^5}V2Cxi}D z{kV67Fo;>_hDtKd_!N9kzs6<6O;MZ_4q4af*Z3XDD$n+Wb7hj<{TQ@C_ayQYY~3Lx zCm_Iasu*C6E`S%Euw&U2Zw z=W=O&hN;_yGIf>0ZPFA1)0coZjLviY?Re@&tqCLo6whzOY|-bj7wi5kubVSs2WNy1 zpTZlO)noOha1H9kI?mu@Db{Q!8ardD>sR&%|?& z_lk>*i!m72z%_sEHs9Bnyh@n)+(Vbx&;QQr0gx?0YcQiS<8wTT=h+?OFq5exZ!OI- zQxsM>^yQoI?oHp|)I=l9(opX`T!Lr}%==2kP=A8x&l?W;rD25C>a%qw8!NT920n2b zcqYB%?o0$vF~0GYkz=1khP}CF{6&;2!E<_3NRl1Wuv^f)aeCL)522NhqZ+Rvg63PB`S`F`G_tx5Sl zLQLalwP`~&sKTZZhDofft$t#6bc^}ajCR$(E~Q5B7dfweT{~}ujuk4(JPb9TCq**v z>QLuU<$o7@sbv<2us*9OVH4d-pLJ~5MT+a8t5x%{OYgNenlbr*7f|ZW^B5zG?kL6^Kq{8>kidOV_b=R8f z&?Ufw*q9BstsRI0Z0UBFMzl9&4?yHya?u%~50iY02mhV?nt5}k;Kh$@R}qMlSH_g~QGGYtRmnH` zBK?_<-Z!?Ay4E)?w*3h&AW4ev0+B8_1Wo$1+ppl4o_WNG9u;H@Ooh3IxlG#;8+{nz z*0eFV%5YttP(<)p?eHZZx3f8m!P=fmv9>|!M(uP-h0A!k7O+H;)Z_az<)TS`Gw@tq z)-+;xs_WN!T!{D^$(do^NrP6OyjzK@55Gz(K&*@MIXfR*i>(~Ql^JZmLr<+=3HLe2 zA_@q+oHGk4J@aWV2iIEuBx~T&?<0Z_JM5!0-j*N3l-O@7>Z5EVG8)0ZMyC&%KLTAUP69PIDCeh-$9VHqQ8*o1C#xONsW?pp;yF~SONCcA4Bx{&?b$0gN?GFQA8h?Pw}jQ@##5UgLy>9q05@iQ|}Vj;nm*Uwn7ZzQ-`l zyq}6Ob>$BnhMqYvQ1;>*XD7M~RB76`IIckNqiFoe50h&-l-p-;(_io@! zKe|NjDtLL|!%dkWDfZTh{_BIfBZgV5LXx!IzS|P7g;c${J&425hKVy1DBwzy&+q5~ z_%@po!huHOhq~=J*Aw*|yH?FHz~A@{Ybo#A^HWlUcjA*ROh@(xjpG#0%Ps?5W!SGw z8s5H}Zt49g$GjFxdWPd(6DOO^k-ojwO!CX4bDv9oop{=hDT@N)6?gU-5;#2v33cqW zw|M|cS8F{h!dtvochNXkyA_vrRewzy@*dzecOSivHep${_|`F(v3gvp7&E&WVP9(q zvv6f&X`g3c#XMBdM>Uo!Lm>{f@kf6~v{~-hjaThQ?voY&00c~*>n`|7kJT2PdG9tX zH%;*WESTe3d>i%zT>v|}p+vbE6QyXbu{XA!#0=X3V^dFJGs5))2EK=)#1y+e*Z%c! zt8Q@)wB5@%jPore`X_&d{}ntL!{!HCk5%!`zpD7I>%-m%(CqaIv}Ye{y}g*lZEqte zK3FOXGY+THp0`0Ne+#yri?QF{75hZY5xOymAK}7(6%$BKpBa96%#GRj;}ymDN5_}? zo~f?rej(CROYsJmZqwY`B%(-e<7GQ!iTKKrE1A%KCiwfr9}R6hQ=@o4Pc~N3%<~EE z*&0%W?hr21kOJc^)v^Giqciq@7N3Ic>U!;yH<>n7w(R}b9aH4vcY29jFOU%De54S~ z$va2gtnY^YB((8Igk$kGo`yJeD`76#2h7}IlhYvNap_)Z`#@iK+S|uo42MU9N^3vu zg*HZOsTHJ~$|R(q4ig;bo<$p2ai*J!?c}?9p22x2Tgd{4KOxz;bsG;;O6&%g23|#d z{{TKZFU!GSt#IBl@y@xV&8KJsM6%R;U8~!(&1Y{pTWfSx!%EIZLJwh+gHh&$*+;6#%ckGhT$v(yg5<#RE(Af692t+@T0N?Lzb^*il8MH5|}`vHuA z*vWh^L-nXqRr9U>Z!>EXjD5~3eLp{Fz1pZxmUnLHfV`3P&3RA4pC96MeXA(cEx=KqEa6x9x#)i?@;!6n4~@JltHW#JF9_Jr2E8NO=}~Eyo8*e; zg0W0pK*1Ty4h9M4wER2bpBHI=6EvR>c+*JIptskem+Ti>Z0{gh5N{~^z~>zZ<06iz zA2MtF{{Y|~jUWOU4#K3(p1i5gZ*D5(#>TwjA_GOvafaml->2zbQ>b{y#XceNW~-%q zKGSR`wb5>b7n+u-JET%d4t%K90VIs_dQ!*n1I4mvz9rM{G;4c*7I!khzwDfJ`d6LnzYq)` zv)#XrbfFaY7qLLLcFcd)+{y9;80UsKBiPh>?~FWM{{RUTJ`M2IhMRZd7t_-BR=T-z zmUc)$!Q>Yac8$k_|$ZjlJQTx5@-zy$Dz{grUlgyByrOpop@jDKAts?z%a{od8A9B6LsZ4kw`9@Z#94=$=kOf zx(`Z&;t#~XA9x!8^+VMj@dQx2sMs_9 z&#yJgc-O_2ei!jKhctO@E~S&im#t?5M#B*mUBtF}jCxi##IG1@aO>V5@Q;D4ZDiNH zMFZU|_sqfz8)e32aJ;rj01oE7_s5X@K(+Ya@Xt^2jn0>NzuPj&ZGET4@eGK_D9FU} zI5`KL((DNL_)=hYiIIU`gm$P}X0@}LYo(20h9c5PSOB07Nj{hdn&5sbYko1&{6fMX z9C$~=8d^dO530xJO?2PHc9&hMIQQ#Zg^!IqQSk@%aq&gPwxy<8-syjAwY|K8-_C~i z2;yw{V6fY~FvD|?N(PnBWYKjiEl){X>q*jES>qB%G-q;1IKu)uihBrTwK?3X{7a0a=td8TM9O=hm?! z@qV3o<4ANZK6JQ}OIMfe5iVd|j-a4^gyp%x%2|4s6(^{q)TVY$o)BgZzT3G7(CFYiHzvJFZhK-S*mJ5|*hU201?@iY=`K@l7 zO0v=}^*gjhl7F+53lf~L49svz=L$K+GsO2%__xGXFlll9qQgtOOPiapK1X=T2^@TU z$8q_H-~o!Ms%kg3UO3bj!@6?nqTwQY)Oo}yZ92xGR51rRjT0jrb?;94Hp+IpXw>k% z{947kU)qTm%3_m4A{BUDPtJu;LJeK=tz(-Qp>VFz4Wx2OC$Zh1aEx2R=eo{Lg{C}RcG*@>9Qwq4- zfK%q+{Xzc#Ju0@NC5D`l&3`OV`OSb?!h%jPPh+3yPm@#9t}Xn#d$=QYAg}LLf$Q&D zU!FHOCuu7^FYe2YCK$sX35Ua&5_fq^jRAD8P+_?4zzT>N72P3lmNj-u0t-l`le@XEcgB6|a>$j709Ps%Qn4MWpM%Mb`wRGJ(>^gOgv^SGo z$qKToPcNG;GI?g|dY)<%_g%#qH%j~2c{7gij*oQ@#;+D%+}TW)bLqD6TTD=hwC}cW5Cp#)2p<;_Vbmo|zz%o@w)L!&oJP=5( zufRH2gY-WJc!yKc{0Xc>V|1~>b!#=Gl1qCRP0I{~u>*PR4+jI+)$Ofs{{XSq8hy*j zB;=v;+on!_r;2)1>ZRWVuY`jHr;vH#(x}D4DS7A)-2odfng^e-*?ZGCwqu}2T_;1Eq z^}mn2J>nUo)OA@e^!-Om7g9q!G5}0vVLvOW$jKcK(!Pqlg6Pk9?<+bic#+W#I&h<( zYLaVNjCL`^7%@^-BAlQ-OA0M}>OO8%+Kc{|sp0-E@XnKO`!@Kf_*cVv#iHsL_VO*Q z+DO+jZMcW`jI2ulR35zGWPx6*u12fjIRY<|KeMLu125kqJREv`J*!$cm2C9}R%J$z z?<8Z*=<@1Xg@Zaa>Fh{HjX4Eda3cpNJXW0%4_U1{^hYiGI)(oL zg$tmazd!bBktZ041CD<>i{nOxt=ssc!+tyPzL?skp`;|X*VZcGTKd*Z7V`7_Yn+c< z*HPhb4q5nT!I9`Xe6vrd>CuaL42%oShTxV18Tty_yoH4JsI%>H8DakbOhG&heQ7J$ z&zVP=+Us85*Y&C9()?MpZ6@vwFTy@2yw#-)AhDB5cz(?1kKRbbU;~VhO>q7{@Qv?; zzh%99`%Xb7v8r6H{rCiId1SeVkZ=j;hqrpZR@yZ7kyDCVFZ*=mJZ0mi);tra_=3pTYDve>%CYR;icG}zcw44i|=qvE!Sb>Xi9{5e(A=hPwaz5TOa8(5l0c-9t- zxKYU)k81SfSmV>(1ghL^NW)}Eayif7YQ@=vO&T@RF2wnWZeu&R0Dg2RzE)!@w9=dP z>bo9a;@QT(@kin2xu!vQY<$g4o!;5wHo>7)P~m_pk~q#YRdgHckA|K;@e=F0W~C;R zt!fRa>DTsluW+tiKzL+S1(b{o9Adp#NdUFDVJaiZAu#~?4hiq?O^Qn~e;R$IS~XVQ z#m3>t`@W*Dk@F$Sx>k;xKC8&{KMlcq;xCC_Bk}Rk?zG)4v~TR)J#LW4G{Y?A_8wGZ zfHTf3L*QMM_TRH!iDPwVEHjG>pkZ|?$M>?32t1Czt#>m|YO6aN#0Ct)|Pwcg8EuX-BD&FeeWSSUd^COi? zv?tDfaCy%^tyTE?lG$DOH(Xsh=Hk~z*M-gKw}{Iih{jib#Tg(HI#-}Wab_aB5?;lU z#3Azsw-3*?NA`;qxr81Cy3PGdj`m*WT4x}|3?N0X5?a|6c zLc9kGq@iwEDrCNzduTRmlj7)gf;A7q0E?Zgd2K}d;9uS zxVtf`(P^zNy*mPM zGJMW)@{FFQtb9V#@9sV;d_1|gidmzzy}N6PQZmbK38de_A5YsY$yi*cpeU)jN9r(51dwrQY@ zv1wg8uHxJS&lTU?SznlL9y=R_Rd1M>i3{@%`1*cTV(J^TxQsom&|<$Z^3OcyBip4e zT^TZ+qc7|8M}&AI!k!?H;vJuZ?sWTs;cp7+*BZs8%b^iWWdOklCxT;ll%J>4tbAJV zW|lrQP2!&t_$KefnkJ#9#RDyh$t1QDpy2FA1(Amt^%d=w?Kskrq)Amn4WnTpv7X+w zJ-3>TGZ7+XLV1pa{{YK8;QOAma_VdC5~=d?JmcUFjo~{l5?yM(8u)prcx-)?KyGhU z?i1JeiTS|7B2pQ!5I2=KMGmtj4`mi{KNo=BmLvqXwc<~aTk zJJ)w6qo%WOd+T)#5g>_|=N`Xb@u@$wbiX1XFYTDA8D^1sdLDSB_G3;;?C~Fpo(IzH z{xj+xEb%wPtG^cLn$4UshOt(8^!ei(Kh=g^%E|{lh_2({9)aO2ch)r@4}3Pf@QTd} zTPxp>E_<#Rk|W&3R1i9xoY!@4sA*4cvOP-LMqqrg=Bdv-f&t0@0P9toYppQu0`}J* zbvDXAR>n5<`u!=;v0@Q z#&R)SSBX3?b9L|@e+&3~;I6A{;w86;YC4tek{6M~ubA;mxulVpgU|p5Nw2DH!~u{Q zqg5ninFdt<0D7MM2(jfM{zk~d+8BC)(-FOEEI@sq$B%pMHYw7nNe)Z}SSpo%9}Da**J$6|B? zCnS?z{{SV1qHZx1Hw;S<0Eqr)+Lq%_)`Ji<`-Z^-K1+PZ)2AQ(YF9%^pFZAcTE>C# zyTux&z3{$o8Oy9*E9m-y#SGEG2wkEQ(jVObahwl)Ro{a?5%ER8!rva~n*Nm@o2qM< z+K-U2uGmSIJ*%*P&ph-#mF{;IdZIHk>32$1hUPH0TyzK4t6a>M_l@?1Xjxq3NWXVJ zxv9G!HiysFx<`%tMf)?{{61d|UTE5;m8aX=2D!LJhf-8;og-ypKxJOwXLV)x_dxM? zio6%3XkHxnPCPwtr|LGdU51w2NXm>e2`^|oCI*psO_!N=)E>=zHSu(M^1vJgf-ZZTZmx#8_XZCc}2wVK^!8f#fv zi3ckqvle1N>B4*PYp1u5%!$Rc`;O7j=O;eY^QUTU5oEQNOg}k^vU8kZAGxcraL=P0B+<31E0#Vwe3Gpo8o?xtBVrWw%T0q%EXQxQYXcm7tXpwlI^ULfgw$rSmlKOkA^_N)|tgy7I<(^%Ihzq%i9f$_K z15miOOU=(PPMO{@gVP+Eqj7(vtmWj?Eo0o^XUtGKV;y?cBGWmWT~>JJhS7WgFehV7-p&^H9`c zj@oTX;a)3rA&gO~1VG23Y$+$UPaP{t8>dK`Rc|TB%o#^Lhi<>)PSmv>A!jUaEn^X& z%88V)IL33IT3IeFR@yXE5m_JPV*8nZ9S3vG3R<>Wz1x4O6`^@Rc9w|JmNp{H6c^ literal 0 HcmV?d00001 diff --git a/src/modules/imageresizer/tests/TestMetadataIssue1928_NoMetadata.jpg b/src/modules/imageresizer/tests/TestMetadataIssue1928_NoMetadata.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4b85fb4f4e3e81248c1fea7e7c9fef4b66fcaced GIT binary patch literal 19519 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 nfzLVV%@u1(OR29_*3rXcAd|_?>~c + /// 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); + } + } + ///

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - @@ -235,11 +256,6 @@ - - - - - diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml.cs index ea85cd2c8..f81034dd4 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml.cs @@ -19,5 +19,10 @@ namespace Microsoft.PowerToys.Settings.UI.Views ViewModel = new FancyZonesViewModel(settingsUtils, SettingsRepository.GetInstance(settingsUtils), SettingsRepository.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage); DataContext = ViewModel; } + + private void OpenColorsSettings_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e) + { + Helpers.StartProcessHelper.Start(Helpers.StartProcessHelper.ColorsSettings); + } } } From 3a6dd45741c16b97c6e92758367bcfbcc0b3e261 Mon Sep 17 00:00:00 2001 From: Jaime Bernardo Date: Thu, 4 Nov 2021 16:53:36 +0000 Subject: [PATCH 30/64] [Telemetry] Add basic interaction events to FZ (#12793) * [Telemetry] Add basic interaction events to FZ Adds some basic interaction events to FancyZones, such as: - Starting the drag Window movement. - A newly created window snapping to a zone. - Using the Keyboard to snap or extend to a zone. * Update src/modules/fancyzones/FancyZonesLib/FancyZones.cpp Co-authored-by: Seraphima Zykova * Update trace.cpp * Rename EventSnapNewWindowIntoZone * Adjust event names according to PR feedback Co-authored-by: Seraphima Zykova --- .../fancyzones/FancyZonesLib/FancyZones.cpp | 40 +++++++++++-- .../fancyzones/FancyZonesLib/WorkArea.cpp | 3 +- .../fancyzones/FancyZonesLib/trace.cpp | 56 +++++++++++++++++-- src/modules/fancyzones/FancyZonesLib/trace.h | 5 +- 4 files changed, 92 insertions(+), 12 deletions(-) diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp index 39e5ad66a..284aa890b 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp @@ -330,6 +330,10 @@ void FancyZones::MoveWindowIntoZone(HWND window, winrt::com_ptr zoneW auto& fancyZonesData = FancyZonesDataInstance(); if (!fancyZonesData.IsAnotherWindowOfApplicationInstanceZoned(window, zoneWindow->UniqueId())) { + if (zoneWindow) + { + Trace::FancyZones::SnapNewWindowIntoZone(zoneWindow->ActiveZoneSet()); + } m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, zoneIndexSet, zoneWindow); fancyZonesData.UpdateProcessIdToHandleMap(window, zoneWindow->UniqueId()); } @@ -966,8 +970,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 zoneWindow = m_workAreaHandler.GetWorkArea(m_currentDesktopId, *currMonitorInfo); + if (m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */, zoneWindow)) { + Trace::FancyZones::KeyboardSnapWindowToZone(zoneWindow->ActiveZoneSet()); return true; } // We iterated through all zones in current monitor zone layout, move on to next one (or previous depending on direction). @@ -991,20 +997,30 @@ bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexce } else { + auto zoneWindow = 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 */, zoneWindow); if (!moved) { FancyZonesUtils::RestoreWindowOrigin(window); FancyZonesUtils::RestoreWindowSize(window); } - return true; + else + { + Trace::FancyZones::KeyboardSnapWindowToZone(zoneWindow->ActiveZoneSet()); + } + 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 */, zoneWindow); + if (moved) + { + Trace::FancyZones::KeyboardSnapWindowToZone(zoneWindow->ActiveZoneSet()); + } + return moved; } } @@ -1078,6 +1094,7 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept // Moving to another monitor succeeded const auto& [trueZoneIdx, zoneWindow] = zoneRectsInfo[chosenIdx]; m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }, zoneWindow); + Trace::FancyZones::KeyboardSnapWindowToZone(zoneWindow->ActiveZoneSet()); return true; } @@ -1122,6 +1139,7 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept // Moving to another monitor succeeded const auto& [trueZoneIdx, zoneWindow] = zoneRectsInfo[chosenIdx]; m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }, zoneWindow); + Trace::FancyZones::KeyboardSnapWindowToZone(zoneWindow->ActiveZoneSet()); return true; } else @@ -1157,11 +1175,21 @@ bool FancyZones::ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle // 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, zoneWindow); + if (result) + { + Trace::FancyZones::KeyboardSnapWindowToZone(zoneWindow->ActiveZoneSet()); + } + return result; } else { - return m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle, zoneWindow); + bool result = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle, zoneWindow); + if (result) + { + Trace::FancyZones::KeyboardSnapWindowToZone(zoneWindow->ActiveZoneSet()); + } + return result; } } diff --git a/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp b/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp index 160d380b6..995dcaeb0 100644 --- a/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp +++ b/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp @@ -234,6 +234,7 @@ IFACEMETHODIMP WorkArea::MoveSizeEnter(HWND window) noexcept m_highlightZone = {}; m_initialHighlightZone = {}; ShowZoneWindow(); + Trace::WorkArea::MoveOrResizeStarted(m_activeZoneSet); return S_OK; } @@ -299,7 +300,7 @@ IFACEMETHODIMP WorkArea::MoveSizeEnd(HWND window, POINT const& ptScreen) noexcep SaveWindowProcessToZoneIndex(window); } } - Trace::WorkArea::MoveSizeEnd(m_activeZoneSet); + Trace::WorkArea::MoveOrResizeEnd(m_activeZoneSet); HideZoneWindow(); m_windowMoveSize = nullptr; diff --git a/src/modules/fancyzones/FancyZonesLib/trace.cpp b/src/modules/fancyzones/FancyZonesLib/trace.cpp index 8cb3619bc..13e06c6d8 100644 --- a/src/modules/fancyzones/FancyZonesLib/trace.cpp +++ b/src/modules/fancyzones/FancyZonesLib/trace.cpp @@ -15,7 +15,10 @@ #define EventSettingsKey "FancyZones_Settings" #define EventDesktopChangedKey "FancyZones_VirtualDesktopChanged" #define EventZoneWindowKeyUpKey "FancyZones_ZoneWindowKeyUp" -#define EventMoveSizeEndKey "FancyZones_MoveSizeEnd" +#define EventSnapNewWindowIntoZone "FancyZones_SnapNewWindowIntoZone" +#define EventKeyboardSnapWindowToZone "FancyZones_KeyboardSnapWindowToZone" +#define EventMoveOrResizeStartedKey "FancyZones_MoveOrResizeStarted" +#define EventMoveOrResizeEndedKey "FancyZones_MoveOrResizeEnded" #define EventCycleActiveZoneSetKey "FancyZones_CycleActiveZoneSet" #define EventQuickLayoutSwitchKey "FancyZones_QuickLayoutSwitch" @@ -78,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) @@ -97,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); @@ -247,6 +256,32 @@ 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()) @@ -316,12 +351,25 @@ void Trace::WorkArea::KeyUp(WPARAM wParam) noexcept 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; }; }; From 159629372d0df0b9447b8d8861ceffcbe7b29e17 Mon Sep 17 00:00:00 2001 From: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com> Date: Fri, 5 Nov 2021 16:44:39 +0100 Subject: [PATCH 31/64] Unify processes list in BugReportTool source (#14269) --- .../BugReportTool/BugReportTool.vcxproj | 1 + .../BugReportTool.vcxproj.filters | 1 + .../BugReportTool/EventViewer.cpp | 16 ++------------- .../BugReportTool/ProcessesList.cpp | 20 +++++++++++++++++++ .../BugReportTool/RegistryUtils.cpp | 18 ++++------------- 5 files changed, 28 insertions(+), 28 deletions(-) create mode 100644 tools/BugReportTool/BugReportTool/ProcessesList.cpp diff --git a/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj b/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj index 476c1116a..75be4075e 100644 --- a/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj +++ b/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj @@ -39,6 +39,7 @@ + diff --git a/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj.filters b/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj.filters index 64d306e74..d574367de 100644 --- a/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj.filters +++ b/tools/BugReportTool/BugReportTool/BugReportTool.vcxproj.filters @@ -14,6 +14,7 @@ + diff --git a/tools/BugReportTool/BugReportTool/EventViewer.cpp b/tools/BugReportTool/BugReportTool/EventViewer.cpp index 2e9e69f22..bc4e99dbb 100644 --- a/tools/BugReportTool/BugReportTool/EventViewer.cpp +++ b/tools/BugReportTool/BugReportTool/EventViewer.cpp @@ -10,22 +10,10 @@ #include "XmlDocumentEx.h" +extern std::vector processes; + namespace { - std::vector processes = - { - L"PowerToys.exe", - L"PowerToys.Settings.exe", - L"ColorPickerUI.exe", - L"PowerToys.Awake.exe", - L"FancyZonesEditor.exe", - L"PowerToys.FancyZones.exe", - L"PowerToys.KeyboardManagerEngine.exe", - L"PowerToys.KeyboardManagerEditor.exe", - L"PowerLauncher.exe", - L"PowerToys.ShortcutGuide.exe" - }; - // Batch size for number of events queried at once constexpr int BATCH_SIZE = 50; diff --git a/tools/BugReportTool/BugReportTool/ProcessesList.cpp b/tools/BugReportTool/BugReportTool/ProcessesList.cpp new file mode 100644 index 000000000..2c3975b54 --- /dev/null +++ b/tools/BugReportTool/BugReportTool/ProcessesList.cpp @@ -0,0 +1,20 @@ +#include +#include + +std::vector processes = +{ + L"PowerToys.exe", + L"PowerToys.Settings.exe", + L"ColorPickerUI.exe", + L"PowerToys.Awake.exe", + L"FancyZonesEditor.exe", + L"PowerToys.FancyZones.exe", + L"PowerToys.KeyboardManagerEngine.exe", + L"PowerToys.KeyboardManagerEditor.exe", + L"PowerLauncher.exe", + L"PowerToys.ShortcutGuide.exe", + L"PowerRename.exe", + L"ImageResizer.exe", + L"PowerToys.Update.exe", + L"PowerToys.ActionRunner.exe" +}; diff --git a/tools/BugReportTool/BugReportTool/RegistryUtils.cpp b/tools/BugReportTool/BugReportTool/RegistryUtils.cpp index b205bb0ab..58eb5914e 100644 --- a/tools/BugReportTool/BugReportTool/RegistryUtils.cpp +++ b/tools/BugReportTool/BugReportTool/RegistryUtils.cpp @@ -4,6 +4,8 @@ using namespace std; +extern std::vector processes; + namespace { vector> registryKeys = { @@ -163,20 +165,8 @@ namespace void ReportCompatibilityTab(HKEY key, wofstream& report) { - vector apps - { - L"PowerToys.exe", - L"ColorPickerUI.exe", - L"FancyZonesEditor.exe", - L"PowerToys.FancyZones.exe", - L"PowerToys.KeyboardManagerEngine.exe", - L"PowerToys.KeyboardManagerEditor.exe", - L"PowerLauncher.exe", - L"PowerToys.ShortcutGuide.exe" - }; - map flags; - for (auto app : apps) + for (auto app : processes) { flags[app] = L""; } @@ -190,7 +180,7 @@ void ReportCompatibilityTab(HKEY key, wofstream& report) auto values = QueryValues(outKey); for (auto value : values) { - for (auto app : apps) + for (auto app : processes) { if (value.first.find(app) != wstring::npos) { From 019c05c8e4a4f2363d620e3d57f1575ab507dd8e Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Fri, 5 Nov 2021 19:30:30 +0100 Subject: [PATCH 32/64] [Shortcut Guide] UI tweaks (#14215) * New SVGs * Updated tooltips * Tweaks * Portrait mode updates Co-authored-by: Laute --- .../ShortcutGuide/overlay_window.cpp | 37 +- .../ShortcutGuide/ShortcutGuide/svgs/0.svg | 41 +- .../ShortcutGuide/ShortcutGuide/svgs/1.svg | 43 +- .../ShortcutGuide/ShortcutGuide/svgs/2.svg | 41 +- .../ShortcutGuide/ShortcutGuide/svgs/3.svg | 41 +- .../ShortcutGuide/ShortcutGuide/svgs/4.svg | 43 +- .../ShortcutGuide/ShortcutGuide/svgs/5.svg | 41 +- .../ShortcutGuide/ShortcutGuide/svgs/6.svg | 41 +- .../ShortcutGuide/ShortcutGuide/svgs/7.svg | 41 +- .../ShortcutGuide/ShortcutGuide/svgs/8.svg | 41 +- .../ShortcutGuide/ShortcutGuide/svgs/9.svg | 41 +- .../ShortcutGuide/svgs/no_active_window.svg | 20 +- .../ShortcutGuide/svgs/overlay.svg | 407 +++++++++--------- .../ShortcutGuide/svgs/overlay_portrait.svg | 402 +++++++++-------- 14 files changed, 606 insertions(+), 674 deletions(-) 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 From c9dca6802ef1441eb3a0c75bd92650631adf0922 Mon Sep 17 00:00:00 2001 From: Jaime Bernardo Date: Mon, 8 Nov 2021 11:36:38 +0000 Subject: [PATCH 33/64] [FindMyMouse]Account for low double click settings (#14291) --- src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp index 1726014ae..6f9e0ecc2 100644 --- a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp @@ -55,6 +55,7 @@ protected: POINT m_sonarPos = ptNowhere; // 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; static constexpr int SonarRadius = 100; @@ -312,9 +313,10 @@ void SuperSonar::OnSonarKeyboardInput(RAWINPUT const& input) auto now = GetTickCount(); auto doubleClickInterval = now - m_lastKeyTime; POINT ptCursor{}; + auto doubleClickTimeSetting = GetDoubleClickTime(); if (GetCursorPos(&ptCursor) && - doubleClickInterval >= MIN_DOUBLE_CLICK_TIME && - doubleClickInterval <= GetDoubleClickTime() && + doubleClickInterval >= min(MIN_DOUBLE_CLICK_TIME, doubleClickTimeSetting / 5) && + doubleClickInterval <= doubleClickTimeSetting && IsEqual(m_lastKeyPos, ptCursor)) { m_sonarState = SonarState::ControlDown2; From 079a3b49def1e35049d9eed0e59fdb20a115ca23 Mon Sep 17 00:00:00 2001 From: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com> Date: Mon, 8 Nov 2021 13:02:56 +0100 Subject: [PATCH 34/64] Add logging for PowerRename (#14249) * Add logging for PowerRename Move call tracer to common/utils/logger Add logging to both PowerRename dll and PowerRenameUIHost Add PowerRename to BugReportTool event viewer collection * Log more errors and exceptions --- .../logger/call_tracer.cpp} | 4 +- .../logger/call_tracer.h} | 4 +- src/common/logger/logger.vcxproj | 2 + src/common/logger/logger.vcxproj.filters | 6 + src/common/logger/logger_settings.h | 1 + .../fancyzones/FancyZonesLib/FancyZones.cpp | 2 +- .../FancyZonesLib/FancyZonesData.cpp | 2 +- .../FancyZonesLib/FancyZonesLib.vcxproj | 2 - .../FancyZonesLib.vcxproj.filters | 6 - .../FancyZonesLib/OnThreadExecutor.cpp | 3 +- .../fancyzones/FancyZonesLib/WorkArea.cpp | 2 +- .../FancyZonesLib/ZoneWindowDrawing.cpp | 2 +- .../PowerRenameUIHost/PowerRenameUIHost.cpp | 171 ++++++++++++++---- .../PowerRenameUIHost.vcxproj | 10 +- .../powerrename/dll/PowerRenameExt.vcxproj | 4 + src/modules/powerrename/dll/dllmain.cpp | 9 +- 16 files changed, 177 insertions(+), 53 deletions(-) rename src/{modules/fancyzones/FancyZonesLib/CallTracer.cpp => common/logger/call_tracer.cpp} (96%) rename src/{modules/fancyzones/FancyZonesLib/CallTracer.h => common/logger/call_tracer.h} (82%) 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.vcxproj b/src/common/logger/logger.vcxproj index e8417dd42..7dde0c7c8 100644 --- a/src/common/logger/logger.vcxproj +++ b/src/common/logger/logger.vcxproj @@ -31,12 +31,14 @@ + + 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.h b/src/common/logger/logger_settings.h index 35b398792..cada3f537 100644 --- a/src/common/logger/logger_settings.h +++ b/src/common/logger/logger_settings.h @@ -24,6 +24,7 @@ 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 powerRenameLoggerName = "powerrename"; inline const static int retention = 30; std::wstring logLevel; LogSettings(); diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp index 284aa890b..246fb1f60 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,7 +26,6 @@ #include "VirtualDesktop.h" #include "MonitorWorkAreaHandler.h" #include "util.h" -#include "CallTracer.h" #include #include 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/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/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/WorkArea.cpp b/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp index 995dcaeb0..29fefa989 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 diff --git a/src/modules/fancyzones/FancyZonesLib/ZoneWindowDrawing.cpp b/src/modules/fancyzones/FancyZonesLib/ZoneWindowDrawing.cpp index f23193843..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 diff --git a/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.cpp b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.cpp index 214e7445a..9f1b94317 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"path"); - - 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) }; @@ -99,12 +126,21 @@ 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(); + SetHandlers(); + ReadSettings(); + } + catch (std::exception e) + { + Logger::error("Exception thrown during explorer items population: {}", std::string{ e.what() }); + } m_mainUserControl.UIUpdatesItem().ButtonRenameEnabled(false); InitAutoComplete(); @@ -143,11 +179,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); } @@ -155,6 +195,8 @@ HRESULT AppWindow::CreateShellItemArrayFromPaths( std::vector files, IShellItemArray** shellItemArray) { + _TRACER_; + *shellItemArray = nullptr; PIDLIST_ABSOLUTE* itemList = nullptr; itemList = new (std::nothrow) PIDLIST_ABSOLUTE[files.size()]; @@ -174,14 +216,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; } @@ -191,8 +240,11 @@ HRESULT AppWindow::CreateShellItemArrayFromPaths( void AppWindow::PopulateExplorerItems() { + _TRACER_; + UINT count = 0; m_prManager->GetVisibleItemCount(&count); + Logger::debug(L"Number of visible items: {}", count); UINT currDepth = 0; std::stack parents{}; @@ -245,6 +297,8 @@ void AppWindow::PopulateExplorerItems() HRESULT AppWindow::InitAutoComplete() { + _TRACER_; + HRESULT hr = S_OK; if (CSettingsInstance().GetMRUEnabled()) { @@ -281,6 +335,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) @@ -303,6 +359,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))) @@ -359,6 +418,8 @@ void AppWindow::ValidateFlags(PowerRenameFlags flag) void AppWindow::UpdateFlag(PowerRenameFlags flag, UpdateFlagCommand command) { + _TRACER_; + DWORD flags{}; m_prManager->GetFlags(&flags); @@ -371,6 +432,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) { @@ -380,6 +443,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") @@ -538,14 +603,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(); @@ -562,12 +634,16 @@ void AppWindow::ToggleAll() void AppWindow::SwitchView() { + _TRACER_; + m_prManager->SwitchFilter(0); PopulateExplorerItems(); } void AppWindow::Rename(bool closeWindow) { + _TRACER_; + if (m_prManager) { m_prManager->Rename(m_window, closeWindow); @@ -581,10 +657,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(); @@ -604,6 +685,8 @@ HRESULT AppWindow::ReadSettings() HRESULT AppWindow::WriteSettings() { + _TRACER_; + // Check if we should store our settings if (CSettingsInstance().GetPersistState()) { @@ -835,6 +918,8 @@ HRESULT AppWindow::OnRegExCanceled(_In_ DWORD threadId) HRESULT AppWindow::OnRegExCompleted(_In_ DWORD threadId) { + _TRACER_; + if (m_flagValidationInProgress) { m_flagValidationInProgress = false; @@ -860,6 +945,9 @@ HRESULT AppWindow::OnRenameStarted() HRESULT AppWindow::OnRenameCompleted(bool closeUIWindowAfterRenaming) { + _TRACER_; + + Logger::debug(L"Renaming completed. Close UI window - {}", closeUIWindowAfterRenaming); if (closeUIWindowAfterRenaming) { // Close the window @@ -878,11 +966,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; @@ -909,10 +1003,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.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/dll/PowerRenameExt.vcxproj b/src/modules/powerrename/dll/PowerRenameExt.vcxproj index 9005dbeee..ba221b69c 100644 --- a/src/modules/powerrename/dll/PowerRenameExt.vcxproj +++ b/src/modules/powerrename/dll/PowerRenameExt.vcxproj @@ -54,6 +54,9 @@ {caba8dfb-823b-4bf2-93ac-3f31984150d9} + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + {98537082-0fdb-40de-abd8-0dc5a4269bab} @@ -65,6 +68,7 @@ + diff --git a/src/modules/powerrename/dll/dllmain.cpp b/src/modules/powerrename/dll/dllmain.cpp index 985aa2399..d61570392 100644 --- a/src/modules/powerrename/dll/dllmain.cpp +++ b/src/modules/powerrename/dll/dllmain.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include "Generated Files/resource.h" #include @@ -174,6 +176,7 @@ public: // Enable the powertoy virtual void enable() { + Logger::info(L"PowerRename enabled"); m_enabled = true; save_settings(); } @@ -181,6 +184,7 @@ public: // Disable the powertoy virtual void disable() { + Logger::info(L"PowerRename disabled"); m_enabled = false; save_settings(); } @@ -261,9 +265,9 @@ public: Trace::SettingsChanged(); } - catch (std::exception) + catch (std::exception e) { - // Improper JSON. + Logger::error("Configuration parsing failed: {}", std::string{ e.what() }); } } @@ -297,6 +301,7 @@ public: init_settings(); app_name = GET_RESOURCE_STRING(IDS_POWERRENAME_APP_NAME); app_key = PowerRenameConstants::ModuleKey; + LoggerHelpers::init_logger(app_key, L"ModuleInterface", LogSettings::powerRenameLoggerName); } ~PowerRenameModule(){}; From 06882b4fbd34aa2c22cb135a6c5690b0f5e869f8 Mon Sep 17 00:00:00 2001 From: AnonymousWP <50231698+AnonymousWP@users.noreply.github.com> Date: Mon, 8 Nov 2021 13:46:56 +0100 Subject: [PATCH 35/64] [VCM]Fixed typo of string regarding feature (#14309) --- .../Strings/en-us/Resources.resw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw index 1395c4ea3..18d014052 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw @@ -125,7 +125,7 @@ Enable Video Conference Mute - Video Conference Mute is a quick and easy way to do an global "mute" of both your microphone and webcam. Disabling this module or closing PowerToys will unmute the microphone and camera. + Video Conference Mute is a quick and easy way to do a global "mute" of both your microphone and webcam. Disabling this module or closing PowerToys will unmute the microphone and camera. Mute camera & microphone From c2adab07160e724dbe3d22aac2de293096bad4c5 Mon Sep 17 00:00:00 2001 From: Jaime Bernardo Date: Mon, 8 Nov 2021 16:43:50 +0000 Subject: [PATCH 36/64] [ImageResizer]Sanitize target file name (#14040) * [ImageResizer] Sanitize target file name * Add a test * Avoid not recommended file names --- .../tests/Models/ResizeOperationTests.cs | 35 +++++++++++++++++++ .../imageresizer/ui/Models/ResizeOperation.cs | 33 ++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/modules/imageresizer/tests/Models/ResizeOperationTests.cs b/src/modules/imageresizer/tests/Models/ResizeOperationTests.cs index fa5f3882c..ea22ccd05 100644 --- a/src/modules/imageresizer/tests/Models/ResizeOperationTests.cs +++ b/src/modules/imageresizer/tests/Models/ResizeOperationTests.cs @@ -486,6 +486,41 @@ namespace ImageResizer.Models 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/ui/Models/ResizeOperation.cs b/src/modules/imageresizer/ui/Models/ResizeOperation.cs index 268457e49..954c5c09c 100644 --- a/src/modules/imageresizer/ui/Models/ResizeOperation.cs +++ b/src/modules/imageresizer/ui/Models/ResizeOperation.cs @@ -26,6 +26,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; @@ -205,16 +213,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)) From fb97ce040b83873665109b34aedaf6a1233132a0 Mon Sep 17 00:00:00 2001 From: Heiko <61519853+htcfreek@users.noreply.github.com> Date: Wed, 10 Nov 2021 17:38:03 +0100 Subject: [PATCH 37/64] [PT Run] Improvements on EnvironmentHelper and deletion of old env vars (#13363) * Improve log message * New method * changes made so far * code cleanup and new method * fix method name * final changes so far * Code cleanup and typo fixes * fix bugs and code cleanup * fix typo * rename Method * fix cast exception * fix type casting * exception handling for testing * Update path var name# * make collections case insensitive * fix spelling * add code to update names * improve comments * exception handling and logging * update comments * final changes * fix typo * Update comments * add summary to IsRunningAsSystem method * update var and fix typos * Update code * add log warning for protected vars * add comment * fix bugs * small change * Update log text * Skipp logging for USERNAME --- .github/actions/spell-check/expect.txt | 1 + .../PowerLauncher/Helper/EnvironmentHelper.cs | 237 ++++++++++++++---- .../launcher/PowerLauncher/MainWindow.xaml.cs | 5 +- 3 files changed, 189 insertions(+), 54 deletions(-) diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index d3d79443f..53068b753 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -733,6 +733,7 @@ Hardlines HARDWAREINPUT hashcode Hashset +Hashtable HASHVAL hbitmap hbmp 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); From 2c9b86d8739f7a639c40b8d95be202cccb776acc Mon Sep 17 00:00:00 2001 From: Heiko <61519853+htcfreek@users.noreply.github.com> Date: Wed, 10 Nov 2021 17:38:27 +0100 Subject: [PATCH 38/64] [PT Run] [Settings plugin] Add new settings (#13746) * Add entries for settings tools (#13741) * Add UAC entry (#13743) * fix typo * Add entries for environment vars (#13734) * fix typos * fixes * Improvements * fix resource strings * Fix merge conflicts * update system env vars command * fix json schema * Update settings * fix typo * add firstResultScore * fix typos --- .github/actions/spell-check/expect.txt | 1 + .../launcher/plugins/windowssettings.md | 18 +- .../Classes/WindowsSetting.cs | 6 + .../Helper/ResultHelper.cs | 16 +- .../Properties/Resources.Designer.cs | 168 +- .../Properties/Resources.resx | 69 +- .../WindowsSettings.json | 3501 +++++++++-------- .../WindowsSettings.schema.json | 124 +- 8 files changed, 2079 insertions(+), 1824 deletions(-) diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 53068b753..5efd5f1d1 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -787,6 +787,7 @@ hmonitor HOLDENTER HOLDESC homljgmgpmcbpjbnjpfijnhipfkiclkd +Homepage HOOKPROC hostname hotkeycontrol 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/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/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" + } } + } } + } } From d036740c8bd63db63c5e475f21796e83138bdacc Mon Sep 17 00:00:00 2001 From: yuyoyuppe Date: Tue, 9 Nov 2021 13:50:39 +0300 Subject: [PATCH 39/64] [BugReportTool] Fix notification activator registry path --- tools/BugReportTool/BugReportTool/RegistryUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/BugReportTool/BugReportTool/RegistryUtils.cpp b/tools/BugReportTool/BugReportTool/RegistryUtils.cpp index 58eb5914e..2bfc81cb3 100644 --- a/tools/BugReportTool/BugReportTool/RegistryUtils.cpp +++ b/tools/BugReportTool/BugReportTool/RegistryUtils.cpp @@ -9,7 +9,7 @@ extern std::vector processes; namespace { vector> registryKeys = { - { HKEY_CLASSES_ROOT, L"Software\\Classes\\CLSID\\{DD5CACDA-7C2E-4997-A62A-04A597B58F76}" }, + { HKEY_CLASSES_ROOT, L"CLSID\\{DD5CACDA-7C2E-4997-A62A-04A597B58F76}" }, { HKEY_CLASSES_ROOT, L"powertoys" }, { HKEY_CLASSES_ROOT, L"CLSID\\{ddee2b8a-6807-48a6-bb20-2338174ff779}" }, { HKEY_CLASSES_ROOT, L"CLSID\\{36B27788-A8BB-4698-A756-DF9F11F64F84}" }, From bef119b03b1124c2890bfeadf41d8a6d65df5019 Mon Sep 17 00:00:00 2001 From: yuyoyuppe Date: Tue, 9 Nov 2021 22:48:07 +0300 Subject: [PATCH 40/64] [Setup] Add logging for registry changes + add logger for powerpreview - cleanup logger project + remove SettingsAPI dependency --- .github/actions/spell-check/expect.txt | 3 +- installer/PowerToysSetup.sln | 12 +++ .../CustomAction.cpp | 34 +++++++- .../PowerToysSetupCustomActions.vcxproj | 6 ++ .../PowerToysSetupCustomActions/stdafx.h | 1 + src/common/logger/logger.cpp | 45 +++++++--- src/common/logger/logger.h | 1 + src/common/logger/logger.vcxproj | 3 - src/common/logger/logger_settings.cpp | 2 +- src/common/logger/logger_settings.h | 2 + src/common/utils/registry.h | 86 +++++++++++++++---- .../ShortcutGuide/ShortcutGuide.vcxproj | 3 + .../ShortcutGuideModuleInterface.vcxproj | 3 + .../fancyzones/FancyZones/FancyZones.vcxproj | 3 + .../FancyZonesModuleInterface.vcxproj | 3 + .../UnitTests/UnitTests.vcxproj | 3 + .../common/KeyboardManagerCommon.vcxproj | 3 + .../previewpane/powerpreview/powerpreview.cpp | 14 ++- 18 files changed, 187 insertions(+), 40 deletions(-) diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 5efd5f1d1..48d655461 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 @@ -655,7 +656,6 @@ finalizer findfast findstr FIXEDFILEINFO -FFAA FLASHZONES FLASHZONESONQUICKSWITCH flt @@ -2439,6 +2439,7 @@ workaround Workflow workspaces wostream +wostringstream wox wparam wpf 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/PowerToysSetupCustomActions/CustomAction.cpp b/installer/PowerToysSetupCustomActions/CustomAction.cpp index 972f1d2df..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."); + 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; 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/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 7dde0c7c8..09649a7ce 100644 --- a/src/common/logger/logger.vcxproj +++ b/src/common/logger/logger.vcxproj @@ -52,9 +52,6 @@ {7e1e3f13-2bd6-3f75-a6a7-873a2b55c60f} - - {6955446d-23f7-4023-9bb3-8657f904af99} - 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 cada3f537..1015ef9f5 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"; 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/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/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/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/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/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/keyboardmanager/common/KeyboardManagerCommon.vcxproj b/src/modules/keyboardmanager/common/KeyboardManagerCommon.vcxproj index 62cec9b6b..5820b4a7c 100644 --- a/src/modules/keyboardmanager/common/KeyboardManagerCommon.vcxproj +++ b/src/modules/keyboardmanager/common/KeyboardManagerCommon.vcxproj @@ -71,6 +71,9 @@ {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + {6955446d-23f7-4023-9bb3-8657f904af99} + diff --git a/src/modules/previewpane/powerpreview/powerpreview.cpp b/src/modules/previewpane/powerpreview/powerpreview.cpp index e7c8768ad..de6b2433d 100644 --- a/src/modules/previewpane/powerpreview/powerpreview.cpp +++ b/src/modules/previewpane/powerpreview/powerpreview.cpp @@ -11,12 +11,20 @@ #include #include +#include + // Constructor PowerPreviewModule::PowerPreviewModule() : m_moduleName(GET_RESOURCE_STRING(IDS_MODULE_NAME)), app_key(powerpreviewConstants::ModuleKey) { const std::wstring installationDir = get_module_folderpath(); + + std::filesystem::path logFilePath(PTSettingsHelper::get_module_save_folder_location(this->app_key)); + logFilePath.append(LogSettings::fileExplorerLogPath); + Logger::init(LogSettings::fileExplorerLoggerName, logFilePath.wstring(), PTSettingsHelper::get_log_settings_file_location()); + + Logger::info("Initializing PowerPreviewModule"); const bool installPerUser = false; m_fileExplorerModules.push_back({ .settingName = L"svg-previewer-toggle-setting", .settingDescription = GET_RESOURCE_STRING(IDS_PREVPANE_SVG_SETTINGS_DESCRIPTION), @@ -133,7 +141,10 @@ void PowerPreviewModule::disable() { for (auto& fileExplorerModule : m_fileExplorerModules) { - fileExplorerModule.registryChanges.unApply(); + if (!fileExplorerModule.registryChanges.unApply()) + { + Logger::error(L"Couldn't disable file explorer module {} during module disable() call", fileExplorerModule.settingName); + } } } else @@ -207,6 +218,7 @@ void PowerPreviewModule::apply_settings(const PowerToysSettings::PowerToyValues& } else { + Logger::error(L"Couldn't {} file explorer module {} during apply_settings", *toggle ? L"enable " : L"disable", fileExplorerModule.settingName); Trace::PowerPreviewSettingsUpdateFailed(fileExplorerModule.settingName.c_str(), !*toggle, *toggle, true); } } From 0aa213a31ddcb157b8ae6dc4c4d4161426b7cb98 Mon Sep 17 00:00:00 2001 From: Jacob Deuchert <43518397+JacobDeuchert@users.noreply.github.com> Date: Thu, 11 Nov 2021 15:06:59 +0100 Subject: [PATCH 41/64] [PT Run][VSCode] Add DevContainer workspaces to search results (#14313) * [PT Run] (VSCode Workspaces Plugin) Added devcontainers * [PT Run] (VSCode Workspaces Plugin) Added localization for dev container workspace type * [PT Run] (VSCode Workspaces Plugin) Streamlined result title for different workspace types --- .../Main.cs | 8 ++------ .../Properties/Resources.Designer.cs | 9 +++++++++ .../Properties/Resources.resx | 4 ++++ .../WorkspacesHelper/ParseVSCodeUri.cs | 13 ++++++++++++- .../WorkspacesHelper/VSCodeWorkspace.cs | 2 ++ 5 files changed, 29 insertions(+), 7 deletions(-) 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, } } From 9bb6d57515c52ac784d238a506b0ca465ac3deab Mon Sep 17 00:00:00 2001 From: FLOAT4 <17330977+FLOAT4@users.noreply.github.com> Date: Thu, 11 Nov 2021 19:23:22 +0200 Subject: [PATCH 42/64] [FancyZones] Cleanup (#14274) * [FancyZones] Remove obsolete code The field `m_zoneSets` is unused, and may be removed. * [FancyZones] Remove obsolete code The field `m_windows` is unused, and may be removed. * [FancyZones] Move adjustment of `RECT` to utils.cpp By doing so, also fix a bug where a non-`WS_SIZEBOX` window (a window that should not be resized) was resized (and not properly resized) if it was zoned into more than one zone. * [FancyZones] Complete rename `ZoneWindow` -> `WorkArea` Fix leftovers from "[FancyZones] Rename ZoneWindow -> WorkArea (#12223)" * [FancyZones] Refer to the move/size action as dragging * [FancyZones] Rename `ActiveZoneSet` -> `ZoneSet` There is only one zone set used by a work area. * [FancyZones] Rename `zoneUuid` -> `layoutUuid` The variable holds the UUID of the layout (not of a zone). Co-authored-by: float4 --- .../fancyzones/FancyZonesLib/FancyZones.cpp | 93 ++++++----- .../FancyZonesLib/FancyZonesDataTypes.cpp | 4 +- .../FancyZonesLib/MonitorWorkAreaHandler.cpp | 8 +- .../FancyZonesLib/WindowMoveHandler.cpp | 144 +++++++++--------- .../FancyZonesLib/WindowMoveHandler.h | 26 ++-- .../fancyzones/FancyZonesLib/WorkArea.cpp | 67 ++++---- .../fancyzones/FancyZonesLib/WorkArea.h | 2 +- src/modules/fancyzones/FancyZonesLib/Zone.cpp | 34 ----- src/modules/fancyzones/FancyZonesLib/Zone.h | 10 -- .../fancyzones/FancyZonesLib/ZoneSet.cpp | 20 +-- .../fancyzones/FancyZonesLib/trace.cpp | 4 +- src/modules/fancyzones/FancyZonesLib/util.cpp | 42 ++++- src/modules/fancyzones/FancyZonesLib/util.h | 5 +- .../UnitTests/WorkArea.Spec.cpp | 80 +++++----- .../UnitTests/ZoneSet.Spec.cpp | 14 +- 15 files changed, 272 insertions(+), 281 deletions(-) diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp index 246fb1f60..d16e97249 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp @@ -148,19 +148,19 @@ 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 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; @@ -168,7 +168,7 @@ private: 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; @@ -324,18 +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())) { - if (zoneWindow) + if (workArea) { - Trace::FancyZones::SnapNewWindowIntoZone(zoneWindow->ActiveZoneSet()); + Trace::FancyZones::SnapNewWindowIntoZone(workArea->ZoneSet()); } - m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, zoneIndexSet, zoneWindow); - fancyZonesData.UpdateProcessIdToHandleMap(window, zoneWindow->UniqueId()); + m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, zoneIndexSet, workArea); + fancyZonesData.UpdateProcessIdToHandleMap(window, workArea->UniqueId()); } } @@ -480,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; @@ -749,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)) { @@ -820,8 +820,7 @@ void FancyZones::OnDisplayChange(DisplayChangeType changeType) noexcept } } - UpdateZoneWindows(); - + UpdateWorkAreas(); if ((changeType == DisplayChangeType::WorkArea) || (changeType == DisplayChangeType::DisplayChange)) { @@ -832,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)) @@ -898,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 { @@ -923,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; }; @@ -938,10 +937,10 @@ 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, suppressMove); + m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, zoneIndexSet, workArea, suppressMove); } } } @@ -970,10 +969,10 @@ bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexce auto currMonitorInfo = std::find(std::begin(monitorInfo), std::end(monitorInfo), current); do { - auto zoneWindow = m_workAreaHandler.GetWorkArea(m_currentDesktopId, *currMonitorInfo); - if (m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */, zoneWindow)) + auto workArea = m_workAreaHandler.GetWorkArea(m_currentDesktopId, *currMonitorInfo); + if (m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */, workArea)) { - Trace::FancyZones::KeyboardSnapWindowToZone(zoneWindow->ActiveZoneSet()); + 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). @@ -997,11 +996,11 @@ bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexce } else { - auto zoneWindow = m_workAreaHandler.GetWorkArea(m_currentDesktopId, current); + 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 */, zoneWindow); + bool moved = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */, workArea); if (!moved) { FancyZonesUtils::RestoreWindowOrigin(window); @@ -1009,16 +1008,16 @@ bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexce } else { - Trace::FancyZones::KeyboardSnapWindowToZone(zoneWindow->ActiveZoneSet()); + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); } return moved; } else { - bool moved = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, true /* cycle through zones */, zoneWindow); + bool moved = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, true /* cycle through zones */, workArea); if (moved) { - Trace::FancyZones::KeyboardSnapWindowToZone(zoneWindow->ActiveZoneSet()); + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); } return moved; } @@ -1059,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(); @@ -1092,9 +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); - Trace::FancyZones::KeyboardSnapWindowToZone(zoneWindow->ActiveZoneSet()); + const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx]; + m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }, workArea); + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); return true; } @@ -1107,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(); @@ -1137,9 +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); - Trace::FancyZones::KeyboardSnapWindowToZone(zoneWindow->ActiveZoneSet()); + const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx]; + m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }, workArea); + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); return true; } else @@ -1170,24 +1169,24 @@ 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) { - bool result = m_windowMoveHandler.ExtendWindowByDirectionAndPosition(window, vkCode, zoneWindow); + bool result = m_windowMoveHandler.ExtendWindowByDirectionAndPosition(window, vkCode, workArea); if (result) { - Trace::FancyZones::KeyboardSnapWindowToZone(zoneWindow->ActiveZoneSet()); + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); } return result; } else { - bool result = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle, zoneWindow); + bool result = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle, workArea); if (result) { - Trace::FancyZones::KeyboardSnapWindowToZone(zoneWindow->ActiveZoneSet()); + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->ZoneSet()); } return result; } @@ -1276,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) { @@ -1295,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; } } @@ -1348,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); 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/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/WindowMoveHandler.cpp b/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.cpp index 87e12404c..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,51 +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 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) { - activeZoneSet->DismissWindow(window); + 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; } @@ -146,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()); } } } @@ -191,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; } @@ -214,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 @@ -231,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 @@ -251,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()); } } } @@ -269,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, bool suppressMove) 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, suppressMove); + 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 2677e9a24..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, bool suppressMove = false) 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 29fefa989..5d498f54e 100644 --- a/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp +++ b/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp @@ -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) @@ -164,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{}; @@ -234,7 +233,7 @@ IFACEMETHODIMP WorkArea::MoveSizeEnter(HWND window) noexcept m_highlightZone = {}; m_initialHighlightZone = {}; ShowZoneWindow(); - Trace::WorkArea::MoveOrResizeStarted(m_activeZoneSet); + Trace::WorkArea::MoveOrResizeStarted(m_zoneSet); return S_OK; } @@ -257,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 @@ -276,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; @@ -289,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::MoveOrResizeEnd(m_activeZoneSet); + Trace::WorkArea::MoveOrResizeEnd(m_zoneSet); HideZoneWindow(); m_windowMoveSize = nullptr; @@ -316,18 +315,18 @@ WorkArea::MoveWindowIntoZoneByIndex(HWND window, ZoneIndex index) noexcept IFACEMETHODIMP_(void) WorkArea::MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, bool suppressMove) noexcept { - if (m_activeZoneSet) + if (m_zoneSet) { - m_activeZoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, indexSet, suppressMove); + 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)) { @@ -342,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; @@ -356,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; @@ -370,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); } @@ -389,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()); } @@ -406,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(); } } @@ -430,16 +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_activeZoneSet) + if (m_zoneSet) { - m_activeZoneSet->CycleTabs(window, reverse); + m_zoneSet->CycleTabs(window, reverse); } } @@ -449,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); } } @@ -459,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(); } } @@ -555,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); } @@ -595,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 b0dbc47ea..ab834c352 100644 --- a/src/modules/fancyzones/FancyZonesLib/WorkArea.h +++ b/src/modules/fancyzones/FancyZonesLib/WorkArea.h @@ -103,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 */ 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/ZoneSet.cpp b/src/modules/fancyzones/FancyZonesLib/ZoneSet.cpp index c0995a44a..6d049da87 100644 --- a/src/modules/fancyzones/FancyZonesLib/ZoneSet.cpp +++ b/src/modules/fancyzones/FancyZonesLib/ZoneSet.cpp @@ -323,7 +323,7 @@ ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const Zo 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); @@ -351,7 +351,9 @@ ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const Zo if (!suppressMove) { SaveWindowSizeAndOrigin(window); - SizeWindowToRect(window, size); + + auto rect = AdjustRectForSizeWindowToRect(window, size, workAreaWindow); + SizeWindowToRect(window, rect); } StampWindow(window, bitmask); @@ -432,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()) @@ -453,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()) diff --git a/src/modules/fancyzones/FancyZonesLib/trace.cpp b/src/modules/fancyzones/FancyZonesLib/trace.cpp index 13e06c6d8..f1c0f209f 100644 --- a/src/modules/fancyzones/FancyZonesLib/trace.cpp +++ b/src/modules/fancyzones/FancyZonesLib/trace.cpp @@ -14,7 +14,7 @@ #define EventEditorLaunchKey "FancyZones_EditorLaunch" #define EventSettingsKey "FancyZones_Settings" #define EventDesktopChangedKey "FancyZones_VirtualDesktopChanged" -#define EventZoneWindowKeyUpKey "FancyZones_ZoneWindowKeyUp" +#define EventWorkAreaKeyUpKey "FancyZones_ZoneWindowKeyUp" #define EventSnapNewWindowIntoZone "FancyZones_SnapNewWindowIntoZone" #define EventKeyboardSnapWindowToZone "FancyZones_KeyboardSnapWindowToZone" #define EventMoveOrResizeStartedKey "FancyZones_MoveOrResizeStarted" @@ -345,7 +345,7 @@ void Trace::WorkArea::KeyUp(WPARAM wParam) noexcept { TraceLoggingWrite( g_hProvider, - EventZoneWindowKeyUpKey, + EventWorkAreaKeyUpKey, ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), TraceLoggingValue(wParam, KeyboardValueKey)); diff --git a/src/modules/fancyzones/FancyZonesLib/util.cpp b/src/modules/fancyzones/FancyZonesLib/util.cpp index 9d5d21385..dac0c1a4c 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{}; @@ -660,22 +692,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..5898586e6 100644 --- a/src/modules/fancyzones/FancyZonesLib/util.h +++ b/src/modules/fancyzones/FancyZonesLib/util.h @@ -189,6 +189,9 @@ 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; @@ -209,7 +212,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/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); From 7703991f4c41e3aa21443e31c391c7aa9fcf4cef Mon Sep 17 00:00:00 2001 From: Franky Chen Date: Tue, 16 Nov 2021 00:32:34 +0800 Subject: [PATCH 43/64] Fix spellcheck typos (#14426) --- .github/actions/spell-check/expect.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 48d655461..27a7e1d8c 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -787,7 +787,7 @@ hmonitor HOLDENTER HOLDESC homljgmgpmcbpjbnjpfijnhipfkiclkd -Homepage +homepage HOOKPROC hostname hotkeycontrol @@ -2334,7 +2334,7 @@ VTABLE Vtbl wav WBounds -Wca +wca wcautil WCE wcex From 11354a45ce79948d1a194d158c08409b224479cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?gyro=E6=B0=B8=E4=B8=8D=E6=8A=BD=E9=A3=8E?= <1247006353@qq.com> Date: Tue, 16 Nov 2021 00:33:12 +0800 Subject: [PATCH 44/64] [doc]Rename master -> main in the link (#14445) --- doc/devdocs/akaLinks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/devdocs/akaLinks.md b/doc/devdocs/akaLinks.md index 1e87d0e14..8dc69c338 100644 --- a/doc/devdocs/akaLinks.md +++ b/doc/devdocs/akaLinks.md @@ -4,7 +4,7 @@ |----------|----------| | 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 | From c7381cf1d5d4945a50b573118f52f0de960b05d8 Mon Sep 17 00:00:00 2001 From: Davide Giacometti Date: Tue, 16 Nov 2021 17:09:18 +0100 Subject: [PATCH 45/64] PT Run plugins directories alignment (#14462) --- installer/PowerToysSetup/Product.wxs | 80 +++++++++---------- ....PowerToys.Run.Plugin.UnitConverter.csproj | 4 +- .../Microsoft.Plugin.Folder.csproj | 4 +- .../Microsoft.Plugin.Indexer.csproj | 4 +- .../Microsoft.Plugin.Program.csproj | 4 +- .../Microsoft.Plugin.Shell.csproj | 4 +- .../Microsoft.Plugin.Uri.csproj | 4 +- .../Microsoft.Plugin.WindowWalker.csproj | 4 +- ...osoft.PowerToys.Run.Plugin.Registry.csproj | 4 +- ...owerToys.Run.Plugin.WindowsSettings.csproj | 4 +- 10 files changed, 58 insertions(+), 58 deletions(-) diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index 16d21dd23..2b1139984 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -316,27 +316,27 @@ - + - + - + - + - + - + @@ -344,11 +344,11 @@ - + - + @@ -361,7 +361,7 @@ - + @@ -1059,28 +1059,28 @@ - + - + - + - + - + - + - + @@ -1089,7 +1089,7 @@ - + - + - + - + - + - + - + - + - + - + - - + + - + - - + + @@ -1227,23 +1227,23 @@ - + - - + + - + - - + + @@ -1281,12 +1281,12 @@ - + - - + + 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/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/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.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.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 From 84d361e8a986c935af16f3e609f85a17f6acd166 Mon Sep 17 00:00:00 2001 From: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com> Date: Tue, 16 Nov 2021 17:10:43 +0100 Subject: [PATCH 46/64] [Settings]TabStop in General page only if element is opened (#14480) --- .../Views/GeneralPage.xaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/GeneralPage.xaml b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/GeneralPage.xaml index 8beb9b602..42a6a90bf 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/GeneralPage.xaml +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/GeneralPage.xaml @@ -61,14 +61,14 @@ @@ -104,7 +104,7 @@ @@ -129,7 +129,7 @@ @@ -196,7 +196,7 @@ From 2128b88571191a6228016ddc6aed979a0fc5a55a Mon Sep 17 00:00:00 2001 From: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com> Date: Tue, 16 Nov 2021 17:15:18 +0100 Subject: [PATCH 47/64] [PowerRename] Add PowerRename.exe metadata (#14465) * Add PowerRename.exe metadata * Remove unneeded stuff --- .../PowerRenameUIHost/PowerRenameUIHost.rc | 106 ++++++------------ 1 file changed, 35 insertions(+), 71 deletions(-) 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 From c934127d847d6bbd4f22ecb90cf34d17d9b7397d Mon Sep 17 00:00:00 2001 From: Franky Chen Date: Wed, 17 Nov 2021 06:06:45 +0800 Subject: [PATCH 48/64] [PT Run] Support for application URI (#14391) * [RUN] Add support for uri with scheme only * Fix typo * Add full support for application URI * Apply suggestions from code review and add tests * [PT Run] Add support for application uri * Update error message * Adapt the icon if the result is web URI * Update icons for application URI * Update icons for application URI (dark mode) * Update icon --- .../UriHelper/ExtendedUriParserTests.cs | 117 +++++++++++------- .../Microsoft.Plugin.Uri/Images/uri.dark.png | Bin 2580 -> 3825 bytes .../Microsoft.Plugin.Uri/Images/uri.light.png | Bin 2541 -> 2833 bytes .../Interfaces/IUriParser.cs | 2 +- .../Plugins/Microsoft.Plugin.Uri/Main.cs | 45 ++++--- .../Properties/Resources.Designer.cs | 13 +- .../Properties/Resources.resx | 7 +- .../UriHelper/ExtendedUriParser.cs | 27 +++- 8 files changed, 140 insertions(+), 71 deletions(-) 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 6c34ed1582e7aaddffda88aeab26666ee9cdb22e..e0807173096ae4f549cffd070df815d008a37609 100644 GIT binary patch delta 3818 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 literal 2580 zcmcIm32+lt7~ZyW$PtQ&NEyauTZePAyE!)5wSktTr5#M^lt>EW2%Fuv$x4!qyIYc$ z0_73}JX*?S$HAGxAcH8NA|r=IJ1U9_qlkbwzzhXpC^r;2^lg$JARyz6nQV6Vz3+Yh z_x}HT@8z}PqA@+YWpqOjq-SA)s|4Oj{p#8o{(4hyEP%JiBLx#x1Ti$~SIV;Ch5-oD zB}ejc>d)q&;e0nt)- zG3t~RfYL@1=Sk9p+8856TP!p=0yU8)k|3=FWx^?%B`qvvLz5p2MpFclEpg>1W5G8E z7SOZ^OAyg$)EG4zWyMcW48stli7=UPh``nIu*Svku$tM*-~uYIND)nv!>G>4`Q$Rq zfkCB-DTE?zv|%-wCYUfH#zhFqNa|B+1-ji2hlWCJXjRLb2BReP-j`VQlt%zj0#vz7 z;X&Rs5Y{qVgGB9{U9-X-&8dM5Po3EK)X4>{Vh$%>*?G+XUv+`>~7E?6yQ zoMR|}n?;7Skrc_8eTi8@wsDFCR}L5K$f~OnAfv$9MczhRaFMhDT;wba&hb10UG9VG zXp_%ELRIZ?opMlC+_C_(Ywp-y=yVn%hgvdbG+LPFq$!4`ag&k4P=RMfSqX8_ zXi|vt10oXk>$Zn7puhr2g*7fuE~N3`mE==ULgVgaIbL5B4vg2`00b=AEZrx~k2awM zZ8QKN=|fzSfT^;mML7lJ`e8}_)rG`^#H-x2hf6nG>~Y(6g64Q(VV}%tnIisricqc(>HIN-Elp{dvuyakDkYP=mZhbPH|VL zIHbcd&^ij9E&5~Ip$1>tjztiLl~UlL*Y#4_3Is{5D0Jm|0<+JRo~y1S(`VG}`D@6) zmSJ57&U@lC+ME^bOL}J$eP1lueW>qB!!|=?SnT@T1EuM+-bYr&UbTHWW#WdT*FRqT zR(j1gf92bC4W%Peo4&%Pgp;?*tr?5Hd0{|}A;*BGUD$bWXIgQ}uZzCfeeApZC2Rip z>;#{uIG>}lyh|@+WLB-ooY=YX<PQR?J=2@_5aFtNX9yW%YUYo@rEgN#>&hV7zo1=D?9_(G6 zGI^x2lX~(*|KW8rry_<1Z}*{>#iDm6 z2{XgMGSR$seWV!gO%Pfi!Cq&oYUeeV2) z9{$hHgRr#e`PpPwPZpDI`)elXSZ*8d cX!jOm^2De3=1Omi{=cm-ugLYm=qYpl25cjcQUCw| 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 bea54d005c0bf182cf81c3f6663c6d8e46fc48b2..26722696aae9763bf6cc0be357619f38d78e86cc 100644 GIT binary patch delta 2826 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 ZjBvHm5*~fDmYwB5Z&a2q)(R zgv-t_HV)c34uUSW>8gZwju7q5YN!$sqbN8-z`?OZh;l$8B(N@|3b;^}2ZNy?#{^l5 z!?7N_9wn-%enmvlwe=qNc|0Ym5|W~5LMxp*3G+?%aI}l#2*yg|mCnHx7(yfFhn5mTnT}J98*aTN8nZ z^fpzJhG|MjuMt#O5JoBa*D54$C0-Ne-d?(mbvlC}h>{pWXM~etX(H%A5p=lhPL2yg z!4YzFx&FUM&x9331UU@R2}|Pt-I+Tp_kZT$rW^21^T6~z4bqElaE3Gv)^vrDHx^${ zl?Px#FA{2@s%4TnNeK6(wFj5N6bms_^zEDLd+&$Tj6sv0G2)mqohBlCD6TV=g7mru zx>ljP#prflYUri=T7)vnl!`9BiNBR}noO25pSK_osXzb9h1hpg-tK3|Ufg-E@BTig z`+n8$+velz_xCAyu#hh}oE@EB6+0v&Y7v*0n{BZAt*Amv{ z^ik&Feg3M^_nI|u59P=-?WH--uPvm@^0Ol+XRz;N&92H?V=Gt@V~c9YgXaOlxV_esD{}c2LzhN#0Sh%UW+&nbW@%_QU z)^o2`e%fFO#OuzQe;oF8g4{T#Ut;&(3zhK;ht9THrX7h)z-yOm-ZZm#%&yqlQF+bT zx#9li@O?#n1~>7I75mHAuKMY~A!*)U%PzmSeBj;hc03B+&TST7ijCchw^Ex&FI)T3 zaiRV62K(hF*S#?d%-Z#`8DPJ(4Y@ROam7WLJCCnE*g1Cds%zi%1E!XhMGcFV=PjF6 z^}^K!!{1mu>&p+pXTu+Ctavdi*!Iney4BAtzEW~x`_p1#*UY1DK4;D?TYu<@raHZK zxP1P-PtFWpSUx|0LwRxyyKx&iEWdQw+4?C>SN6P8*1G8ayVoQ~Mfqdm z7mw_o^7S+L>!E8)zW992$4!Udw}_a&ZA#Jk?hO}Ped}VLaVMvG#U)pV zTwQrB9DTQ;e&2wO=a&r2Wb#r$;cYOBvCqbe)2ezF!T~vIg@!ZDF qL5|KVf8bT}c*~hRs5$yUvgOpvzmHp;Y;QLHkohJTd$&xgTJSg0tbX(W 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/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; } } From 5a4822f89ed28e18c86a9d5c44049cd5b0d29856 Mon Sep 17 00:00:00 2001 From: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com> Date: Wed, 17 Nov 2021 10:57:22 +0100 Subject: [PATCH 49/64] [PowerRename] Tweak UI and fix performance issues (#14365) * Init * Update MainWindow.xaml * Add identation * Remove template selector * Vertical UI * Update PowerRenameUILib.vcxproj * Revert "Vertical UI" This reverts commit d0b3d264fbd108c393de91f7ebfea48ac59fa920. * Revert "Update PowerRenameUILib.vcxproj" This reverts commit ba18503db29c95091df8b0d878cf6b6fd913d5ba. * Tweaks to margins * Updated tweaks * Update MainWindow.xaml * Wire counters * Improve perf: Constant O(1) find-item-by-id time instead of O(n) Co-authored-by: Laute --- .../PowerRenameUIHost/PowerRenameUIHost.cpp | 31 +- .../PowerRenameUILib/ExplorerItem.cpp | 37 +- .../PowerRenameUILib/ExplorerItem.h | 8 +- .../PowerRenameUILib/ExplorerItem.idl | 6 +- .../ExplorerItemTemplateSelector.cpp | 37 -- .../ExplorerItemTemplateSelector.h | 28 -- .../ExplorerItemTemplateSelector.idl | 11 - .../PowerRenameUILib/MainWindow.cpp | 60 +--- .../powerrename/PowerRenameUILib/MainWindow.h | 10 +- .../PowerRenameUILib/MainWindow.idl | 4 +- .../PowerRenameUILib/MainWindow.xaml | 317 +++++++++--------- .../PowerRenameUILib/PowerRenameUILib.vcxproj | 6 +- .../PowerRenameUILib/UIUpdates.cpp | 28 ++ .../powerrename/PowerRenameUILib/UIUpdates.h | 6 + .../powerrename/PowerRenameUILib/pch.h | 1 + 15 files changed, 243 insertions(+), 347 deletions(-) delete mode 100644 src/modules/powerrename/PowerRenameUILib/ExplorerItemTemplateSelector.cpp delete mode 100644 src/modules/powerrename/PowerRenameUILib/ExplorerItemTemplateSelector.h delete mode 100644 src/modules/powerrename/PowerRenameUILib/ExplorerItemTemplateSelector.idl diff --git a/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.cpp b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.cpp index 9f1b94317..f47cad755 100644 --- a/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.cpp +++ b/src/modules/powerrename/PowerRenameUIHost/PowerRenameUIHost.cpp @@ -134,6 +134,7 @@ bool AppWindow::OnCreate(HWND, LPCREATESTRUCT) noexcept try { PopulateExplorerItems(); + UpdateCounts(); SetHandlers(); ReadSettings(); } @@ -246,11 +247,6 @@ void AppWindow::PopulateExplorerItems() m_prManager->GetVisibleItemCount(&count); Logger::debug(L"Number of visible items: {}", count); - UINT currDepth = 0; - std::stack parents{}; - UINT prevId = 0; - parents.push(0); - for (UINT i = 0; i < count; ++i) { CComPtr renameItem; @@ -274,23 +270,8 @@ 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); } } } @@ -638,6 +619,7 @@ void AppWindow::SwitchView() m_prManager->SwitchFilter(0); PopulateExplorerItems(); + UpdateCounts(); } void AppWindow::Rename(bool closeWindow) @@ -851,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) @@ -878,7 +861,6 @@ HRESULT AppWindow::OnUpdate(_In_ IPowerRenameItem* renameItem) } } - UpdateCounts(); return S_OK; } @@ -935,6 +917,7 @@ HRESULT AppWindow::OnRegExCompleted(_In_ DWORD threadId) } } + UpdateCounts(); return S_OK; } 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 88336778b..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,20 +218,20 @@ Extension only - - - + + + - + - - - - + + + + @@ -260,29 +239,38 @@ - + - + + - + + + diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Helpers/NativeMethods.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Helpers/NativeMethods.cs index 8f7d374ba..a3750322a 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Helpers/NativeMethods.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Helpers/NativeMethods.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.InteropServices; +using System.Text; namespace Microsoft.PowerToys.Settings.UI.Helpers { @@ -11,6 +12,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers { private const int GWL_STYLE = -16; private const int WS_POPUP = 1 << 31; // 0x80000000 + internal const int SPI_GETDESKWALLPAPER = 0x0073; [DllImport("user32.dll")] internal static extern uint SendInput(uint nInputs, NativeKeyboardHelper.INPUT[] pInputs, int cbSize); @@ -38,6 +40,10 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers public static extern bool FreeLibrary(IntPtr hModule); #pragma warning restore CA1401 // P/Invokes should not be visible + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool SystemParametersInfo(int uiAction, int uiParam, StringBuilder pvParam, int fWinIni); + public static void SetPopupStyle(IntPtr hwnd) { _ = SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) | WS_POPUP); diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj index 19cb24073..33ad5c2df 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj @@ -99,6 +99,9 @@ ColorPickerButton.xaml + + FancyZonesPreviewControl.xaml + @@ -274,6 +277,7 @@ + @@ -321,6 +325,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Themes/Colors.xaml b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Themes/Colors.xaml index 1066f6d13..2ae414382 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Themes/Colors.xaml +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Themes/Colors.xaml @@ -9,6 +9,7 @@ #FF5fb2f2 + 1 @@ -18,6 +19,7 @@ #FF0063b1 + 1 @@ -26,6 +28,7 @@ + #FF5fb2f2 2 diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml index bacad57cb..f67564de2 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml @@ -68,7 +68,6 @@ - @@ -94,7 +93,7 @@ - + @@ -109,28 +108,16 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + @@ -171,7 +178,7 @@ - + @@ -179,13 +186,13 @@ - + - - + + From 0aaf00dc5e2a431812251d894020f446123c4e9f Mon Sep 17 00:00:00 2001 From: Clint Rutkas Date: Fri, 19 Nov 2021 09:43:45 -0800 Subject: [PATCH 55/64] Getting it sync'ed with settings but this needs to be localized / pulled from a common spot w/ settings (#14537) Co-authored-by: Clint Rutkas --- src/modules/awake/Awake/Core/TrayHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From 022dde4754d39600ce3e5a488bdf6ad7e20ff8d4 Mon Sep 17 00:00:00 2001 From: Clint Rutkas Date: Fri, 19 Nov 2021 14:08:14 -0800 Subject: [PATCH 56/64] #14020 and #12630 - Show color as a float and as a decimal value --- .../ColorPickerUI/Helpers/ColorHelper.cs | 8 ++++++ .../Helpers/ColorRepresentationHelper.cs | 25 +++++++++++++++++++ .../ViewModels/ColorEditorViewModel.cs | 12 +++++++++ .../Helpers/ColorRepresentationHelperTest.cs | 2 ++ .../Enumerations/ColorRepresentationType.cs | 12 +++++++++ .../ViewModels/ColorPickerViewModel.cs | 6 +++++ 6 files changed, 65 insertions(+) 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 8fafa440c..ece620088 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.RGBfloat => ColorToFloat(color), + ColorRepresentationType.Decimal => ColorToDecimal(color), // Fall-back value, when "_userSettings.CopiedColorRepresentation.Value" is incorrect _ => ColorToHex(color), @@ -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 precison = 2; + + return $"({Math.Round(red, precison)}f, {Math.Round(green, precison)}f, {Math.Round(blue, precison)}f, 1)"; + } + + /// + /// 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 87ed3edae..65cd33d61 100644 --- a/src/modules/colorPicker/ColorPickerUI/ViewModels/ColorEditorViewModel.cs +++ b/src/modules/colorPicker/ColorPickerUI/ViewModels/ColorEditorViewModel.cs @@ -216,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.RGBfloat.ToString(), + Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.RGBfloat); }, + }); + _allColorRepresentations.Add( + new ColorFormatModel() + { + FormatName = ColorRepresentationType.Decimal.ToString(), + Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.Decimal); }, + }); _userSettings.VisibleColorFormats.CollectionChanged += VisibleColorFormats_CollectionChanged; diff --git a/src/modules/colorPicker/UnitTest-ColorPickerUI/Helpers/ColorRepresentationHelperTest.cs b/src/modules/colorPicker/UnitTest-ColorPickerUI/Helpers/ColorRepresentationHelperTest.cs index 5b1f26f91..e8f32b2be 100644 --- a/src/modules/colorPicker/UnitTest-ColorPickerUI/Helpers/ColorRepresentationHelperTest.cs +++ b/src/modules/colorPicker/UnitTest-ColorPickerUI/Helpers/ColorRepresentationHelperTest.cs @@ -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.RGBfloat, "(0.00f, 0.00f, 0.00f, 1)")] + [DataRow(ColorRepresentationType.Decimal, "0")] public void GetStringRepresentationTest(ColorRepresentationType type, string expected) { diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Enumerations/ColorRepresentationType.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Enumerations/ColorRepresentationType.cs index 5b592f22a..b99ab7330 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Enumerations/ColorRepresentationType.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Enumerations/ColorRepresentationType.cs @@ -65,5 +65,17 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Enumerations /// Color presentation as CIEXYZ color space (X[0..95], Y[0..100], Z[0..109] /// CIEXYZ = 10, + + /// + /// Color presentation as RGB float (red[0..1], green[0..1], blue[0..1]) + /// + RGBfloat = 11, + + /// + /// Color presentation as integer decimal value 0-16777215 + /// +#pragma warning disable CA1720 // Identifier contains type name + Decimal = 12, +#pragma warning restore CA1720 // Identifier contains type name } } diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/ColorPickerViewModel.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/ColorPickerViewModel.cs index e69324ea4..92024daec 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/ColorPickerViewModel.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/ColorPickerViewModel.cs @@ -55,6 +55,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels { ColorRepresentationType.RGB, "RGB - rgb(100, 50, 75)" }, { ColorRepresentationType.CIELAB, "CIE LAB - CIELab(76, 21, 80)" }, { ColorRepresentationType.CIEXYZ, "CIE XYZ - xyz(56, 50, 7)" }, + { ColorRepresentationType.RGBfloat, "(1.0f, 0.7f, 0.00f)" }, + { ColorRepresentationType.Decimal, "16755200" }, }; GeneralSettingsConfig = settingsRepository.SettingsConfig; @@ -198,6 +200,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels var ncolFormatName = ColorRepresentationType.NCol.ToString(); var cielabFormatName = ColorRepresentationType.CIELAB.ToString(); var ciexyzFormatName = ColorRepresentationType.CIEXYZ.ToString(); + var rgbFloatFormatName = ColorRepresentationType.RGBfloat.ToString(); + var decimalFormatName = ColorRepresentationType.Decimal.ToString(); formatsUnordered.Add(new ColorFormatModel(hexFormatName, "ef68ff", visibleFormats.ContainsKey(hexFormatName) && visibleFormats[hexFormatName])); formatsUnordered.Add(new ColorFormatModel(rgbFormatName, "rgb(239, 104, 255)", visibleFormats.ContainsKey(rgbFormatName) && visibleFormats[rgbFormatName])); @@ -210,6 +214,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels formatsUnordered.Add(new ColorFormatModel(ncolFormatName, "R10, 50%, 75%", visibleFormats.ContainsKey(ncolFormatName) && visibleFormats[ncolFormatName])); formatsUnordered.Add(new ColorFormatModel(cielabFormatName, "CIELab(66, 72, -52)", visibleFormats.ContainsKey(cielabFormatName) && visibleFormats[cielabFormatName])); formatsUnordered.Add(new ColorFormatModel(ciexyzFormatName, "xyz(59, 35, 98)", visibleFormats.ContainsKey(ciexyzFormatName) && visibleFormats[ciexyzFormatName])); + formatsUnordered.Add(new ColorFormatModel(rgbFloatFormatName, "(0.94f, 0.41f, 1.00f, 1)", visibleFormats.ContainsKey(rgbFloatFormatName) && visibleFormats[rgbFloatFormatName])); + formatsUnordered.Add(new ColorFormatModel(decimalFormatName, "15689983", visibleFormats.ContainsKey(decimalFormatName) && visibleFormats[decimalFormatName])); foreach (var storedColorFormat in _colorPickerSettings.Properties.VisibleColorFormats) { From 0dae5d040207a74092193d9f119860eded0efd60 Mon Sep 17 00:00:00 2001 From: Clint Rutkas Date: Fri, 19 Nov 2021 14:11:32 -0800 Subject: [PATCH 57/64] Revert "#14020 and #12630 - Show color as a float and as a decimal value" This reverts commit 022dde4754d39600ce3e5a488bdf6ad7e20ff8d4. --- .../ColorPickerUI/Helpers/ColorHelper.cs | 8 ------ .../Helpers/ColorRepresentationHelper.cs | 25 ------------------- .../ViewModels/ColorEditorViewModel.cs | 12 --------- .../Helpers/ColorRepresentationHelperTest.cs | 2 -- .../Enumerations/ColorRepresentationType.cs | 12 --------- .../ViewModels/ColorPickerViewModel.cs | 6 ----- 6 files changed, 65 deletions(-) diff --git a/src/modules/colorPicker/ColorPickerUI/Helpers/ColorHelper.cs b/src/modules/colorPicker/ColorPickerUI/Helpers/ColorHelper.cs index 7015201b2..d242a9e56 100644 --- a/src/modules/colorPicker/ColorPickerUI/Helpers/ColorHelper.cs +++ b/src/modules/colorPicker/ColorPickerUI/Helpers/ColorHelper.cs @@ -44,14 +44,6 @@ 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 ece620088..8fafa440c 100644 --- a/src/modules/colorPicker/ColorPickerUI/Helpers/ColorRepresentationHelper.cs +++ b/src/modules/colorPicker/ColorPickerUI/Helpers/ColorRepresentationHelper.cs @@ -46,8 +46,6 @@ namespace ColorPicker.Helpers ColorRepresentationType.RGB => ColorToRGB(color), ColorRepresentationType.CIELAB => ColorToCIELAB(color), ColorRepresentationType.CIEXYZ => ColorToCIEXYZ(color), - ColorRepresentationType.RGBfloat => ColorToFloat(color), - ColorRepresentationType.Decimal => ColorToDecimal(color), // Fall-back value, when "_userSettings.CopiedColorRepresentation.Value" is incorrect _ => ColorToHex(color), @@ -101,29 +99,6 @@ 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 precison = 2; - - return $"({Math.Round(red, precison)}f, {Math.Round(green, precison)}f, {Math.Round(blue, precison)}f, 1)"; - } - - /// - /// 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 65cd33d61..87ed3edae 100644 --- a/src/modules/colorPicker/ColorPickerUI/ViewModels/ColorEditorViewModel.cs +++ b/src/modules/colorPicker/ColorPickerUI/ViewModels/ColorEditorViewModel.cs @@ -216,18 +216,6 @@ namespace ColorPicker.ViewModels FormatName = ColorRepresentationType.CIEXYZ.ToString(), Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.CIEXYZ); }, }); - _allColorRepresentations.Add( - new ColorFormatModel() - { - FormatName = ColorRepresentationType.RGBfloat.ToString(), - Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.RGBfloat); }, - }); - _allColorRepresentations.Add( - new ColorFormatModel() - { - FormatName = ColorRepresentationType.Decimal.ToString(), - Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.Decimal); }, - }); _userSettings.VisibleColorFormats.CollectionChanged += VisibleColorFormats_CollectionChanged; diff --git a/src/modules/colorPicker/UnitTest-ColorPickerUI/Helpers/ColorRepresentationHelperTest.cs b/src/modules/colorPicker/UnitTest-ColorPickerUI/Helpers/ColorRepresentationHelperTest.cs index e8f32b2be..5b1f26f91 100644 --- a/src/modules/colorPicker/UnitTest-ColorPickerUI/Helpers/ColorRepresentationHelperTest.cs +++ b/src/modules/colorPicker/UnitTest-ColorPickerUI/Helpers/ColorRepresentationHelperTest.cs @@ -24,8 +24,6 @@ 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.RGBfloat, "(0.00f, 0.00f, 0.00f, 1)")] - [DataRow(ColorRepresentationType.Decimal, "0")] public void GetStringRepresentationTest(ColorRepresentationType type, string expected) { diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Enumerations/ColorRepresentationType.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Enumerations/ColorRepresentationType.cs index b99ab7330..5b592f22a 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Enumerations/ColorRepresentationType.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Enumerations/ColorRepresentationType.cs @@ -65,17 +65,5 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Enumerations /// Color presentation as CIEXYZ color space (X[0..95], Y[0..100], Z[0..109] /// CIEXYZ = 10, - - /// - /// Color presentation as RGB float (red[0..1], green[0..1], blue[0..1]) - /// - RGBfloat = 11, - - /// - /// Color presentation as integer decimal value 0-16777215 - /// -#pragma warning disable CA1720 // Identifier contains type name - Decimal = 12, -#pragma warning restore CA1720 // Identifier contains type name } } diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/ColorPickerViewModel.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/ColorPickerViewModel.cs index 92024daec..e69324ea4 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/ColorPickerViewModel.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/ColorPickerViewModel.cs @@ -55,8 +55,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels { ColorRepresentationType.RGB, "RGB - rgb(100, 50, 75)" }, { ColorRepresentationType.CIELAB, "CIE LAB - CIELab(76, 21, 80)" }, { ColorRepresentationType.CIEXYZ, "CIE XYZ - xyz(56, 50, 7)" }, - { ColorRepresentationType.RGBfloat, "(1.0f, 0.7f, 0.00f)" }, - { ColorRepresentationType.Decimal, "16755200" }, }; GeneralSettingsConfig = settingsRepository.SettingsConfig; @@ -200,8 +198,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels var ncolFormatName = ColorRepresentationType.NCol.ToString(); var cielabFormatName = ColorRepresentationType.CIELAB.ToString(); var ciexyzFormatName = ColorRepresentationType.CIEXYZ.ToString(); - var rgbFloatFormatName = ColorRepresentationType.RGBfloat.ToString(); - var decimalFormatName = ColorRepresentationType.Decimal.ToString(); formatsUnordered.Add(new ColorFormatModel(hexFormatName, "ef68ff", visibleFormats.ContainsKey(hexFormatName) && visibleFormats[hexFormatName])); formatsUnordered.Add(new ColorFormatModel(rgbFormatName, "rgb(239, 104, 255)", visibleFormats.ContainsKey(rgbFormatName) && visibleFormats[rgbFormatName])); @@ -214,8 +210,6 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels formatsUnordered.Add(new ColorFormatModel(ncolFormatName, "R10, 50%, 75%", visibleFormats.ContainsKey(ncolFormatName) && visibleFormats[ncolFormatName])); formatsUnordered.Add(new ColorFormatModel(cielabFormatName, "CIELab(66, 72, -52)", visibleFormats.ContainsKey(cielabFormatName) && visibleFormats[cielabFormatName])); formatsUnordered.Add(new ColorFormatModel(ciexyzFormatName, "xyz(59, 35, 98)", visibleFormats.ContainsKey(ciexyzFormatName) && visibleFormats[ciexyzFormatName])); - formatsUnordered.Add(new ColorFormatModel(rgbFloatFormatName, "(0.94f, 0.41f, 1.00f, 1)", visibleFormats.ContainsKey(rgbFloatFormatName) && visibleFormats[rgbFloatFormatName])); - formatsUnordered.Add(new ColorFormatModel(decimalFormatName, "15689983", visibleFormats.ContainsKey(decimalFormatName) && visibleFormats[decimalFormatName])); foreach (var storedColorFormat in _colorPickerSettings.Properties.VisibleColorFormats) { From 2d5276f74289630a32926a67bc783ab859fc1f2b Mon Sep 17 00:00:00 2001 From: Jaime Bernardo Date: Mon, 22 Nov 2021 13:31:31 +0000 Subject: [PATCH 58/64] Mouse Utils - Mouse Highlighter (#14496) * New PowerToys template * Add CppWinRt to empty PowerToy * Add Settings reference to empty PowerToy * Use proper output dir * Proper WindowsTargetPlatformVersion * Add filters to vcxproj * Proper resource file generation * Add MouseHighlighter proof of concept code * Abstract implementation into a struct * Enable module * Disable module * Add enable module to settings page * Can change the hotkey in settings * Remove remaining boilerplate code * Add logging * Add telemetry * Add Oobe entry * Add installer instructions * Add dll to pipelines * fix spellchecker * Add more configurability * Make settings a bit prettier * Fix spellchecker * Fix wrong default fade timers * Fix user facing strings * Tweak default duration values * Fix to appear in every virtual desktop * [Mouse Highlighter] Show highlight on mouse drag (#14529) * [Mouse Highlighter]show pointer on mouse drag * fix spellchecker * [MU] UI tweaks (#14544) * UI tweaks * Update Resources.resw * Updated text strings * fix spellcheck Co-authored-by: Laute * tweak default values * PR feedback: use wstring_view * PR feedback: Log error on json error * PR feedback: don't throw 1 * PR feedback: fix copy-pasta leftColor->rightColor * PR feedback:Add another error message on exception * PR feedback: add todo to use commons/utils/json.h Co-authored-by: Niels Laute Co-authored-by: Laute --- .github/actions/spell-check/expect.txt | 3 + .pipelines/pipeline.user.windows.yml | 1 + PowerToys.sln | 9 + doc/images/icons/Find My Mouse.png | Bin 0 -> 21427 bytes doc/images/icons/Mouse Highlighter.png | Bin 0 -> 17318 bytes installer/PowerToysSetup/Product.wxs | 4 + src/common/logger/logger_settings.h | 1 + .../MouseHighlighter/Directory.Build.targets | 5 + .../MouseHighlighter/MouseHighlighter.base.rc | 40 ++ .../MouseHighlighter/MouseHighlighter.cpp | 443 ++++++++++++++++++ .../MouseHighlighter/MouseHighlighter.h | 24 + .../MouseHighlighter/MouseHighlighter.vcxproj | 145 ++++++ .../MouseHighlighter.vcxproj.filters | 62 +++ .../MouseUtils/MouseHighlighter/dllmain.cpp | 323 +++++++++++++ .../MouseHighlighter/packages.config | 4 + .../MouseUtils/MouseHighlighter/pch.cpp | 1 + src/modules/MouseUtils/MouseHighlighter/pch.h | 22 + .../MouseHighlighter/resource.base.h | 14 + .../MouseUtils/MouseHighlighter/trace.cpp | 40 ++ .../MouseUtils/MouseHighlighter/trace.h | 14 + src/runner/main.cpp | 3 +- .../EnabledModules.cs | 16 + .../Helpers/SettingsUtilities.cs | 39 ++ .../MouseHighlighterProperties.cs | 43 ++ .../MouseHighlighterSettings.cs | 35 ++ .../MouseHighlighterSettingsIPCMessage.cs | 29 ++ .../SndMouseHighlighterSettings.cs | 29 ++ .../ViewModels/FancyZonesViewModel.cs | 28 +- .../ViewModels/MouseUtilsViewModel.cs | 194 +++++++- .../FluentIcons/FluentIconsFindMyMouse.png | Bin 0 -> 2701 bytes .../FluentIconsMouseHighlighter.png | Bin 0 -> 2060 bytes .../Microsoft.PowerToys.Settings.UI.csproj | 3 + .../OOBE/Views/OobeMouseUtils.xaml | 4 + .../Strings/en-us/Resources.resw | 71 ++- .../Views/MouseUtilsPage.xaml | 125 ++++- .../Views/MouseUtilsPage.xaml.cs | 2 +- 36 files changed, 1726 insertions(+), 50 deletions(-) create mode 100644 doc/images/icons/Find My Mouse.png create mode 100644 doc/images/icons/Mouse Highlighter.png create mode 100644 src/modules/MouseUtils/MouseHighlighter/Directory.Build.targets create mode 100644 src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.base.rc create mode 100644 src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.cpp create mode 100644 src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.h create mode 100644 src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj create mode 100644 src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj.filters create mode 100644 src/modules/MouseUtils/MouseHighlighter/dllmain.cpp create mode 100644 src/modules/MouseUtils/MouseHighlighter/packages.config create mode 100644 src/modules/MouseUtils/MouseHighlighter/pch.cpp create mode 100644 src/modules/MouseUtils/MouseHighlighter/pch.h create mode 100644 src/modules/MouseUtils/MouseHighlighter/resource.base.h create mode 100644 src/modules/MouseUtils/MouseHighlighter/trace.cpp create mode 100644 src/modules/MouseUtils/MouseHighlighter/trace.h create mode 100644 src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Helpers/SettingsUtilities.cs create mode 100644 src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/MouseHighlighterProperties.cs create mode 100644 src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/MouseHighlighterSettings.cs create mode 100644 src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/MouseHighlighterSettingsIPCMessage.cs create mode 100644 src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/SndMouseHighlighterSettings.cs create mode 100644 src/settings-ui/Microsoft.PowerToys.Settings.UI/Assets/FluentIcons/FluentIconsFindMyMouse.png create mode 100644 src/settings-ui/Microsoft.PowerToys.Settings.UI/Assets/FluentIcons/FluentIconsMouseHighlighter.png diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index d1b2de49d..883ff3807 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -763,8 +763,10 @@ hglobal hhk HHmmss HHOOK +hhx HICON HIDEWINDOW +highlighter HIMAGELIST himl hinst @@ -2084,6 +2086,7 @@ Switchbetweenvirtualdesktops SWP swprintf SWRESTORE +swscanf SYMED SYMOPT SYNCMFT 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/PowerToys.sln b/PowerToys.sln index e185f09f7..5b023cf43 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -371,6 +371,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 +988,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 +1113,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/doc/images/icons/Find My Mouse.png b/doc/images/icons/Find My Mouse.png new file mode 100644 index 0000000000000000000000000000000000000000..5098e2237abf16c58fa4fa02069b5d47073b7242 GIT binary patch literal 21427 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 literal 0 HcmV?d00001 diff --git a/doc/images/icons/Mouse Highlighter.png b/doc/images/icons/Mouse Highlighter.png new file mode 100644 index 0000000000000000000000000000000000000000..789b0b1c04907068f1146dda344c991d1caf2ef5 GIT binary patch literal 17318 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 REW7{!002ovPDHLkV1oX)_geq} literal 0 HcmV?d00001 diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index 2b1139984..0cbf25227 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -657,6 +657,9 @@ + + + @@ -971,6 +974,7 @@ + diff --git a/src/common/logger/logger_settings.h b/src/common/logger/logger_settings.h index 1015ef9f5..b88af2bc7 100644 --- a/src/common/logger/logger_settings.h +++ b/src/common/logger/logger_settings.h @@ -26,6 +26,7 @@ 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; 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..78858cbed --- /dev/null +++ b/src/modules/MouseUtils/MouseHighlighter/dllmain.cpp @@ -0,0 +1,323 @@ +#include "pch.h" +#include +#include +#include "trace.h" +#include "MouseHighlighter.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; + + // helper function to get the RGB from a #FFFFFF string. + 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; + } + +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/runner/main.cpp b/src/runner/main.cpp index 7743ae581..bee782b8a 100644 --- a/src/runner/main.cpp +++ b/src/runner/main.cpp @@ -148,7 +148,8 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow L"modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.dll", L"modules/ColorPicker/ColorPicker.dll", L"modules/Awake/AwakeModuleInterface.dll", - L"modules/MouseUtils/FindMyMouse.dll" + L"modules/MouseUtils/FindMyMouse.dll" , + L"modules/MouseUtils/MouseHighlighter.dll" }; const auto VCM_PATH = L"modules/VideoConference/VideoConferenceModule.dll"; diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/EnabledModules.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/EnabledModules.cs index 0232ff641..910a4dd78 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/EnabledModules.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/EnabledModules.cs @@ -191,6 +191,22 @@ namespace Microsoft.PowerToys.Settings.UI.Library } } + private bool mouseHighlighter = true; + + [JsonPropertyName("MouseHighlighter")] + public bool MouseHighlighter + { + get => mouseHighlighter; + set + { + if (mouseHighlighter != value) + { + LogTelemetryEvent(value); + mouseHighlighter = value; + } + } + } + public string ToJsonString() { return JsonSerializer.Serialize(this); diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Helpers/SettingsUtilities.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Helpers/SettingsUtilities.cs new file mode 100644 index 000000000..913948921 --- /dev/null +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Helpers/SettingsUtilities.cs @@ -0,0 +1,39 @@ +// 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.Drawing; +using System.Globalization; + +namespace Microsoft.PowerToys.Settings.UI.Library.Helpers +{ + public static class SettingsUtilities + { + public static string ToRGBHex(string color) + { + if (color == null) + { + return "#FFFFFF"; + } + + // Using InvariantCulture as these are expected to be hex codes. + bool success = int.TryParse( + color.Replace("#", string.Empty), + System.Globalization.NumberStyles.HexNumber, + CultureInfo.InvariantCulture, + out int argb); + + if (success) + { + Color clr = Color.FromArgb(argb); + return "#" + clr.R.ToString("X2", CultureInfo.InvariantCulture) + + clr.G.ToString("X2", CultureInfo.InvariantCulture) + + clr.B.ToString("X2", CultureInfo.InvariantCulture); + } + else + { + return "#FFFFFF"; + } + } + } +} diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/MouseHighlighterProperties.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/MouseHighlighterProperties.cs new file mode 100644 index 000000000..7ccd5534f --- /dev/null +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/MouseHighlighterProperties.cs @@ -0,0 +1,43 @@ +// 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.Text.Json.Serialization; + +namespace Microsoft.PowerToys.Settings.UI.Library +{ + public class MouseHighlighterProperties + { + [JsonPropertyName("activation_shortcut")] + public HotkeySettings ActivationShortcut { get; set; } + + [JsonPropertyName("left_button_click_color")] + public StringProperty LeftButtonClickColor { get; set; } + + [JsonPropertyName("right_button_click_color")] + public StringProperty RightButtonClickColor { get; set; } + + [JsonPropertyName("highlight_opacity")] + public IntProperty HighlightOpacity { get; set; } + + [JsonPropertyName("highlight_radius")] + public IntProperty HighlightRadius { get; set; } + + [JsonPropertyName("highlight_fade_delay_ms")] + public IntProperty HighlightFadeDelayMs { get; set; } + + [JsonPropertyName("highlight_fade_duration_ms")] + public IntProperty HighlightFadeDurationMs { get; set; } + + public MouseHighlighterProperties() + { + ActivationShortcut = new HotkeySettings(true, false, false, true, 0x48); + LeftButtonClickColor = new StringProperty("#FFFF00"); + RightButtonClickColor = new StringProperty("#0000FF"); + HighlightOpacity = new IntProperty(160); + HighlightRadius = new IntProperty(20); + HighlightFadeDelayMs = new IntProperty(500); + HighlightFadeDurationMs = new IntProperty(250); + } + } +} diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/MouseHighlighterSettings.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/MouseHighlighterSettings.cs new file mode 100644 index 000000000..100e5b60d --- /dev/null +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/MouseHighlighterSettings.cs @@ -0,0 +1,35 @@ +// 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.Text.Json.Serialization; +using Microsoft.PowerToys.Settings.UI.Library.Interfaces; + +namespace Microsoft.PowerToys.Settings.UI.Library +{ + public class MouseHighlighterSettings : BasePTModuleSettings, ISettingsConfig + { + public const string ModuleName = "MouseHighlighter"; + + [JsonPropertyName("properties")] + public MouseHighlighterProperties Properties { get; set; } + + public MouseHighlighterSettings() + { + Name = ModuleName; + Properties = new MouseHighlighterProperties(); + Version = "1.0"; + } + + public string GetModuleName() + { + return Name; + } + + // This can be utilized in the future if the settings.json file is to be modified/deleted. + public bool UpgradeSettingsConfiguration() + { + return false; + } + } +} diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/MouseHighlighterSettingsIPCMessage.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/MouseHighlighterSettingsIPCMessage.cs new file mode 100644 index 000000000..e96669493 --- /dev/null +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/MouseHighlighterSettingsIPCMessage.cs @@ -0,0 +1,29 @@ +// 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.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.PowerToys.Settings.UI.Library +{ + public class MouseHighlighterSettingsIPCMessage + { + [JsonPropertyName("powertoys")] + public SndMouseHighlighterSettings Powertoys { get; set; } + + public MouseHighlighterSettingsIPCMessage() + { + } + + public MouseHighlighterSettingsIPCMessage(SndMouseHighlighterSettings settings) + { + this.Powertoys = settings; + } + + public string ToJsonString() + { + return JsonSerializer.Serialize(this); + } + } +} diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/SndMouseHighlighterSettings.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/SndMouseHighlighterSettings.cs new file mode 100644 index 000000000..ccaf04e0d --- /dev/null +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/SndMouseHighlighterSettings.cs @@ -0,0 +1,29 @@ +// 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.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.PowerToys.Settings.UI.Library +{ + public class SndMouseHighlighterSettings + { + [JsonPropertyName("MouseHighlighter")] + public MouseHighlighterSettings MouseHighlighter { get; set; } + + public SndMouseHighlighterSettings() + { + } + + public SndMouseHighlighterSettings(MouseHighlighterSettings settings) + { + MouseHighlighter = settings; + } + + public string ToJsonString() + { + return JsonSerializer.Serialize(this); + } + } +} diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs index 29d365044..7c057e024 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs @@ -554,7 +554,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels // The fallback value is based on ToRGBHex's behavior, which returns // #FFFFFF if any exceptions are encountered, e.g. from passing in a null value. // This extra handling is added here to deal with FxCop warnings. - value = (value != null) ? ToRGBHex(value) : "#FFFFFF"; + value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#FFFFFF"; if (!value.Equals(_zoneHighlightColor, StringComparison.OrdinalIgnoreCase)) { _zoneHighlightColor = value; @@ -576,7 +576,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels // The fallback value is based on ToRGBHex's behavior, which returns // #FFFFFF if any exceptions are encountered, e.g. from passing in a null value. // This extra handling is added here to deal with FxCop warnings. - value = (value != null) ? ToRGBHex(value) : "#FFFFFF"; + value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#FFFFFF"; if (!value.Equals(_zoneBorderColor, StringComparison.OrdinalIgnoreCase)) { _zoneBorderColor = value; @@ -598,7 +598,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels // The fallback value is based on ToRGBHex's behavior, which returns // #FFFFFF if any exceptions are encountered, e.g. from passing in a null value. // This extra handling is added here to deal with FxCop warnings. - value = (value != null) ? ToRGBHex(value) : "#FFFFFF"; + value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#FFFFFF"; if (!value.Equals(_zoneInActiveColor, StringComparison.OrdinalIgnoreCase)) { _zoneInActiveColor = value; @@ -753,27 +753,5 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels OnPropertyChanged(propertyName); SettingsUtils.SaveSettings(Settings.ToJsonString(), GetSettingsSubPath()); } - - private static string ToRGBHex(string color) - { - // Using InvariantCulture as these are expected to be hex codes. - bool success = int.TryParse( - color.Replace("#", string.Empty), - System.Globalization.NumberStyles.HexNumber, - CultureInfo.InvariantCulture, - out int argb); - - if (success) - { - Color clr = Color.FromArgb(argb); - return "#" + clr.R.ToString("X2", CultureInfo.InvariantCulture) + - clr.G.ToString("X2", CultureInfo.InvariantCulture) + - clr.B.ToString("X2", CultureInfo.InvariantCulture); - } - else - { - return "#FFFFFF"; - } - } } } diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/MouseUtilsViewModel.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/MouseUtilsViewModel.cs index 5faec966f..1a22ba39d 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/MouseUtilsViewModel.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/MouseUtilsViewModel.cs @@ -17,7 +17,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels private FindMyMouseSettings FindMyMouseSettingsConfig { get; set; } - public MouseUtilsViewModel(ISettingsUtils settingsUtils, ISettingsRepository settingsRepository, ISettingsRepository findMyMouseSettingsRepository, Func ipcMSGCallBackFunc) + private MouseHighlighterSettings MouseHighlighterSettingsConfig { get; set; } + + public MouseUtilsViewModel(ISettingsUtils settingsUtils, ISettingsRepository settingsRepository, ISettingsRepository findMyMouseSettingsRepository, ISettingsRepository mouseHighlighterSettingsRepository, Func ipcMSGCallBackFunc) { SettingsUtils = settingsUtils; @@ -31,6 +33,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels _isFindMyMouseEnabled = GeneralSettingsConfig.Enabled.FindMyMouse; + _isMouseHighlighterEnabled = GeneralSettingsConfig.Enabled.MouseHighlighter; + // To obtain the find my mouse settings, if the file exists. // If not, to create a file with the default settings and to return the default configurations. if (findMyMouseSettingsRepository == null) @@ -41,6 +45,23 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels FindMyMouseSettingsConfig = findMyMouseSettingsRepository.SettingsConfig; _findMyMouseDoNotActivateOnGameMode = FindMyMouseSettingsConfig.Properties.DoNotActivateOnGameMode.Value; + if (mouseHighlighterSettingsRepository == null) + { + throw new ArgumentNullException(nameof(mouseHighlighterSettingsRepository)); + } + + MouseHighlighterSettingsConfig = mouseHighlighterSettingsRepository.SettingsConfig; + string leftClickColor = MouseHighlighterSettingsConfig.Properties.LeftButtonClickColor.Value; + _highlighterLeftButtonClickColor = !string.IsNullOrEmpty(leftClickColor) ? leftClickColor : "#FFFF00"; + + string rightClickColor = MouseHighlighterSettingsConfig.Properties.RightButtonClickColor.Value; + _highlighterRightButtonClickColor = !string.IsNullOrEmpty(rightClickColor) ? rightClickColor : "#0000FF"; + + _highlighterOpacity = MouseHighlighterSettingsConfig.Properties.HighlightOpacity.Value; + _highlighterRadius = MouseHighlighterSettingsConfig.Properties.HighlightRadius.Value; + _highlightFadeDelayMs = MouseHighlighterSettingsConfig.Properties.HighlightFadeDelayMs.Value; + _highlightFadeDurationMs = MouseHighlighterSettingsConfig.Properties.HighlightFadeDurationMs.Value; + // set the callback functions value to handle outgoing IPC message. SendConfigMSG = ipcMSGCallBackFunc; } @@ -93,9 +114,180 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels SettingsUtils.SaveSettings(FindMyMouseSettingsConfig.ToJsonString(), FindMyMouseSettings.ModuleName); } + public bool IsMouseHighlighterEnabled + { + get => _isMouseHighlighterEnabled; + set + { + if (_isMouseHighlighterEnabled != value) + { + _isMouseHighlighterEnabled = value; + + GeneralSettingsConfig.Enabled.MouseHighlighter = value; + OnPropertyChanged(nameof(_isMouseHighlighterEnabled)); + + OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig); + SendConfigMSG(outgoing.ToString()); + + NotifyMouseHighlighterPropertyChanged(); + } + } + } + + public HotkeySettings MouseHighlighterActivationShortcut + { + get + { + return MouseHighlighterSettingsConfig.Properties.ActivationShortcut; + } + + set + { + if (MouseHighlighterSettingsConfig.Properties.ActivationShortcut != value) + { + MouseHighlighterSettingsConfig.Properties.ActivationShortcut = value; + NotifyMouseHighlighterPropertyChanged(); + } + } + } + + public string MouseHighlighterLeftButtonClickColor + { + get + { + return _highlighterLeftButtonClickColor; + } + + set + { + // The fallback value is based on ToRGBHex's behavior, which returns + // #FFFFFF if any exceptions are encountered, e.g. from passing in a null value. + // This extra handling is added here to deal with FxCop warnings. + value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#FFFFFF"; + if (!value.Equals(_highlighterLeftButtonClickColor, StringComparison.OrdinalIgnoreCase)) + { + _highlighterLeftButtonClickColor = value; + MouseHighlighterSettingsConfig.Properties.LeftButtonClickColor.Value = value; + NotifyMouseHighlighterPropertyChanged(); + } + } + } + + public string MouseHighlighterRightButtonClickColor + { + get + { + return _highlighterRightButtonClickColor; + } + + set + { + // The fallback value is based on ToRGBHex's behavior, which returns + // #FFFFFF if any exceptions are encountered, e.g. from passing in a null value. + // This extra handling is added here to deal with FxCop warnings. + value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#FFFFFF"; + if (!value.Equals(_highlighterRightButtonClickColor, StringComparison.OrdinalIgnoreCase)) + { + _highlighterRightButtonClickColor = value; + MouseHighlighterSettingsConfig.Properties.RightButtonClickColor.Value = value; + NotifyMouseHighlighterPropertyChanged(); + } + } + } + + public int MouseHighlighterOpacity + { + get + { + return _highlighterOpacity; + } + + set + { + if (value != _highlighterOpacity) + { + _highlighterOpacity = value; + MouseHighlighterSettingsConfig.Properties.HighlightOpacity.Value = value; + NotifyMouseHighlighterPropertyChanged(); + } + } + } + + public int MouseHighlighterRadius + { + get + { + return _highlighterRadius; + } + + set + { + if (value != _highlighterRadius) + { + _highlighterRadius = value; + MouseHighlighterSettingsConfig.Properties.HighlightRadius.Value = value; + NotifyMouseHighlighterPropertyChanged(); + } + } + } + + public int MouseHighlighterFadeDelayMs + { + get + { + return _highlightFadeDelayMs; + } + + set + { + if (value != _highlightFadeDelayMs) + { + _highlightFadeDelayMs = value; + MouseHighlighterSettingsConfig.Properties.HighlightFadeDelayMs.Value = value; + NotifyMouseHighlighterPropertyChanged(); + } + } + } + + public int MouseHighlighterFadeDurationMs + { + get + { + return _highlightFadeDurationMs; + } + + set + { + if (value != _highlightFadeDurationMs) + { + _highlightFadeDurationMs = value; + MouseHighlighterSettingsConfig.Properties.HighlightFadeDurationMs.Value = value; + NotifyMouseHighlighterPropertyChanged(); + } + } + } + + public void NotifyMouseHighlighterPropertyChanged([CallerMemberName] string propertyName = null) + { + OnPropertyChanged(propertyName); + + SndMouseHighlighterSettings outsettings = new SndMouseHighlighterSettings(MouseHighlighterSettingsConfig); + SndModuleSettings ipcMessage = new SndModuleSettings(outsettings); + SendConfigMSG(ipcMessage.ToJsonString()); + SettingsUtils.SaveSettings(MouseHighlighterSettingsConfig.ToJsonString(), MouseHighlighterSettings.ModuleName); + } + private Func SendConfigMSG { get; } private bool _isFindMyMouseEnabled; private bool _findMyMouseDoNotActivateOnGameMode; + + private bool _isMouseHighlighterEnabled; + private string _highlighterLeftButtonClickColor; + private string _highlighterRightButtonClickColor; + private int _highlighterOpacity; + private int _highlighterRadius; + private int _highlightFadeDelayMs; + private int _highlightFadeDurationMs; } } diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Assets/FluentIcons/FluentIconsFindMyMouse.png b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Assets/FluentIcons/FluentIconsFindMyMouse.png new file mode 100644 index 0000000000000000000000000000000000000000..98e7e4cf68099908f7c143bfbc97e66e0ed3b603 GIT binary patch literal 2701 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 + + + diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeMouseUtils.xaml b/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeMouseUtils.xaml index 6fe25099c..5ac794509 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeMouseUtils.xaml +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeMouseUtils.xaml @@ -18,6 +18,10 @@ Style="{ThemeResource OobeSubtitleStyle}" /> + + +