From 0a1759028d6dc7f87c46dfd7469b32299437e257 Mon Sep 17 00:00:00 2001 From: Davide Giacometti Date: Wed, 20 Sep 2023 12:46:36 +0200 Subject: [PATCH 1/8] [Hosts]Fix move up/down not being disabled when filtering (#28615) --- src/modules/Hosts/Hosts/HostsXAML/Views/MainPage.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/Hosts/Hosts/HostsXAML/Views/MainPage.xaml b/src/modules/Hosts/Hosts/HostsXAML/Views/MainPage.xaml index e889c0381e..f62eef0a98 100644 --- a/src/modules/Hosts/Hosts/HostsXAML/Views/MainPage.xaml +++ b/src/modules/Hosts/Hosts/HostsXAML/Views/MainPage.xaml @@ -199,7 +199,7 @@ + IsEnabled="{x:Bind ViewModel.Filtered, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}"> @@ -207,7 +207,7 @@ + IsEnabled="{x:Bind ViewModel.Filtered, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}"> From 5ddd8d72ddd2fb488ae517f190a967b36f681504 Mon Sep 17 00:00:00 2001 From: gokcekantarci <115616017+gokcekantarci@users.noreply.github.com> Date: Wed, 20 Sep 2023 20:30:32 +0300 Subject: [PATCH 2/8] [Peek]Always use default white background for HTML files(#28557) * [Peek] A check for color of body element in html and change it based on theme is added. * [Peek] WebView2 solution is added instead of javascript injection * [Peek] Detailed comments are added. --- .../Controls/BrowserControl.xaml.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/modules/peek/Peek.FilePreviewer/Controls/BrowserControl.xaml.cs b/src/modules/peek/Peek.FilePreviewer/Controls/BrowserControl.xaml.cs index 39f94af55e..1fbe78a166 100644 --- a/src/modules/peek/Peek.FilePreviewer/Controls/BrowserControl.xaml.cs +++ b/src/modules/peek/Peek.FilePreviewer/Controls/BrowserControl.xaml.cs @@ -24,6 +24,8 @@ namespace Peek.FilePreviewer.Controls /// private Uri? _navigatedUri; + private Color originalBackgroundColor; + public delegate void NavigationCompletedHandler(WebView2? sender, CoreWebView2NavigationCompletedEventArgs? args); public delegate void DOMContentLoadedHandler(CoreWebView2? sender, CoreWebView2DOMContentLoadedEventArgs? args); @@ -120,7 +122,11 @@ namespace Peek.FilePreviewer.Controls { await PreviewBrowser.EnsureCoreWebView2Async(); - // transparent background when loading the page + // Storing the original background color so it can be reset later for specific file types like HTML. + originalBackgroundColor = PreviewBrowser.DefaultBackgroundColor; + + // Setting the background color to transparent when initially loading the WebView2 component. + // This ensures that non-HTML files are displayed with a transparent background. PreviewBrowser.DefaultBackgroundColor = Color.FromArgb(0, 0, 0, 0); PreviewBrowser.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = false; @@ -150,6 +156,15 @@ namespace Peek.FilePreviewer.Controls private void CoreWebView2_DOMContentLoaded(CoreWebView2 sender, CoreWebView2DOMContentLoadedEventArgs args) { + // If the file being previewed is HTML or HTM, reset the background color to its original state. + // This is done to ensure that HTML and HTM files are displayed as intended, with their own background settings. + if (Source?.ToString().EndsWith(".html", StringComparison.OrdinalIgnoreCase) == true || + Source?.ToString().EndsWith(".htm", StringComparison.OrdinalIgnoreCase) == true) + { + // Reset to default behavior for HTML files + PreviewBrowser.DefaultBackgroundColor = originalBackgroundColor; + } + DOMContentLoaded?.Invoke(sender, args); } From 59f0ccebc7b360735b72651a733f9fffc7b85135 Mon Sep 17 00:00:00 2001 From: Heiko <61519853+htcfreek@users.noreply.github.com> Date: Wed, 20 Sep 2023 19:31:40 +0200 Subject: [PATCH 3/8] [PT Run > PluginAdditionalOptions] Refactoring and more settings types (#28601) * refactor existing code * fixes * fix combo box layout * improve layout * add more settings types * combined setting * enabled state * fix spelling * improve settings.json handling on null values * textbox improvements * rework xaml code * fix xaml style * spell fixes * spell fixes * update comment --- .../Plugins/Microsoft.Plugin.Shell/Main.cs | 6 +- .../PluginAdditionalOption.cs | 67 ++++++- .../PowerLauncherSettings.cs | 1 + .../SettingsXAML/Views/PowerLauncherPage.xaml | 168 +++++++++++++++++- .../PluginAdditionalOptionViewModel.cs | 75 +++++++- 5 files changed, 296 insertions(+), 21 deletions(-) diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Main.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Main.cs index d458b7996a..dfbdbc8450 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Main.cs @@ -52,7 +52,7 @@ namespace Microsoft.Plugin.Shell { Key = "ShellCommandExecution", DisplayLabel = Resources.wox_shell_command_execution, - SelectionTypeValue = (int)PluginAdditionalOption.SelectionType.Combobox, + PluginOptionType = PluginAdditionalOption.AdditionalOptionType.Combobox, ComboBoxOptions = new List { Resources.run_command_in_command_prompt, @@ -60,7 +60,7 @@ namespace Microsoft.Plugin.Shell Resources.find_executable_file_and_run_it, Resources.run_command_in_windows_terminal, }, - Option = (int)_settings.Shell, + ComboBoxValue = (int)_settings.Shell, }, }; @@ -442,7 +442,7 @@ namespace Microsoft.Plugin.Shell _settings.LeaveShellOpen = leaveShellOpen; var optionShell = settings.AdditionalOptions.FirstOrDefault(x => x.Key == "ShellCommandExecution"); - shellOption = optionShell?.Option ?? shellOption; + shellOption = optionShell?.ComboBoxValue ?? shellOption; _settings.Shell = (ExecutionShell)shellOption; } diff --git a/src/settings-ui/Settings.UI.Library/PluginAdditionalOption.cs b/src/settings-ui/Settings.UI.Library/PluginAdditionalOption.cs index 362384c534..54e0c7868e 100644 --- a/src/settings-ui/Settings.UI.Library/PluginAdditionalOption.cs +++ b/src/settings-ui/Settings.UI.Library/PluginAdditionalOption.cs @@ -3,17 +3,28 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Text.Json.Serialization; namespace Microsoft.PowerToys.Settings.UI.Library { public class PluginAdditionalOption { - public enum SelectionType + public enum AdditionalOptionType { Checkbox = 0, Combobox = 1, + Textbox = 2, + Numberbox = 3, + CheckboxAndCombobox = 11, + CheckboxAndTextbox = 12, + CheckboxAndNumberbox = 13, } + /// + /// Gets or sets the layout type of the option in settings ui (Optional; Default is checkbox) + /// + public AdditionalOptionType PluginOptionType { get; set; } + public string Key { get; set; } public string DisplayLabel { get; set; } @@ -21,14 +32,64 @@ namespace Microsoft.PowerToys.Settings.UI.Library /// /// Gets or sets a value to show a description of this setting in the settings ui. (Optional) /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string DisplayDescription { get; set; } + /// + /// Gets or sets a value to show a label for the second setting if two combined settings are shown + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string SecondDisplayLabel { get; set; } + + /// + /// Gets or sets a value to show a description for the second setting in the settings ui if two combined settings are shown. (Optional) + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string SecondDisplayDescription { get; set; } + + /// + /// Gets or sets a value indicating whether the checkbox is set or not set + /// public bool Value { get; set; } + public int ComboBoxValue { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public List ComboBoxOptions { get; set; } - public int Option { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string TextValue { get; set; } - public int SelectionTypeValue { get; set; } + /// + /// Gets or sets the value that specifies the maximum number of characters allowed for user input in the text box. (Optional; Default is 0 which means no limit.) + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public int? TextBoxMaxLength { get; set; } + + public double NumberValue { get; set; } + + /// + /// Gets or sets a minimal value for the number box. (Optional; Default is Double.MinValue) + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public double? NumberBoxMin { get; set; } + + /// + /// Gets or sets a maximal value for the number box. (Optional; Default is Double.MaxValue) + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public double? NumberBoxMax { get; set; } + + /// + /// Gets or sets the value for small changes of the number box. (Optional; Default is 1) + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public double? NumberBoxSmallChange { get; set; } + + /// + /// Gets or sets the value for large changes of the number box. (Optional; Default is 10) + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public double? NumberBoxLargeChange { get; set; } } } diff --git a/src/settings-ui/Settings.UI.Library/PowerLauncherSettings.cs b/src/settings-ui/Settings.UI.Library/PowerLauncherSettings.cs index b1e4cec04d..b85906a001 100644 --- a/src/settings-ui/Settings.UI.Library/PowerLauncherSettings.cs +++ b/src/settings-ui/Settings.UI.Library/PowerLauncherSettings.cs @@ -33,6 +33,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library var options = new JsonSerializerOptions { WriteIndented = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, }; if (settingsUtils == null) diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerLauncherPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerLauncherPage.xaml index 94fd68d5bc..9e62c42206 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerLauncherPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerLauncherPage.xaml @@ -360,6 +360,7 @@ + - + CornerRadius="0" + Visibility="{x:Bind Path=ShowCheckBox, Converter={StaticResource BoolToVisibilityConverter}}"> + + + + + + + + + + + + + + + + + + IsChecked="{x:Bind Path=Value, Mode=TwoWay}" /> + + + + + + + + - - + IsChecked="{x:Bind Path=Value, Mode=TwoWay}" /> + + + + + + + + + + + + + + + _additionalOption.DisplayLabel; public string DisplayDescription => _additionalOption.DisplayDescription; + // Labels of second setting of combined types + public string SecondDisplayLabel => _additionalOption.SecondDisplayLabel; + + public string SecondDisplayDescription => _additionalOption.SecondDisplayDescription; + + // Bool checkbox setting + public bool ShowCheckBox => _additionalOption.PluginOptionType == PluginAdditionalOption.AdditionalOptionType.Checkbox; + public bool Value { get => _additionalOption.Value; @@ -31,29 +40,83 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels { _additionalOption.Value = value; NotifyPropertyChanged(); + NotifyPropertyChanged(nameof(SecondSettingIsEnabled)); } } } + // ComboBox setting + public bool ShowComboBox => _additionalOption.PluginOptionType == PluginAdditionalOption.AdditionalOptionType.Combobox && + _additionalOption.ComboBoxOptions != null && _additionalOption.ComboBoxOptions.Count > 0; + public List ComboBoxOptions => _additionalOption.ComboBoxOptions; - public int Option + public int ComboBoxValue { - get => _additionalOption.Option; + get => _additionalOption.ComboBoxValue; set { - if (value != _additionalOption.Option) + if (value != _additionalOption.ComboBoxValue) { - _additionalOption.Option = value; + _additionalOption.ComboBoxValue = value; NotifyPropertyChanged(); } } } - public bool ShowComboBox => _additionalOption.SelectionTypeValue == (int)PluginAdditionalOption.SelectionType.Combobox && _additionalOption.ComboBoxOptions != null && _additionalOption.ComboBoxOptions.Count > 0; + // TextBox setting + public bool ShowTextBox => _additionalOption.PluginOptionType == PluginAdditionalOption.AdditionalOptionType.Textbox; - public bool ShowCheckBox => _additionalOption.SelectionTypeValue == (int)PluginAdditionalOption.SelectionType.Checkbox; + public int TextBoxMaxLength => (_additionalOption.TextBoxMaxLength == null) ? 0 : _additionalOption.TextBoxMaxLength.Value; // 0 ist the default and means no limit. + public string TextValue + { + get => _additionalOption.TextValue; + set + { + if (value != _additionalOption.TextValue) + { + _additionalOption.TextValue = value; + NotifyPropertyChanged(); + } + } + } + + // NumberBox setting + public bool ShowNumberBox => _additionalOption.PluginOptionType == PluginAdditionalOption.AdditionalOptionType.Numberbox; + + public double NumberBoxMin => (_additionalOption.NumberBoxMin == null) ? double.MinValue : _additionalOption.NumberBoxMin.Value; + + public double NumberBoxMax => (_additionalOption.NumberBoxMax == null) ? double.MaxValue : _additionalOption.NumberBoxMax.Value; + + public double NumberBoxSmallChange => (_additionalOption.NumberBoxSmallChange == null) ? 1 : _additionalOption.NumberBoxSmallChange.Value; + + public double NumberBoxLargeChange => (_additionalOption.NumberBoxLargeChange == null) ? 10 : _additionalOption.NumberBoxLargeChange.Value; + + public double NumberValue + { + get => _additionalOption.NumberValue; + set + { + if (value != _additionalOption.NumberValue) + { + _additionalOption.NumberValue = value; + NotifyPropertyChanged(); + } + } + } + + // Show combined settings cards + public bool ShowCheckboxAndCombobox => _additionalOption.PluginOptionType == PluginAdditionalOption.AdditionalOptionType.CheckboxAndCombobox; + + public bool ShowCheckboxAndTextbox => _additionalOption.PluginOptionType == PluginAdditionalOption.AdditionalOptionType.CheckboxAndTextbox; + + public bool ShowCheckboxAndNumberbox => _additionalOption.PluginOptionType == PluginAdditionalOption.AdditionalOptionType.CheckboxAndNumberbox; + + // Enabled state of ComboBox, TextBox, NumberBox (If combined with checkbox then checkbox value decides it.) + public bool SecondSettingIsEnabled => (int)_additionalOption.PluginOptionType > 10 ? _additionalOption.Value : true; + + // Handle property changes public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") From 8cd2b7cdc3fa0eeb4ae7ed175aa2d418aefb3fd5 Mon Sep 17 00:00:00 2001 From: Seraphima Zykova Date: Thu, 21 Sep 2023 11:11:51 +0300 Subject: [PATCH 4/8] [FancyZones] Fix handling newly created windows on Windows 11 (#28646) --- .../fancyzones/FancyZonesLib/FancyZones.cpp | 9 +-- .../FancyZonesLib/FancyZonesLib.vcxproj | 1 + .../FancyZonesLib.vcxproj.filters | 3 + .../FancyZonesWindowProcessing.cpp | 56 +++++++++++++++ .../FancyZonesWindowProcessing.h | 35 +-------- .../FancyZonesLib/WindowMouseSnap.cpp | 1 - .../fancyzones/FancyZonesLib/WindowUtils.cpp | 71 +++++++------------ .../fancyzones/FancyZonesLib/WindowUtils.h | 3 +- 8 files changed, 91 insertions(+), 88 deletions(-) create mode 100644 src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProcessing.cpp diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp index 76f99175d5..7ddcd955c5 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -397,12 +398,6 @@ void FancyZones::WindowCreated(HWND window) noexcept return; } - const bool isCandidateForLastKnownZone = FancyZonesWindowUtils::IsCandidateForZoning(window); - if (!isCandidateForLastKnownZone) - { - return; - } - HMONITOR primary = MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY); HMONITOR active = primary; @@ -994,7 +989,7 @@ bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept return false; } - if (FancyZonesSettings::settings().overrideSnapHotkeys && FancyZonesWindowUtils::IsCandidateForZoning(window)) + if (FancyZonesSettings::settings().overrideSnapHotkeys) { HMONITOR monitor = WorkAreaKeyFromWindow(window); diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj index 9bb26ccd95..a659a1839e 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj @@ -103,6 +103,7 @@ ../pch.h + diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters index e8012868f2..f97b95c5a9 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters @@ -272,6 +272,9 @@ Source Files + + Source Files + diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProcessing.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProcessing.cpp new file mode 100644 index 0000000000..102392ba91 --- /dev/null +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProcessing.cpp @@ -0,0 +1,56 @@ +#include "pch.h" +#include "FancyZonesWindowProcessing.h" + +#include +#include +#include + +bool FancyZonesWindowProcessing::IsProcessable(HWND window) noexcept +{ + const bool isSplashScreen = FancyZonesWindowUtils::IsSplashScreen(window); + if (isSplashScreen) + { + return false; + } + + const bool windowMinimized = IsIconic(window); + if (windowMinimized) + { + return false; + } + + const bool standard = FancyZonesWindowUtils::IsStandardWindow(window); + if (!standard) + { + return false; + } + + // popup could be the window we don't want to snap: start menu, notification popup, tray window, etc. + // also, popup could be the windows we want to snap disregarding the "allowSnapPopupWindows" setting, e.g. Telegram + bool isPopup = FancyZonesWindowUtils::IsPopupWindow(window) && !FancyZonesWindowUtils::HasThickFrameAndMinimizeMaximizeButtons(window); + if (isPopup && !FancyZonesSettings::settings().allowSnapPopupWindows) + { + return false; + } + + // allow child windows + auto hasOwner = FancyZonesWindowUtils::HasVisibleOwner(window); + if (hasOwner && !FancyZonesSettings::settings().allowSnapChildWindows) + { + return false; + } + + if (FancyZonesWindowUtils::IsExcluded(window)) + { + return false; + } + + // Switch between virtual desktops results with posting same windows messages that also indicate + // creation of new window. We need to check if window being processed is on currently active desktop. + if (!VirtualDesktop::instance().IsWindowOnCurrentDesktop(window)) + { + return false; + } + + return true; +} diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProcessing.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProcessing.h index fc2108ffbb..21fbcfbad2 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProcessing.h +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProcessing.h @@ -1,39 +1,6 @@ #pragma once -#include -#include - namespace FancyZonesWindowProcessing { - inline bool IsProcessable(HWND window) noexcept - { - const bool isSplashScreen = FancyZonesWindowUtils::IsSplashScreen(window); - if (isSplashScreen) - { - return false; - } - - const bool windowMinimized = IsIconic(window); - if (windowMinimized) - { - return false; - } - - // For windows that FancyZones shouldn't process (start menu, tray, popup menus) - // VirtualDesktopManager is unable to retrieve virtual desktop id and returns an error. - auto desktopId = VirtualDesktop::instance().GetDesktopId(window); - if (!desktopId.has_value()) - { - return false; - } - - // Switch between virtual desktops results with posting same windows messages that also indicate - // creation of new window. We need to check if window being processed is on currently active desktop. - if (!VirtualDesktop::instance().IsWindowOnCurrentDesktop(window)) - { - return false; - } - - return true; - } + bool IsProcessable(HWND window) noexcept; } \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.cpp b/src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.cpp index c6b8f8cedd..a69aaa7ef7 100644 --- a/src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.cpp +++ b/src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.cpp @@ -32,7 +32,6 @@ WindowMouseSnap::~WindowMouseSnap() std::unique_ptr WindowMouseSnap::Create(HWND window, const std::unordered_map>& activeWorkAreas) { if (!FancyZonesWindowProcessing::IsProcessable(window) || - !FancyZonesWindowUtils::IsCandidateForZoning(window) || FancyZonesWindowUtils::IsCursorTypeIndicatingSizeEvent()) { return nullptr; diff --git a/src/modules/fancyzones/FancyZonesLib/WindowUtils.cpp b/src/modules/fancyzones/FancyZonesLib/WindowUtils.cpp index dbad77732c..ef895cf830 100644 --- a/src/modules/fancyzones/FancyZonesLib/WindowUtils.cpp +++ b/src/modules/fancyzones/FancyZonesLib/WindowUtils.cpp @@ -166,6 +166,8 @@ bool FancyZonesWindowUtils::HasVisibleOwner(HWND window) noexcept bool FancyZonesWindowUtils::IsStandardWindow(HWND window) { + // True if from the styles the window looks like a standard window + if (GetAncestor(window, GA_ROOT) != window) { return false; @@ -181,13 +183,6 @@ bool FancyZonesWindowUtils::IsStandardWindow(HWND window) return false; } - std::array class_name; - GetClassNameA(window, class_name.data(), static_cast(class_name.size())); - if (is_system_window(window, class_name.data())) - { - return false; - } - return true; } @@ -203,44 +198,6 @@ bool FancyZonesWindowUtils::HasThickFrameAndMinimizeMaximizeButtons(HWND window) return ((style & WS_THICKFRAME) == WS_THICKFRAME && (style & WS_MINIMIZEBOX) == WS_MINIMIZEBOX && (style & WS_MAXIMIZEBOX) == WS_MAXIMIZEBOX); } -bool FancyZonesWindowUtils::IsCandidateForZoning(HWND window) -{ - bool isStandard = IsStandardWindow(window); - if (!isStandard) - { - return false; - } - - // popup could be the window we don't want to snap: start menu, notification popup, tray window, etc. - // also, popup could be the windows we want to snap disregarding the "allowSnapPopupWindows" setting, e.g. Telegram - bool isPopup = IsPopupWindow(window); - if (isPopup && !HasThickFrameAndMinimizeMaximizeButtons(window) && !FancyZonesSettings::settings().allowSnapPopupWindows) - { - return false; - } - - // allow child windows - auto hasOwner = HasVisibleOwner(window); - if (hasOwner && !FancyZonesSettings::settings().allowSnapChildWindows) - { - return false; - } - - std::wstring processPath = get_process_path_waiting_uwp(window); - CharUpperBuffW(const_cast(processPath).data(), static_cast(processPath.length())); - if (IsExcludedByUser(window, processPath)) - { - return false; - } - - if (IsExcludedByDefault(window, processPath)) - { - return false; - } - - return true; -} - bool FancyZonesWindowUtils::IsProcessOfWindowElevated(HWND window) { DWORD pid = 0; @@ -268,6 +225,23 @@ bool FancyZonesWindowUtils::IsProcessOfWindowElevated(HWND window) return false; } +bool FancyZonesWindowUtils::IsExcluded(HWND window) +{ + std::wstring processPath = get_process_path_waiting_uwp(window); + CharUpperBuffW(const_cast(processPath).data(), static_cast(processPath.length())); + if (IsExcludedByUser(window, processPath)) + { + return true; + } + + if (IsExcludedByDefault(window, processPath)) + { + return true; + } + + return false; +} + bool FancyZonesWindowUtils::IsExcludedByUser(const HWND& hwnd, std::wstring& processPath) noexcept { return (check_excluded_app(hwnd, processPath, FancyZonesSettings::settings().excludedAppsArray)); @@ -281,6 +255,13 @@ bool FancyZonesWindowUtils::IsExcludedByDefault(const HWND& hwnd, std::wstring& return true; } + std::array class_name; + GetClassNameA(hwnd, class_name.data(), static_cast(class_name.size())); + if (is_system_window(hwnd, class_name.data())) + { + return true; + } + static std::vector defaultExcludedApps = { NonLocalizable::PowerToysAppFZEditor, NonLocalizable::CoreWindow, NonLocalizable::SearchUI }; return (check_excluded_app(hwnd, processPath, defaultExcludedApps)); } diff --git a/src/modules/fancyzones/FancyZonesLib/WindowUtils.h b/src/modules/fancyzones/FancyZonesLib/WindowUtils.h index 9659d78b9c..4ac973bacb 100644 --- a/src/modules/fancyzones/FancyZonesLib/WindowUtils.h +++ b/src/modules/fancyzones/FancyZonesLib/WindowUtils.h @@ -21,8 +21,9 @@ namespace FancyZonesWindowUtils bool IsStandardWindow(HWND window); bool IsPopupWindow(HWND window) noexcept; bool HasThickFrameAndMinimizeMaximizeButtons(HWND window) noexcept; - bool IsCandidateForZoning(HWND window); bool IsProcessOfWindowElevated(HWND window); // If HWND is already dead, we assume it wasn't elevated + + bool IsExcluded(HWND window); bool IsExcludedByUser(const HWND& hwnd, std::wstring& processPath) noexcept; bool IsExcludedByDefault(const HWND& hwnd, std::wstring& processPath) noexcept; From 890b7f4286a95ced04d7da140b474f90fd4351ed Mon Sep 17 00:00:00 2001 From: Seraphima Zykova Date: Thu, 21 Sep 2023 13:47:56 +0300 Subject: [PATCH 5/8] [FancyZones]Fix for the scenario of layout reset when opening the FZEditor (#28556) * rename * moved applied layouts tests * changed work area id comparison * changed save * changed apply * changed clone * sync applied layouts * save last used vd * replace parent work area ids * proper time for sync * sync layouts considering last used virtual desktop * use ids from work areas on editor opening * update applied layouts tests * sync app zone history vd * fix test * release build fix * app zone history comparison * pass last used vd to sync * clean up unused * dpi unaware values * update GUID_NULL * use registry values only * added more tests * fix failing scenario * added replace condition to zone history * sync time * log * spellcheck * fix pch in project * fixed cloning layout --- .../FancyZonesLib/EditorParameters.cpp | 123 +++-- .../FancyZonesLib/EditorParameters.h | 5 +- .../fancyzones/FancyZonesLib/FancyZones.cpp | 82 +-- .../fancyzones/FancyZonesLib/FancyZonesData.h | 8 +- .../FancyZonesData/AppZoneHistory.cpp | 65 +-- .../FancyZonesData/AppZoneHistory.h | 12 +- .../FancyZonesData/AppliedLayouts.cpp | 155 +++--- .../FancyZonesData/AppliedLayouts.h | 10 +- .../FancyZonesData/LastUsedVirtualDesktop.cpp | 78 +++ .../FancyZonesData/LastUsedVirtualDesktop.h | 41 ++ .../FancyZonesLib/FancyZonesDataTypes.h | 11 +- .../FancyZonesLib/FancyZonesLib.vcxproj | 8 +- .../FancyZonesLib.vcxproj.filters | 10 +- .../FancyZonesLib/VirtualDesktop.cpp | 24 +- .../fancyzones/FancyZonesLib/VirtualDesktop.h | 2 - .../fancyzones/FancyZonesLib/WorkArea.cpp | 8 +- ...kAreaMap.cpp => WorkAreaConfiguration.cpp} | 36 +- ...rWorkAreaMap.h => WorkAreaConfiguration.h} | 15 +- .../UnitTests/AppZoneHistoryTests.Spec.cpp | 155 ++++++ .../UnitTests/AppliedLayoutsTests.Spec.cpp | 497 +++++++++++------- .../UnitTests/WorkAreaIdTests.Spec.cpp | 10 +- 21 files changed, 860 insertions(+), 495 deletions(-) create mode 100644 src/modules/fancyzones/FancyZonesLib/FancyZonesData/LastUsedVirtualDesktop.cpp create mode 100644 src/modules/fancyzones/FancyZonesLib/FancyZonesData/LastUsedVirtualDesktop.h rename src/modules/fancyzones/FancyZonesLib/{MonitorWorkAreaMap.cpp => WorkAreaConfiguration.cpp} (57%) rename src/modules/fancyzones/FancyZonesLib/{MonitorWorkAreaMap.h => WorkAreaConfiguration.h} (78%) diff --git a/src/modules/fancyzones/FancyZonesLib/EditorParameters.cpp b/src/modules/fancyzones/FancyZonesLib/EditorParameters.cpp index 26397eb1ba..b7a7ea4523 100644 --- a/src/modules/fancyzones/FancyZonesLib/EditorParameters.cpp +++ b/src/modules/fancyzones/FancyZonesLib/EditorParameters.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -36,6 +37,31 @@ namespace JsonUtils int monitorHeight{}; bool isSelected = false; + bool FillFromWorkArea(const WorkArea* const workArea) + { + const auto virtualDesktopIdStr = FancyZonesUtils::GuidToString(workArea->UniqueId().virtualDesktopId); + if (!virtualDesktopIdStr) + { + return false; + } + + const auto& monitorId = workArea->UniqueId().monitorId; + monitorName = monitorId.deviceId.id; + monitorInstanceId = monitorId.deviceId.instanceId; + monitorNumber = monitorId.deviceId.number; + monitorSerialNumber = monitorId.serialNumber; + + const auto& rect = workArea->GetWorkAreaRect(); + top = rect.top(); + left = rect.left(); + workAreaWidth = rect.width(); + workAreaHeight = rect.height(); + + virtualDesktop = virtualDesktopIdStr.value(); + + return true; + } + static json::JsonObject ToJson(const MonitorInfo& monitor) { json::JsonObject result{}; @@ -84,21 +110,8 @@ namespace JsonUtils }; } -bool EditorParameters::Save() noexcept +bool EditorParameters::Save(const WorkAreaConfiguration& configuration, OnThreadExecutor& dpiUnawareThread) noexcept { - const auto virtualDesktopIdStr = FancyZonesUtils::GuidToString(VirtualDesktop::instance().GetCurrentVirtualDesktopId()); - if (!virtualDesktopIdStr) - { - Logger::error(L"Save editor params: invalid virtual desktop id"); - return false; - } - - OnThreadExecutor dpiUnawareThread; - dpiUnawareThread.submit(OnThreadExecutor::task_t{ [] { - SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE); - SetThreadDpiHostingBehavior(DPI_HOSTING_BEHAVIOR_MIXED); - } }).wait(); - const bool spanZonesAcrossMonitors = FancyZonesSettings::settings().spanZonesAcrossMonitors; JsonUtils::EditorArgs argsJson; @@ -107,23 +120,31 @@ bool EditorParameters::Save() noexcept if (spanZonesAcrossMonitors) { - RECT combinedWorkArea; - dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&]() { - combinedWorkArea = FancyZonesUtils::GetAllMonitorsCombinedRect<&MONITORINFOEX::rcWork>(); - - } }).wait(); - - RECT combinedMonitorArea = FancyZonesUtils::GetAllMonitorsCombinedRect<&MONITORINFOEX::rcMonitor>(); + const auto& workArea = configuration.GetWorkArea(nullptr); + if (!workArea) + { + return false; + } JsonUtils::MonitorInfo monitorJson; - monitorJson.monitorName = ZonedWindowProperties::MultiMonitorName; - monitorJson.monitorInstanceId = ZonedWindowProperties::MultiMonitorInstance; - monitorJson.monitorNumber = 0; - monitorJson.virtualDesktop = virtualDesktopIdStr.value(); + if (!monitorJson.FillFromWorkArea(workArea)) + { + return false; + } + + RECT combinedWorkArea; + dpiUnawareThread.submit(OnThreadExecutor::task_t{ + [&]() { + combinedWorkArea = FancyZonesUtils::GetAllMonitorsCombinedRect<&MONITORINFOEX::rcWork>(); + } }).wait(); + RECT combinedMonitorArea = FancyZonesUtils::GetAllMonitorsCombinedRect<&MONITORINFOEX::rcMonitor>(); + + // use dpi-unaware values monitorJson.top = combinedWorkArea.top; monitorJson.left = combinedWorkArea.left; monitorJson.workAreaWidth = combinedWorkArea.right - combinedWorkArea.left; monitorJson.workAreaHeight = combinedWorkArea.bottom - combinedWorkArea.top; + monitorJson.monitorWidth = combinedMonitorArea.right - combinedMonitorArea.left; monitorJson.monitorHeight = combinedMonitorArea.bottom - combinedMonitorArea.top; monitorJson.isSelected = true; @@ -133,8 +154,6 @@ bool EditorParameters::Save() noexcept } else { - auto monitors = MonitorUtils::IdentifyMonitors(); - HMONITOR targetMonitor{}; if (FancyZonesSettings::settings().use_cursorpos_editor_startupscreen) { @@ -153,10 +172,25 @@ bool EditorParameters::Save() noexcept return false; } - for (auto& monitorData : monitors) + const auto& config = configuration.GetAllWorkAreas(); + for (auto& [monitor, workArea] : config) { - HMONITOR monitor = monitorData.monitor; + JsonUtils::MonitorInfo monitorJson; + if (!monitorJson.FillFromWorkArea(workArea.get())) + { + continue; + } + monitorJson.isSelected = monitor == targetMonitor; /* Is monitor selected for the main editor window opening */ + + UINT dpi = 0; + if (DPIAware::GetScreenDPIForMonitor(monitor, dpi) != S_OK) + { + continue; + } + + monitorJson.dpi = dpi; + MONITORINFOEX monitorInfo{}; dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] { monitorInfo.cbSize = sizeof(monitorInfo); @@ -166,31 +200,6 @@ bool EditorParameters::Save() noexcept } } }).wait(); - JsonUtils::MonitorInfo monitorJson; - - if (monitor == targetMonitor) - { - monitorJson.isSelected = true; /* Is monitor selected for the main editor window opening */ - } - - monitorJson.monitorName = monitorData.deviceId.id; - monitorJson.monitorInstanceId = monitorData.deviceId.instanceId; - monitorJson.monitorNumber = monitorData.deviceId.number; - monitorJson.monitorSerialNumber = monitorData.serialNumber; - monitorJson.virtualDesktop = virtualDesktopIdStr.value(); - - UINT dpi = 0; - if (DPIAware::GetScreenDPIForMonitor(monitor, dpi) != S_OK) - { - continue; - } - - monitorJson.dpi = dpi; - monitorJson.top = monitorInfo.rcWork.top; - monitorJson.left = monitorInfo.rcWork.left; - monitorJson.workAreaWidth = monitorInfo.rcWork.right - monitorInfo.rcWork.left; - monitorJson.workAreaHeight = monitorInfo.rcWork.bottom - monitorInfo.rcWork.top; - float width = static_cast(monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left); float height = static_cast(monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top); DPIAware::Convert(monitor, width, height); @@ -198,6 +207,12 @@ bool EditorParameters::Save() noexcept monitorJson.monitorWidth = static_cast(std::roundf(width)); monitorJson.monitorHeight = static_cast(std::roundf(height)); + // use dpi-unaware values + monitorJson.top = monitorInfo.rcWork.top; + monitorJson.left = monitorInfo.rcWork.left; + monitorJson.workAreaWidth = monitorInfo.rcWork.right - monitorInfo.rcWork.left; + monitorJson.workAreaHeight = monitorInfo.rcWork.bottom - monitorInfo.rcWork.top; + argsJson.monitors.emplace_back(std::move(monitorJson)); } } diff --git a/src/modules/fancyzones/FancyZonesLib/EditorParameters.h b/src/modules/fancyzones/FancyZonesLib/EditorParameters.h index 70124613d6..74415e9c28 100644 --- a/src/modules/fancyzones/FancyZonesLib/EditorParameters.h +++ b/src/modules/fancyzones/FancyZonesLib/EditorParameters.h @@ -1,5 +1,8 @@ #pragma once +#include +#include + namespace NonLocalizable { namespace EditorParametersIds @@ -26,5 +29,5 @@ namespace NonLocalizable class EditorParameters { public: - static bool Save() noexcept; + static bool Save(const WorkAreaConfiguration& configuration, OnThreadExecutor& dpiUnawareThread) noexcept; }; diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp index 7ddcd955c5..d2314d7816 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp @@ -15,13 +15,13 @@ #include #include #include +#include #include #include #include #include #include #include -#include #include #include #include @@ -30,6 +30,7 @@ #include #include #include +#include enum class DisplayChangeType { @@ -88,6 +89,7 @@ public: AppliedLayouts::instance().LoadData(); AppZoneHistory::instance().LoadData(); DefaultLayouts::instance().LoadData(); + LastUsedVirtualDesktop::instance().LoadData(); } // IFancyZones @@ -170,7 +172,7 @@ private: HWND m_window{}; std::unique_ptr m_windowMouseSnapper{}; WindowKeyboardSnap m_windowKeyboardSnapper{}; - MonitorWorkAreaMap m_workAreaHandler; + WorkAreaConfiguration m_workAreaConfiguration; DraggingState m_draggingState; wil::unique_handle m_terminateEditorEvent; // Handle of FancyZonesEditor.exe we launch and wait on @@ -254,6 +256,7 @@ FancyZones::Run() noexcept } }); + VirtualDesktop::instance().UpdateVirtualDesktopId(); SyncVirtualDesktops(); // id format of applied-layouts and app-zone-history was changed in 0.60 @@ -268,7 +271,7 @@ FancyZones::Run() noexcept IFACEMETHODIMP_(void) FancyZones::Destroy() noexcept { - m_workAreaHandler.Clear(); + m_workAreaConfiguration.Clear(); BufferedPaintUnInit(); if (m_window) { @@ -290,7 +293,7 @@ FancyZones::VirtualDesktopChanged() noexcept void FancyZones::MoveSizeStart(HWND window, HMONITOR monitor) { - m_windowMouseSnapper = WindowMouseSnap::Create(window, m_workAreaHandler.GetAllWorkAreas()); + m_windowMouseSnapper = WindowMouseSnap::Create(window, m_workAreaConfiguration.GetAllWorkAreas()); if (m_windowMouseSnapper) { if (FancyZonesSettings::settings().spanZonesAcrossMonitors) @@ -330,7 +333,7 @@ void FancyZones::MoveSizeEnd() bool FancyZones::MoveToAppLastZone(HWND window, HMONITOR monitor) noexcept { - const auto& workAreas = m_workAreaHandler.GetAllWorkAreas(); + const auto& workAreas = m_workAreaConfiguration.GetAllWorkAreas(); WorkArea* workArea{ nullptr }; ZoneIndexSet indexes{}; @@ -518,7 +521,7 @@ void FancyZones::ToggleEditor() noexcept m_terminateEditorEvent.reset(CreateEvent(nullptr, true, false, nullptr)); - if (!EditorParameters::Save()) + if (!EditorParameters::Save(m_workAreaConfiguration, m_dpiUnawareThread)) { Logger::error(L"Failed to save editor startup parameters"); return; @@ -618,11 +621,11 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa // Check whether Alt is used in the shortcut key combination if (GetAsyncKeyState(VK_MENU) & 0x8000) { - m_windowKeyboardSnapper.Extend(foregroundWindow, windowRect, monitor, static_cast(lparam), m_workAreaHandler.GetAllWorkAreas()); + m_windowKeyboardSnapper.Extend(foregroundWindow, windowRect, monitor, static_cast(lparam), m_workAreaConfiguration.GetAllWorkAreas()); } else { - m_windowKeyboardSnapper.Snap(foregroundWindow, windowRect, monitor, static_cast(lparam), m_workAreaHandler.GetAllWorkAreas(), monitors); + m_windowKeyboardSnapper.Snap(foregroundWindow, windowRect, monitor, static_cast(lparam), m_workAreaConfiguration.GetAllWorkAreas(), monitors); } } else @@ -632,7 +635,7 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa } else { - m_windowKeyboardSnapper.Snap(foregroundWindow, monitor, static_cast(lparam), m_workAreaHandler.GetAllWorkAreas(), FancyZonesUtils::GetMonitorsOrdered()); + m_windowKeyboardSnapper.Snap(foregroundWindow, monitor, static_cast(lparam), m_workAreaConfiguration.GetAllWorkAreas(), FancyZonesUtils::GetMonitorsOrdered()); } } else if (message == WM_PRIV_INIT) @@ -727,6 +730,7 @@ void FancyZones::OnDisplayChange(DisplayChangeType changeType) noexcept updateWindowsPositions = FancyZonesSettings::settings().displayChange_moveWindows; break; case DisplayChangeType::VirtualDesktop: // Switched virtual desktop + SyncVirtualDesktops(); break; case DisplayChangeType::Initialization: // Initialization updateWindowsPositions = FancyZonesSettings::settings().zoneSetChange_moveWindows; @@ -755,15 +759,18 @@ bool FancyZones::AddWorkArea(HMONITOR monitor, const FancyZonesDataTypes::WorkAr { rect = FancyZonesUtils::GetAllMonitorsCombinedRect<&MONITORINFO::rcWork>(); } - - auto workArea = WorkArea::Create(m_hinstance, id, m_workAreaHandler.GetParent(monitor), rect); + + auto parentWorkAreaId = id; + parentWorkAreaId.virtualDesktopId = LastUsedVirtualDesktop::instance().GetId(); + + auto workArea = WorkArea::Create(m_hinstance, id, parentWorkAreaId, rect); if (!workArea) { Logger::error(L"Failed to create work area {}", id.toString()); return false; } - m_workAreaHandler.AddWorkArea(monitor, std::move(workArea)); + m_workAreaConfiguration.AddWorkArea(monitor, std::move(workArea)); return true; } @@ -785,8 +792,7 @@ void FancyZones::UpdateWorkAreas(bool updateWindowPositions) noexcept { Logger::debug(L"Update work areas, update windows positions: {}", updateWindowPositions); - m_workAreaHandler.SaveParentIds(); - m_workAreaHandler.Clear(); + m_workAreaConfiguration.Clear(); if (FancyZonesSettings::settings().spanZonesAcrossMonitors) { @@ -824,7 +830,7 @@ void FancyZones::UpdateWorkAreas(bool updateWindowPositions) noexcept if (FancyZonesSettings::settings().spanZonesAcrossMonitors) // one work area across monitors { - const auto workArea = m_workAreaHandler.GetWorkArea(nullptr); + const auto workArea = m_workAreaConfiguration.GetWorkArea(nullptr); if (workArea) { for (const auto& [window, zones] : windowsToSnap) @@ -841,7 +847,7 @@ void FancyZones::UpdateWorkAreas(bool updateWindowPositions) noexcept const auto window = iter->first; const auto zones = iter->second; const auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL); - const auto workAreaForMonitor = m_workAreaHandler.GetWorkArea(monitor); + const auto workAreaForMonitor = m_workAreaConfiguration.GetWorkArea(monitor); if (workAreaForMonitor && AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaForMonitor->UniqueId(), workAreaForMonitor->GetLayoutId()) == zones) { workAreaForMonitor->Snap(window, zones, false); @@ -856,7 +862,7 @@ void FancyZones::UpdateWorkAreas(bool updateWindowPositions) noexcept // snap rest of the windows to other work areas (in case they were moved after the monitor unplug) for (const auto& [window, zones] : windowsToSnap) { - for (const auto& [_, workArea] : m_workAreaHandler.GetAllWorkAreas()) + for (const auto& [_, workArea] : m_workAreaConfiguration.GetAllWorkAreas()) { const auto savedIndexes = AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId()); if (savedIndexes == zones) @@ -869,7 +875,7 @@ void FancyZones::UpdateWorkAreas(bool updateWindowPositions) noexcept if (updateWindowPositions) { - for (const auto& [_, workArea] : m_workAreaHandler.GetAllWorkAreas()) + for (const auto& [_, workArea] : m_workAreaConfiguration.GetAllWorkAreas()) { if (workArea) { @@ -884,7 +890,7 @@ void FancyZones::CycleWindows(bool reverse) noexcept auto window = GetForegroundWindow(); HMONITOR current = WorkAreaKeyFromWindow(window); - auto workArea = m_workAreaHandler.GetWorkArea(current); + auto workArea = m_workAreaConfiguration.GetWorkArea(current); if (workArea) { workArea->CycleWindows(window, reverse); @@ -893,15 +899,23 @@ void FancyZones::CycleWindows(bool reverse) noexcept void FancyZones::SyncVirtualDesktops() noexcept { + // Explorer persists current virtual desktop identifier to registry on a per session basis, + // but only after first virtual desktop switch happens. If the user hasn't switched virtual + // desktops in this session value in registry will be empty and we will use default GUID in + // that case (00000000-0000-0000-0000-000000000000). + + auto lastUsed = LastUsedVirtualDesktop::instance().GetId(); + auto current = VirtualDesktop::instance().GetCurrentVirtualDesktopId(); auto guids = VirtualDesktop::instance().GetVirtualDesktopIdsFromRegistry(); - if (guids.has_value()) + + if (current != lastUsed) { - AppZoneHistory::instance().RemoveDeletedVirtualDesktops(*guids); - AppliedLayouts::instance().RemoveDeletedVirtualDesktops(*guids); + LastUsedVirtualDesktop::instance().SetId(current); + LastUsedVirtualDesktop::instance().SaveData(); } - AppZoneHistory::instance().SyncVirtualDesktops(); - AppliedLayouts::instance().SyncVirtualDesktops(); + AppliedLayouts::instance().SyncVirtualDesktops(current, lastUsed, guids); + AppZoneHistory::instance().SyncVirtualDesktops(current, lastUsed, guids); } void FancyZones::UpdateHotkey(int hotkeyId, const PowerToysSettings::HotkeyObject& hotkeyObject, bool enable) noexcept @@ -955,7 +969,7 @@ void FancyZones::SettingsUpdate(SettingId id) break; case SettingId::SpanZonesAcrossMonitors: { - m_workAreaHandler.Clear(); + m_workAreaConfiguration.Clear(); PostMessageW(m_window, WM_PRIV_INIT, NULL, NULL); } break; @@ -966,7 +980,7 @@ void FancyZones::SettingsUpdate(SettingId id) void FancyZones::RefreshLayouts() noexcept { - for (const auto& [_, workArea] : m_workAreaHandler.GetAllWorkAreas()) + for (const auto& [_, workArea] : m_workAreaConfiguration.GetAllWorkAreas()) { if (workArea) { @@ -993,7 +1007,7 @@ bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept { HMONITOR monitor = WorkAreaKeyFromWindow(window); - auto workArea = m_workAreaHandler.GetWorkArea(monitor); + auto workArea = m_workAreaConfiguration.GetWorkArea(monitor); if (!workArea) { Logger::error(L"No work area for processing snap hotkey"); @@ -1038,13 +1052,15 @@ void FancyZones::ApplyQuickLayout(int key) noexcept return; } - auto workArea = m_workAreaHandler.GetWorkAreaFromCursor(); + auto workArea = m_workAreaConfiguration.GetWorkAreaFromCursor(); if (workArea) { - AppliedLayouts::instance().ApplyLayout(workArea->UniqueId(), layout.value()); - AppliedLayouts::instance().SaveData(); - RefreshLayouts(); - FlashZones(); + if (AppliedLayouts::instance().ApplyLayout(workArea->UniqueId(), layout.value())) + { + RefreshLayouts(); + FlashZones(); + AppliedLayouts::instance().SaveData(); + } } } @@ -1052,7 +1068,7 @@ void FancyZones::FlashZones() noexcept { if (FancyZonesSettings::settings().flashZonesOnQuickSwitch && !m_draggingState.IsDragging()) { - for (const auto& [_, workArea] : m_workAreaHandler.GetAllWorkAreas()) + for (const auto& [_, workArea] : m_workAreaConfiguration.GetAllWorkAreas()) { if (workArea) { diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesData.h index 565abb3194..db7fe004a9 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData.h +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData.h @@ -26,13 +26,7 @@ public: return settingsFileName; } -private: #if defined(UNIT_TESTS) - friend class FancyZonesUnitTests::LayoutHotkeysUnitTests; - friend class FancyZonesUnitTests::LayoutTemplatesUnitTests; - friend class FancyZonesUnitTests::CustomLayoutsUnitTests; - friend class FancyZonesUnitTests::AppliedLayoutsUnitTests; - inline void SetSettingsModulePath(std::wstring_view moduleName) { std::wstring result = PTSettingsHelper::get_module_save_folder_location(moduleName); @@ -46,6 +40,8 @@ private: return result + L"\\" + std::wstring(L"zones-settings.json"); } #endif + +private: std::wstring settingsFileName; std::wstring zonesSettingsFileName; std::wstring appZoneHistoryFileName; diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.cpp index 886dec5a84..c633f0c0f7 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.cpp @@ -591,67 +591,43 @@ ZoneIndexSet AppZoneHistory::GetAppLastZoneIndexSet(HWND window, const FancyZone return {}; } -void AppZoneHistory::SyncVirtualDesktops() +void AppZoneHistory::SyncVirtualDesktops(const GUID& currentVirtualDesktop, const GUID& lastUsedVirtualDesktop, std::optional> desktops) { - // Explorer persists current virtual desktop identifier to registry on a per session basis, - // but only after first virtual desktop switch happens. If the user hasn't switched virtual - // desktops in this session value in registry will be empty and we will use default GUID in - // that case (00000000-0000-0000-0000-000000000000). - - auto savedInRegistryVirtualDesktopID = VirtualDesktop::instance().GetCurrentVirtualDesktopIdFromRegistry(); - if (!savedInRegistryVirtualDesktopID.has_value() || savedInRegistryVirtualDesktopID.value() == GUID_NULL) + TAppZoneHistoryMap history; + + std::unordered_set activeDesktops{}; + if (desktops.has_value()) { - return; + activeDesktops = std::unordered_set(std::begin(desktops.value()), std::end(desktops.value())); } - auto currentVirtualDesktopStr = FancyZonesUtils::GuidToString(savedInRegistryVirtualDesktopID.value()); - if (!currentVirtualDesktopStr.has_value()) - { - Logger::error(L"Failed to convert virtual desktop GUID to string"); - return; - } - - Logger::info(L"AppZoneHistory Sync virtual desktops: current {}", currentVirtualDesktopStr.value()); - - bool dirtyFlag = false; - - for (auto& [path, perDesktopData] : m_history) - { - for (auto& data : perDesktopData) + auto findCurrentVirtualDesktopInSavedHistory = [&](const std::pair>& val) -> bool + { + for (auto& data : val.second) { - if (data.workAreaId.virtualDesktopId == GUID_NULL) + if (data.workAreaId.virtualDesktopId == currentVirtualDesktop) { - data.workAreaId.virtualDesktopId = savedInRegistryVirtualDesktopID.value(); - dirtyFlag = true; + return true; } } - } + return false; + }; + bool replaceLastUsedWithCurrent = !desktops.has_value() || currentVirtualDesktop == GUID_NULL || lastUsedVirtualDesktop == GUID_NULL || std::find_if(m_history.begin(), m_history.end(), findCurrentVirtualDesktopInSavedHistory) == m_history.end(); - if (dirtyFlag) - { - Logger::info(L"Update Virtual Desktop id to {}", currentVirtualDesktopStr.value()); - SaveData(); - } -} - -void AppZoneHistory::RemoveDeletedVirtualDesktops(const std::vector& activeDesktops) -{ - std::unordered_set active(std::begin(activeDesktops), std::end(activeDesktops)); bool dirtyFlag = false; - for (auto it = std::begin(m_history); it != std::end(m_history);) { auto& perDesktopData = it->second; for (auto desktopIt = std::begin(perDesktopData); desktopIt != std::end(perDesktopData);) { - if (desktopIt->workAreaId.virtualDesktopId != GUID_NULL && !active.contains(desktopIt->workAreaId.virtualDesktopId)) + if (replaceLastUsedWithCurrent && desktopIt->workAreaId.virtualDesktopId == lastUsedVirtualDesktop) { - auto virtualDesktopIdStr = FancyZonesUtils::GuidToString(desktopIt->workAreaId.virtualDesktopId); - if (virtualDesktopIdStr) - { - Logger::info(L"Remove Virtual Desktop id {} from app-zone-history", virtualDesktopIdStr.value()); - } + desktopIt->workAreaId.virtualDesktopId = currentVirtualDesktop; + dirtyFlag = true; + } + if (desktopIt->workAreaId.virtualDesktopId != currentVirtualDesktop && !activeDesktops.contains(desktopIt->workAreaId.virtualDesktopId)) + { desktopIt = perDesktopData.erase(desktopIt); dirtyFlag = true; } @@ -664,6 +640,7 @@ void AppZoneHistory::RemoveDeletedVirtualDesktops(const std::vector& activ if (perDesktopData.empty()) { it = m_history.erase(it); + dirtyFlag = true; } else { diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.h index 18ec84ac93..6356ab50fa 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.h +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.h @@ -41,6 +41,13 @@ public: #endif } +#if defined(UNIT_TESTS) + inline void SetAppZoneHistory(const TAppZoneHistoryMap& history) + { + m_history = history; + } +#endif + void LoadData(); void SaveData(); void AdjustWorkAreaIds(const std::vector& ids); @@ -56,9 +63,8 @@ public: bool IsAnotherWindowOfApplicationInstanceZoned(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId) const noexcept; ZoneIndexSet GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId) const; - void SyncVirtualDesktops(); - void RemoveDeletedVirtualDesktops(const std::vector& activeDesktops); - + void SyncVirtualDesktops(const GUID& currentVirtualDesktop, const GUID& lastUsedVirtualDesktop, std::optional> desktops); + private: AppZoneHistory(); ~AppZoneHistory() = default; diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppliedLayouts.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppliedLayouts.cpp index a6c86952d6..b05cd3da0b 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppliedLayouts.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppliedLayouts.cpp @@ -279,29 +279,7 @@ void AppliedLayouts::LoadData() void AppliedLayouts::SaveData() { - bool dirtyFlag = false; - TAppliedLayoutsMap updatedMap; - - for (const auto& [id, data] : m_layouts) - { - auto updatedId = id; - if (!VirtualDesktop::instance().IsVirtualDesktopIdSavedInRegistry(id.virtualDesktopId)) - { - updatedId.virtualDesktopId = GUID_NULL; - dirtyFlag = true; - } - - updatedMap.insert({ updatedId, data }); - } - - if (dirtyFlag) - { - json::to_file(AppliedLayoutsFileName(), JsonUtils::SerializeJson(updatedMap)); - } - else - { - json::to_file(AppliedLayoutsFileName(), JsonUtils::SerializeJson(m_layouts)); - } + json::to_file(AppliedLayoutsFileName(), JsonUtils::SerializeJson(m_layouts)); } void AppliedLayouts::AdjustWorkAreaIds(const std::vector& ids) @@ -344,86 +322,75 @@ void AppliedLayouts::AdjustWorkAreaIds(const std::vector> desktops) { - // Explorer persists current virtual desktop identifier to registry on a per session basis, - // but only after first virtual desktop switch happens. If the user hasn't switched virtual - // desktops in this session value in registry will be empty and we will use default GUID in - // that case (00000000-0000-0000-0000-000000000000). + TAppliedLayoutsMap layouts; - auto savedInRegistryVirtualDesktopID = VirtualDesktop::instance().GetCurrentVirtualDesktopIdFromRegistry(); - if (!savedInRegistryVirtualDesktopID.has_value() || savedInRegistryVirtualDesktopID.value() == GUID_NULL) + auto findCurrentVirtualDesktopInSavedLayouts = [&](const std::pair& val) -> bool { return val.first.virtualDesktopId == currentVirtualDesktop; }; + bool replaceLastUsedWithCurrent = !desktops.has_value() || currentVirtualDesktop == GUID_NULL || + std::find_if(m_layouts.begin(), m_layouts.end(), findCurrentVirtualDesktopInSavedLayouts) == m_layouts.end(); + bool copyToOtherVirtualDesktops = lastUsedVirtualDesktop == GUID_NULL && currentVirtualDesktop != GUID_NULL && desktops.has_value(); + + for (const auto& [workAreaId, layout] : m_layouts) { - return; - } - - auto currentVirtualDesktopStr = FancyZonesUtils::GuidToString(savedInRegistryVirtualDesktopID.value()); - if (!currentVirtualDesktopStr.has_value()) - { - Logger::error(L"Failed to convert virtual desktop GUID to string"); - return; - } - - Logger::info(L"AppliedLayouts Sync virtual desktops: current {}", currentVirtualDesktopStr.value()); - - bool dirtyFlag = false; - - std::vector replaceWithCurrentId{}; - - for (const auto& [id, data] : m_layouts) - { - if (id.virtualDesktopId == GUID_NULL) + if (replaceLastUsedWithCurrent && workAreaId.virtualDesktopId == lastUsedVirtualDesktop) { - replaceWithCurrentId.push_back(id); - dirtyFlag = true; - } - } + // replace "lastUsedVirtualDesktop" with "currentVirtualDesktop" + auto updatedWorkAreaId = workAreaId; + updatedWorkAreaId.virtualDesktopId = currentVirtualDesktop; + layouts.insert({ updatedWorkAreaId, layout }); - for (const auto& id : replaceWithCurrentId) - { - auto mapEntry = m_layouts.extract(id); - mapEntry.key().virtualDesktopId = savedInRegistryVirtualDesktopID.value(); - m_layouts.insert(std::move(mapEntry)); - } - - if (dirtyFlag) - { - Logger::info(L"Update Virtual Desktop id to {}", currentVirtualDesktopStr.value()); - SaveData(); - } -} - -void AppliedLayouts::RemoveDeletedVirtualDesktops(const std::vector& activeDesktops) -{ - std::unordered_set active(std::begin(activeDesktops), std::end(activeDesktops)); - bool dirtyFlag = false; - - for (auto it = std::begin(m_layouts); it != std::end(m_layouts);) - { - GUID desktopId = it->first.virtualDesktopId; - - if (desktopId != GUID_NULL) - { - auto foundId = active.find(desktopId); - if (foundId == std::end(active)) + if (copyToOtherVirtualDesktops) { - wil::unique_cotaskmem_string virtualDesktopIdStr; - if (SUCCEEDED(StringFromCLSID(desktopId, &virtualDesktopIdStr))) + // Copy to other virtual desktops on the 1st VD creation. + // If we just replace the id, we'll lose the layout on the other desktops. + // Usage scenario: + // apply the layout to the single virtual desktop with id = GUID_NULL, + // create the 2nd virtual desktop and switch to it, + // so virtual desktop id changes from GUID_NULL to a valid value of the 2nd VD. + // Then change the layout on the 2nd VD and switch back to the 1st VD. + // Layout on the initial VD will be changed too without initializing it beforehand. + for (const auto& id : desktops.value()) { - Logger::info(L"Remove Virtual Desktop id {}", virtualDesktopIdStr.get()); + if (id != currentVirtualDesktop) + { + auto copyWorkAreaId = workAreaId; + copyWorkAreaId.virtualDesktopId = id; + layouts.insert({ copyWorkAreaId, layout }); + } } - - it = m_layouts.erase(it); - dirtyFlag = true; - continue; } } - ++it; + + if (workAreaId.virtualDesktopId == currentVirtualDesktop || (desktops.has_value() && + std::find(desktops.value().begin(), desktops.value().end(), workAreaId.virtualDesktopId) != desktops.value().end())) + { + // keep only actual virtual desktop values + layouts.insert({ workAreaId, layout }); + } } - if (dirtyFlag) + if (layouts != m_layouts) { + m_layouts = layouts; SaveData(); + + std::wstring currentStr = FancyZonesUtils::GuidToString(currentVirtualDesktop).value_or(L"incorrect guid"); + std::wstring lastUsedStr = FancyZonesUtils::GuidToString(lastUsedVirtualDesktop).value_or(L"incorrect guid"); + std::wstring registryStr{}; + if (desktops.has_value()) + { + for (const auto& id : desktops.value()) + { + registryStr += FancyZonesUtils::GuidToString(id).value_or(L"incorrect guid") + L" "; + } + } + else + { + registryStr = L"empty"; + } + + Logger::info(L"Synced virtual desktops. Current: {}, last used: {}, registry: {}", currentStr, lastUsedStr, registryStr); } } @@ -449,9 +416,9 @@ bool AppliedLayouts::IsLayoutApplied(const FancyZonesDataTypes::WorkAreaId& id) return iter != m_layouts.end(); } -bool AppliedLayouts::ApplyLayout(const FancyZonesDataTypes::WorkAreaId& deviceId, LayoutData layout) +bool AppliedLayouts::ApplyLayout(const FancyZonesDataTypes::WorkAreaId& workAreaId, LayoutData layout) { - m_layouts[deviceId] = std::move(layout); + m_layouts[workAreaId] = layout; return true; } @@ -496,9 +463,5 @@ bool AppliedLayouts::CloneLayout(const FancyZonesDataTypes::WorkAreaId& srcId, c } Logger::info(L"Clone layout from {} to {}", dstId.toString(), srcId.toString()); - m_layouts[dstId] = m_layouts[srcId]; - - SaveData(); - - return true; + return ApplyLayout(dstId, m_layouts[srcId]); } diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppliedLayouts.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppliedLayouts.h index bea37c6d92..e4f796913b 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppliedLayouts.h +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppliedLayouts.h @@ -49,12 +49,18 @@ public: #endif } +#if defined(UNIT_TESTS) + inline void SetAppliedLayouts(TAppliedLayoutsMap layouts) + { + m_layouts = layouts; + } +#endif + void LoadData(); void SaveData(); void AdjustWorkAreaIds(const std::vector& ids); - void SyncVirtualDesktops(); - void RemoveDeletedVirtualDesktops(const std::vector& activeDesktops); + void SyncVirtualDesktops(const GUID& currentVirtualDesktop, const GUID& lastUsedVirtualDesktop, std::optional> desktops); std::optional GetDeviceLayout(const FancyZonesDataTypes::WorkAreaId& id) const noexcept; const TAppliedLayoutsMap& GetAppliedLayoutMap() const noexcept; diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/LastUsedVirtualDesktop.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/LastUsedVirtualDesktop.cpp new file mode 100644 index 0000000000..3c62df9e88 --- /dev/null +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/LastUsedVirtualDesktop.cpp @@ -0,0 +1,78 @@ +#include "../pch.h" +#include "LastUsedVirtualDesktop.h" + +#include + +#include + +namespace JsonUtils +{ + GUID ParseJson(const json::JsonObject& json) + { + auto idStr = json.GetNamedString(NonLocalizable::LastUsedVirtualDesktop::LastUsedVirtualDesktopID); + auto idOpt = FancyZonesUtils::GuidFromString(idStr.c_str()); + + if (!idOpt.has_value()) + { + return {}; + } + + return idOpt.value(); + } + + json::JsonObject SerializeJson(const GUID& id) + { + json::JsonObject result{}; + + auto virtualDesktopStr = FancyZonesUtils::GuidToString(id); + if (virtualDesktopStr) + { + result.SetNamedValue(NonLocalizable::LastUsedVirtualDesktop::LastUsedVirtualDesktopID, json::value(virtualDesktopStr.value())); + } + + return result; + } +} + + +LastUsedVirtualDesktop& LastUsedVirtualDesktop::instance() +{ + static LastUsedVirtualDesktop self; + return self; +} + +void LastUsedVirtualDesktop::LoadData() +{ + auto data = json::from_file(LastUsedVirtualDesktopFileName()); + + try + { + if (data) + { + m_id = JsonUtils::ParseJson(data.value()); + } + else + { + m_id = GUID_NULL; + } + } + catch (const winrt::hresult_error& e) + { + Logger::error(L"Parsing last-used-virtual-desktop error: {}", e.message()); + } +} + +void LastUsedVirtualDesktop::SaveData() const +{ + json::to_file(LastUsedVirtualDesktopFileName(), JsonUtils::SerializeJson(m_id)); +} + +GUID LastUsedVirtualDesktop::GetId() const +{ + return m_id; +} + +void LastUsedVirtualDesktop::SetId(GUID id) +{ + m_id = id; +} \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/LastUsedVirtualDesktop.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/LastUsedVirtualDesktop.h new file mode 100644 index 0000000000..da9e07eca4 --- /dev/null +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/LastUsedVirtualDesktop.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include + +namespace NonLocalizable +{ + namespace LastUsedVirtualDesktop + { + const static wchar_t* LastUsedVirtualDesktopID = L"last-used-virtual-desktop"; + } +} + +class LastUsedVirtualDesktop +{ +public: + static LastUsedVirtualDesktop& instance(); + + inline static std::wstring LastUsedVirtualDesktopFileName() + { + std::wstring saveFolderPath = PTSettingsHelper::get_module_save_folder_location(NonLocalizable::ModuleKey); +#if defined(UNIT_TESTS) + return saveFolderPath + L"\\test-last-used-virtual-desktop.json"; +#else + return saveFolderPath + L"\\last-used-virtual-desktop.json"; +#endif + } + + void LoadData(); + void SaveData() const; + + GUID GetId() const; + void SetId(GUID id); + +private: + LastUsedVirtualDesktop() = default; + ~LastUsedVirtualDesktop() = default; + + GUID m_id{}; +}; diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.h index 6f61b3e125..58673c7012 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.h +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.h @@ -209,14 +209,12 @@ namespace FancyZonesDataTypes inline bool operator==(const WorkAreaId& lhs, const WorkAreaId& rhs) { - bool vdEqual = (lhs.virtualDesktopId == rhs.virtualDesktopId || lhs.virtualDesktopId == GUID_NULL || rhs.virtualDesktopId == GUID_NULL); - return vdEqual && lhs.monitorId == rhs.monitorId; + return lhs.virtualDesktopId == rhs.virtualDesktopId && lhs.monitorId == rhs.monitorId; } inline bool operator!=(const WorkAreaId& lhs, const WorkAreaId& rhs) { - bool vdEqual = (lhs.virtualDesktopId == rhs.virtualDesktopId || lhs.virtualDesktopId == GUID_NULL || rhs.virtualDesktopId == GUID_NULL); - return !vdEqual || lhs.monitorId != rhs.monitorId; + return lhs.virtualDesktopId != rhs.virtualDesktopId || lhs.monitorId != rhs.monitorId; } inline bool operator<(const WorkAreaId& lhs, const WorkAreaId& rhs) @@ -240,6 +238,11 @@ namespace FancyZonesDataTypes return lhs.monitorId.deviceId < rhs.monitorId.deviceId; } + + inline bool operator==(const AppZoneHistoryData& lhs, const AppZoneHistoryData& rhs) + { + return lhs.layoutId == rhs.layoutId && lhs.workAreaId == rhs.workAreaId && lhs.zoneIndexSet == rhs.zoneIndexSet && lhs.processIdToHandleMap == rhs.processIdToHandleMap; + } } namespace std diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj index a659a1839e..faf124b3ef 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj @@ -43,6 +43,7 @@ + @@ -59,7 +60,7 @@ - + @@ -100,6 +101,9 @@ ../pch.h + + ../pch.h + ../pch.h @@ -115,7 +119,7 @@ - + Create diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters index f97b95c5a9..220b0cc22f 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters @@ -54,7 +54,7 @@ Header Files - + Header Files @@ -168,6 +168,9 @@ Header Files + + Header Files\FancyZonesData + @@ -200,7 +203,7 @@ Source Files - + Source Files @@ -272,6 +275,9 @@ Source Files + + Source Files\FancyZonesData + Source Files diff --git a/src/modules/fancyzones/FancyZonesLib/VirtualDesktop.cpp b/src/modules/fancyzones/FancyZonesLib/VirtualDesktop.cpp index 67bf726d2b..dbd1511d5a 100644 --- a/src/modules/fancyzones/FancyZonesLib/VirtualDesktop.cpp +++ b/src/modules/fancyzones/FancyZonesLib/VirtualDesktop.cpp @@ -270,30 +270,16 @@ GUID VirtualDesktop::GetCurrentVirtualDesktopId() const noexcept return m_currentVirtualDesktopId; } -GUID VirtualDesktop::GetPreviousVirtualDesktopId() const noexcept -{ - return m_previousDesktopId; -} - void VirtualDesktop::UpdateVirtualDesktopId() noexcept { - m_previousDesktopId = m_currentVirtualDesktopId; - auto currentVirtualDesktopId = GetCurrentVirtualDesktopIdFromRegistry(); - if (!currentVirtualDesktopId.has_value()) - { - Logger::info("No Virtual Desktop Id found in registry"); - currentVirtualDesktopId = VirtualDesktop::instance().GetDesktopIdByTopLevelWindows(); - } - if (currentVirtualDesktopId.has_value()) { - m_currentVirtualDesktopId = *currentVirtualDesktopId; - - if (m_currentVirtualDesktopId == GUID_NULL) - { - Logger::warn("Couldn't retrieve virtual desktop id"); - } + m_currentVirtualDesktopId = currentVirtualDesktopId.value(); + } + else + { + m_currentVirtualDesktopId = GUID_NULL; } Trace::VirtualDesktopChanged(); diff --git a/src/modules/fancyzones/FancyZonesLib/VirtualDesktop.h b/src/modules/fancyzones/FancyZonesLib/VirtualDesktop.h index 069e1883ea..4c25518ce8 100644 --- a/src/modules/fancyzones/FancyZonesLib/VirtualDesktop.h +++ b/src/modules/fancyzones/FancyZonesLib/VirtualDesktop.h @@ -7,7 +7,6 @@ public: // saved values GUID GetCurrentVirtualDesktopId() const noexcept; - GUID GetPreviousVirtualDesktopId() const noexcept; void UpdateVirtualDesktopId() noexcept; // IVirtualDesktopManager @@ -29,7 +28,6 @@ private: IVirtualDesktopManager* m_vdManager{nullptr}; GUID m_currentVirtualDesktopId{}; - GUID m_previousDesktopId{}; std::optional> GetVirtualDesktopIdsFromRegistry(HKEY hKey) const; }; diff --git a/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp b/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp index 2d4b6adf90..638776c25a 100644 --- a/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp +++ b/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp @@ -244,14 +244,12 @@ void WorkArea::InitLayout(const FancyZonesDataTypes::WorkAreaId& parentUniqueId) const bool isLayoutAlreadyApplied = AppliedLayouts::instance().IsLayoutApplied(m_uniqueId); if (!isLayoutAlreadyApplied) { - if (parentUniqueId.virtualDesktopId != GUID_NULL) - { - AppliedLayouts::instance().CloneLayout(parentUniqueId, m_uniqueId); - } - else + if (!AppliedLayouts::instance().CloneLayout(parentUniqueId, m_uniqueId)) { AppliedLayouts::instance().ApplyDefaultLayout(m_uniqueId); } + + AppliedLayouts::instance().SaveData(); } CalculateZoneSet(); diff --git a/src/modules/fancyzones/FancyZonesLib/MonitorWorkAreaMap.cpp b/src/modules/fancyzones/FancyZonesLib/WorkAreaConfiguration.cpp similarity index 57% rename from src/modules/fancyzones/FancyZonesLib/MonitorWorkAreaMap.cpp rename to src/modules/fancyzones/FancyZonesLib/WorkAreaConfiguration.cpp index 486e2739ea..a3cfa7cf45 100644 --- a/src/modules/fancyzones/FancyZonesLib/MonitorWorkAreaMap.cpp +++ b/src/modules/fancyzones/FancyZonesLib/WorkAreaConfiguration.cpp @@ -1,9 +1,9 @@ #include "pch.h" -#include "MonitorWorkAreaMap.h" +#include "WorkAreaConfiguration.h" #include -WorkArea* const MonitorWorkAreaMap::GetWorkArea(HMONITOR monitor) const +WorkArea* const WorkAreaConfiguration::GetWorkArea(HMONITOR monitor) const { auto iter = m_workAreaMap.find(monitor); if (iter != m_workAreaMap.end()) @@ -14,7 +14,7 @@ WorkArea* const MonitorWorkAreaMap::GetWorkArea(HMONITOR monitor) const return nullptr; } -WorkArea* const MonitorWorkAreaMap::GetWorkAreaFromCursor() const +WorkArea* const WorkAreaConfiguration::GetWorkAreaFromCursor() const { const auto allMonitorsWorkArea = GetWorkArea(nullptr); if (allMonitorsWorkArea) @@ -35,7 +35,7 @@ WorkArea* const MonitorWorkAreaMap::GetWorkAreaFromCursor() const } } -WorkArea* const MonitorWorkAreaMap::GetWorkAreaFromWindow(HWND window) const +WorkArea* const WorkAreaConfiguration::GetWorkAreaFromWindow(HWND window) const { const auto allMonitorsWorkArea = GetWorkArea(nullptr); if (allMonitorsWorkArea) @@ -51,39 +51,17 @@ WorkArea* const MonitorWorkAreaMap::GetWorkAreaFromWindow(HWND window) const } } -const std::unordered_map>& MonitorWorkAreaMap::GetAllWorkAreas() const noexcept +const std::unordered_map>& WorkAreaConfiguration::GetAllWorkAreas() const noexcept { return m_workAreaMap; } -void MonitorWorkAreaMap::AddWorkArea(HMONITOR monitor, std::unique_ptr workArea) +void WorkAreaConfiguration::AddWorkArea(HMONITOR monitor, std::unique_ptr workArea) { m_workAreaMap.insert({ monitor, std::move(workArea) }); } -FancyZonesDataTypes::WorkAreaId MonitorWorkAreaMap::GetParent(HMONITOR monitor) const -{ - if (m_workAreaParents.contains(monitor)) - { - return m_workAreaParents.at(monitor); - } - - return FancyZonesDataTypes::WorkAreaId{}; -} - -void MonitorWorkAreaMap::SaveParentIds() -{ - m_workAreaParents.clear(); - for (const auto& [monitor, workArea] : m_workAreaMap) - { - if (workArea) - { - m_workAreaParents.insert({ monitor, workArea->UniqueId() }); - } - } -} - -void MonitorWorkAreaMap::Clear() noexcept +void WorkAreaConfiguration::Clear() noexcept { m_workAreaMap.clear(); } diff --git a/src/modules/fancyzones/FancyZonesLib/MonitorWorkAreaMap.h b/src/modules/fancyzones/FancyZonesLib/WorkAreaConfiguration.h similarity index 78% rename from src/modules/fancyzones/FancyZonesLib/MonitorWorkAreaMap.h rename to src/modules/fancyzones/FancyZonesLib/WorkAreaConfiguration.h index 45fabda709..9530dbdae3 100644 --- a/src/modules/fancyzones/FancyZonesLib/MonitorWorkAreaMap.h +++ b/src/modules/fancyzones/FancyZonesLib/WorkAreaConfiguration.h @@ -1,15 +1,14 @@ #pragma once -#include "GuidUtils.h" #include class WorkArea; -class MonitorWorkAreaMap +class WorkAreaConfiguration { public: /** - * Get work area based on virtual desktop id and monitor handle. + * Get work area based on monitor handle. * * @param[in] monitor Monitor handle. * @@ -19,7 +18,7 @@ public: WorkArea* const GetWorkArea(HMONITOR monitor) const; /** - * Get work area based on virtual desktop id and the current cursor position. + * Get work area based on the current cursor position. * * @returns Object representing single work area, interface to all actions available on work area * (e.g. moving windows through zone layout specified for that work area). @@ -49,13 +48,6 @@ public: */ void AddWorkArea(HMONITOR monitor, std::unique_ptr workArea); - FancyZonesDataTypes::WorkAreaId GetParent(HMONITOR monitor) const; - - /** - * Saving current work area IDs as parents for later use. - */ - void SaveParentIds(); - /** * Clear all persisted work area related data. */ @@ -63,5 +55,4 @@ public: private: std::unordered_map> m_workAreaMap; - std::unordered_map m_workAreaParents{}; }; diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/AppZoneHistoryTests.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/AppZoneHistoryTests.Spec.cpp index c3e954fb00..c3eea23236 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/AppZoneHistoryTests.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/AppZoneHistoryTests.Spec.cpp @@ -339,4 +339,159 @@ namespace FancyZonesUnitTests Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(nullptr, workAreaId, layoutId)); } }; + + TEST_CLASS (AppZoneHistorySyncVirtualDesktops) + { + const GUID virtualDesktop1 = FancyZonesUtils::GuidFromString(L"{30387C86-BB15-476D-8683-AF93F6D73E99}").value(); + const GUID virtualDesktop2 = FancyZonesUtils::GuidFromString(L"{65F6343A-868F-47EE-838E-55A178A7FB7A}").value(); + const GUID deletedVirtualDesktop = FancyZonesUtils::GuidFromString(L"{2D9F3E2D-F61D-4618-B35D-85C9B8DFDFD8}").value(); + + FancyZonesDataTypes::WorkAreaId GetWorkAreaID(GUID virtualDesktop) + { + return FancyZonesDataTypes::WorkAreaId{ + .monitorId = { + .deviceId = { .id = L"id", .instanceId = L"id", .number = 1 }, + .serialNumber = L"serial-number" + }, + .virtualDesktopId = virtualDesktop + }; + } + + FancyZonesDataTypes::AppZoneHistoryData GetAppZoneHistoryData(GUID virtualDesktop, const std::wstring& layoutId, const ZoneIndexSet& zones) + { + return FancyZonesDataTypes::AppZoneHistoryData{ + .layoutId = FancyZonesUtils::GuidFromString(layoutId).value(), + .workAreaId = GetWorkAreaID(virtualDesktop), + .zoneIndexSet = zones + }; + }; + + TEST_METHOD_INITIALIZE(Init) + { + AppZoneHistory::instance().LoadData(); + } + + TEST_METHOD_CLEANUP(CleanUp) + { + std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName()); + } + + TEST_METHOD (SyncVirtualDesktops_SwitchVirtualDesktop) + { + AppZoneHistory::TAppZoneHistoryMap history{}; + const std::wstring app = L"app"; + history.insert({ app, std::vector{ + GetAppZoneHistoryData(virtualDesktop1, L"{147243D0-1111-4225-BCD3-31029FE384FC}", { 0 }), + GetAppZoneHistoryData(virtualDesktop2, L"{EAC1BB3B-13D6-4839-BBF7-58C3E8AB7229}", { 1 }), + } }); + AppZoneHistory::instance().SetAppZoneHistory(history); + + GUID currentVirtualDesktop = virtualDesktop1; + GUID lastUsedVirtualDesktop = virtualDesktop2; + std::optional> virtualDesktopsInRegistry = { { virtualDesktop1, virtualDesktop2 } }; + AppZoneHistory::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry); + + Assert::IsTrue(history.at(app)[0] == AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(virtualDesktop1)).value()); + Assert::IsTrue(history.at(app)[1] == AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(virtualDesktop2)).value()); + } + + TEST_METHOD (SyncVirtualDesktops_CurrentVirtualDesktopDeleted) + { + AppZoneHistory::TAppZoneHistoryMap history{}; + const std::wstring app = L"app"; + history.insert({ app, std::vector{ + GetAppZoneHistoryData(virtualDesktop1, L"{147243D0-1111-4225-BCD3-31029FE384FC}", { 0 }), + GetAppZoneHistoryData(deletedVirtualDesktop, L"{EAC1BB3B-13D6-4839-BBF7-58C3E8AB7229}", { 1 }), + } }); + AppZoneHistory::instance().SetAppZoneHistory(history); + + GUID currentVirtualDesktop = virtualDesktop1; + GUID lastUsedVirtualDesktop = deletedVirtualDesktop; + std::optional> virtualDesktopsInRegistry = { { virtualDesktop1 } }; + AppZoneHistory::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry); + + Assert::IsTrue(history.at(app)[0] == AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(virtualDesktop1)).value()); + Assert::IsFalse(AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(deletedVirtualDesktop)).has_value()); + } + + TEST_METHOD (SyncVirtualDesktops_NotCurrentVirtualDesktopDeleted) + { + AppZoneHistory::TAppZoneHistoryMap history{}; + const std::wstring app = L"app"; + history.insert({ app, std::vector{ + GetAppZoneHistoryData(virtualDesktop1, L"{147243D0-1111-4225-BCD3-31029FE384FC}", { 0 }), + GetAppZoneHistoryData(deletedVirtualDesktop, L"{EAC1BB3B-13D6-4839-BBF7-58C3E8AB7229}", { 1 }), + } }); + AppZoneHistory::instance().SetAppZoneHistory(history); + + GUID currentVirtualDesktop = virtualDesktop1; + GUID lastUsedVirtualDesktop = virtualDesktop1; + std::optional> virtualDesktopsInRegistry = { { virtualDesktop1 } }; + AppZoneHistory::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry); + + Assert::IsTrue(history.at(app)[0] == AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(virtualDesktop1)).value()); + Assert::IsFalse(AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(deletedVirtualDesktop)).has_value()); + } + + TEST_METHOD (SyncVirtualDesktops_AllIdsFromRegistryAreNew) + { + AppZoneHistory::TAppZoneHistoryMap history{}; + const std::wstring app = L"app"; + history.insert({ app, std::vector{ + GetAppZoneHistoryData(deletedVirtualDesktop, L"{147243D0-1111-4225-BCD3-31029FE384FC}", { 0 }), + } }); + AppZoneHistory::instance().SetAppZoneHistory(history); + + GUID currentVirtualDesktop = virtualDesktop1; + GUID lastUsedVirtualDesktop = deletedVirtualDesktop; + std::optional> virtualDesktopsInRegistry = { { virtualDesktop1, virtualDesktop2 } }; + AppZoneHistory::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry); + + auto expected = history.at(app)[0]; + expected.workAreaId.virtualDesktopId = currentVirtualDesktop; + Assert::IsTrue(expected == AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(virtualDesktop1)).value()); + Assert::IsFalse(AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(virtualDesktop2)).has_value()); + Assert::IsFalse(AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(deletedVirtualDesktop)).has_value()); + } + + TEST_METHOD (SyncVirtualDesktop_NoDesktopsInRegistry) + { + AppZoneHistory::TAppZoneHistoryMap history{}; + const std::wstring app = L"app"; + history.insert({ app, std::vector{ + GetAppZoneHistoryData(deletedVirtualDesktop, L"{147243D0-1111-4225-BCD3-31029FE384FC}", { 0 }), + } }); + AppZoneHistory::instance().SetAppZoneHistory(history); + + GUID currentVirtualDesktop = GUID_NULL; + GUID lastUsedVirtualDesktop = deletedVirtualDesktop; + std::optional> virtualDesktopsInRegistry = std::nullopt; + AppZoneHistory::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry); + + auto expected = history.at(app)[0]; + expected.workAreaId.virtualDesktopId = currentVirtualDesktop; + Assert::IsTrue(expected == AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(currentVirtualDesktop)).value()); + Assert::IsFalse(AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(deletedVirtualDesktop)).has_value()); + } + + TEST_METHOD (SyncVirtualDesktop_SwithVirtualDesktopFirstTime) + { + AppZoneHistory::TAppZoneHistoryMap history{}; + const std::wstring app = L"app"; + history.insert({ app, std::vector{ + GetAppZoneHistoryData(GUID_NULL, L"{147243D0-1111-4225-BCD3-31029FE384FC}", { 0 }), + } }); + AppZoneHistory::instance().SetAppZoneHistory(history); + + GUID currentVirtualDesktop = virtualDesktop1; + GUID lastUsedVirtualDesktop = GUID_NULL; + std::optional> virtualDesktopsInRegistry = { { virtualDesktop1, virtualDesktop2 } }; + AppZoneHistory::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry); + + auto expected = history.at(app)[0]; + expected.workAreaId.virtualDesktopId = currentVirtualDesktop; + Assert::IsTrue(expected == AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(currentVirtualDesktop)).value()); + } + }; + } diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/AppliedLayoutsTests.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/AppliedLayoutsTests.Spec.cpp index 87f5da5a3c..ec9256664b 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/AppliedLayoutsTests.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/AppliedLayoutsTests.Spec.cpp @@ -14,24 +14,14 @@ namespace FancyZonesUnitTests { TEST_CLASS (AppliedLayoutsUnitTests) { - FancyZonesData& m_fzData = FancyZonesDataInstance(); - std::wstring m_testFolder = L"FancyZonesUnitTests"; - std::wstring m_testFolderPath = PTSettingsHelper::get_module_save_folder_location(m_testFolder); - TEST_METHOD_INITIALIZE(Init) { - m_fzData.SetSettingsModulePath(L"FancyZonesUnitTests"); + AppliedLayouts::instance().LoadData(); } TEST_METHOD_CLEANUP(CleanUp) { - // Move...FromZonesSettings creates all of these files, clean up - std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName()); - std::filesystem::remove(CustomLayouts::CustomLayoutsFileName()); - std::filesystem::remove(LayoutHotkeys::LayoutHotkeysFileName()); - std::filesystem::remove(LayoutTemplates::LayoutTemplatesFileName()); - std::filesystem::remove_all(m_testFolderPath); - AppliedLayouts::instance().LoadData(); // clean data + std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName()); } TEST_METHOD (AppliedLayoutsParse) @@ -75,7 +65,7 @@ namespace FancyZonesUnitTests Assert::IsTrue(AppliedLayouts::instance().IsLayoutApplied(id)); } - TEST_METHOD(AppliedLayoutsParseDataWithResolution) + TEST_METHOD (AppliedLayoutsParseDataWithResolution) { // prepare json::JsonObject root{}; @@ -242,143 +232,94 @@ namespace FancyZonesUnitTests Assert::IsTrue(AppliedLayouts::instance().GetAppliedLayoutMap().empty()); } - TEST_METHOD (MoveAppliedLayoutsFromZonesSettings) + TEST_METHOD (Save) { - // prepare - json::JsonObject root{}; - json::JsonArray devicesArray{}, customLayoutsArray{}, templateLayoutsArray{}, quickLayoutKeysArray{}; - - { - json::JsonObject activeZoneset{}; - activeZoneset.SetNamedValue(L"uuid", json::value(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}")); - activeZoneset.SetNamedValue(L"type", json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Rows))); - - json::JsonObject obj{}; - obj.SetNamedValue(L"device-id", json::value(L"VSC9636#5&37ac4db&0&UID160005_3840_2160_{00000000-0000-0000-0000-000000000000}")); - obj.SetNamedValue(L"active-zoneset", activeZoneset);; - obj.SetNamedValue(L"editor-show-spacing", json::value(true)); - obj.SetNamedValue(L"editor-spacing", json::value(3)); - obj.SetNamedValue(L"editor-zone-count", json::value(4)); - obj.SetNamedValue(L"editor-sensitivity-radius", json::value(22)); - - devicesArray.Append(obj); - } - - root.SetNamedValue(L"devices", devicesArray); - root.SetNamedValue(L"custom-zone-sets", customLayoutsArray); - root.SetNamedValue(L"templates", templateLayoutsArray); - root.SetNamedValue(L"quick-layout-keys", quickLayoutKeysArray); - json::to_file(m_fzData.GetZoneSettingsPath(m_testFolder), root); - - // test - m_fzData.ReplaceZoneSettingsFileFromOlderVersions(); - AppliedLayouts::instance().LoadData(); - Assert::AreEqual((size_t)1, AppliedLayouts::instance().GetAppliedLayoutMap().size()); - - FancyZonesDataTypes::WorkAreaId id{ - .monitorId = { .deviceId = { .id = L"VSC9636", .instanceId = L"5&37ac4db&0&UID160005" }, .serialNumber = L"" }, - .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{00000000-0000-0000-0000-000000000000}").value() + FancyZonesDataTypes::WorkAreaId workAreaId1{ + .monitorId = { + .deviceId = { .id = L"id-1", .instanceId = L"id-1", .number = 1 }, + .serialNumber = L"serial-number-1" + }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{30387C86-BB15-476D-8683-AF93F6D73E99}").value() + }; + FancyZonesDataTypes::WorkAreaId workAreaId2{ + .monitorId = { + .deviceId = { .id = L"id-2", .instanceId = L"id-2", .number = 2 }, + .serialNumber = L"serial-number-2" }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{30387C86-BB15-476D-8683-AF93F6D73E99}").value() + }; + FancyZonesDataTypes::WorkAreaId workAreaId3{ + .monitorId = { + .deviceId = { .id = L"id-1", .instanceId = L"id-1", .number = 1 }, + .serialNumber = L"serial-number-1" }, + .virtualDesktopId = GUID_NULL + }; + FancyZonesDataTypes::WorkAreaId workAreaId4{ + .monitorId = { + .deviceId = { .id = L"id-2", .instanceId = L"id-2", .number = 2 }, + .serialNumber = L"serial-number-2" }, + .virtualDesktopId = GUID_NULL }; - Assert::IsTrue(AppliedLayouts::instance().GetDeviceLayout(id).has_value()); - } - TEST_METHOD (MoveAppliedLayoutsFromZonesSettingsNoAppliedLayoutsData) - { - // prepare - json::JsonObject root{}; - json::JsonArray customLayoutsArray{}, templateLayoutsArray{}, quickLayoutKeysArray{}; - root.SetNamedValue(L"custom-zone-sets", customLayoutsArray); - root.SetNamedValue(L"templates", templateLayoutsArray); - root.SetNamedValue(L"quick-layout-keys", quickLayoutKeysArray); - json::to_file(m_fzData.GetZoneSettingsPath(m_testFolder), root); + LayoutData layout1{ .uuid = FancyZonesUtils::GuidFromString(L"{D7DBECFA-23FC-4F45-9B56-51CFA9F6ABA2}").value() }; + LayoutData layout2{ .uuid = FancyZonesUtils::GuidFromString(L"{B9EDB48C-EC48-4E82-993F-A15DC1FF09D3}").value() }; + LayoutData layout3{ .uuid = FancyZonesUtils::GuidFromString(L"{94CF0000-7814-4D72-9624-794060FA269C}").value() }; + LayoutData layout4{ .uuid = FancyZonesUtils::GuidFromString(L"{13FA7ADF-1B6C-4FB6-8142-254B77C128E2}").value() }; + + AppliedLayouts::TAppliedLayoutsMap expected{}; + expected.insert({ workAreaId1, layout1 }); + expected.insert({ workAreaId2, layout2 }); + expected.insert({ workAreaId3, layout3 }); + expected.insert({ workAreaId4, layout4 }); + + AppliedLayouts::instance().SetAppliedLayouts(expected); + AppliedLayouts::instance().SaveData(); - // test - m_fzData.ReplaceZoneSettingsFileFromOlderVersions(); AppliedLayouts::instance().LoadData(); - Assert::IsTrue(AppliedLayouts::instance().GetAppliedLayoutMap().empty()); - } - - TEST_METHOD (MoveAppliedLayoutsFromZonesSettingsNoFile) - { - // test - m_fzData.ReplaceZoneSettingsFileFromOlderVersions(); - AppliedLayouts::instance().LoadData(); - Assert::IsTrue(AppliedLayouts::instance().GetAppliedLayoutMap().empty()); + auto actual = AppliedLayouts::instance().GetAppliedLayoutMap(); + Assert::AreEqual(expected.size(), actual.size()); + Assert::IsTrue(expected.at(workAreaId1) == actual.at(workAreaId1)); + Assert::IsTrue(expected.at(workAreaId2) == actual.at(workAreaId2)); + Assert::IsTrue(expected.at(workAreaId3) == actual.at(workAreaId3)); + Assert::IsTrue(expected.at(workAreaId4) == actual.at(workAreaId4)); } TEST_METHOD (CloneDeviceInfo) { FancyZonesDataTypes::WorkAreaId deviceSrc{ .monitorId = { .deviceId = { .id = L"Device1", .instanceId = L"" }, .serialNumber = L"" }, - .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{00000000-0000-0000-0000-000000000000}").value() + .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{EA6B6934-D55F-49F5-A9A5-CFADE21FFFB8}").value() }; FancyZonesDataTypes::WorkAreaId deviceDst{ .monitorId = { .deviceId = { .id = L"Device2", .instanceId = L"" }, .serialNumber = L"" }, - .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{00000000-0000-0000-0000-000000000000}").value() + .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{EF1A8099-7D1E-4738-805A-571B31B02674}").value() }; - Assert::IsTrue(AppliedLayouts::instance().ApplyDefaultLayout(deviceSrc)); - Assert::IsTrue(AppliedLayouts::instance().ApplyDefaultLayout(deviceDst)); - + LayoutData layout { .uuid = FancyZonesUtils::GuidFromString(L"{361F96DD-FD10-4D01-ABAC-CC1C857294DD}").value() }; + Assert::IsTrue(AppliedLayouts::instance().ApplyLayout(deviceSrc, layout)); + AppliedLayouts::instance().CloneLayout(deviceSrc, deviceDst); - auto actualMap = AppliedLayouts::instance().GetAppliedLayoutMap(); - Assert::IsFalse(actualMap.find(deviceSrc) == actualMap.end()); - Assert::IsFalse(actualMap.find(deviceDst) == actualMap.end()); - - auto expected = AppliedLayouts::instance().GetDeviceLayout(deviceSrc); - auto actual = AppliedLayouts::instance().GetDeviceLayout(deviceDst); - - Assert::IsTrue(expected.has_value()); - Assert::IsTrue(actual.has_value()); - Assert::IsTrue(expected.value().uuid == actual.value().uuid); - } - - TEST_METHOD (CloneDeviceInfoIntoUnknownDevice) - { - FancyZonesDataTypes::WorkAreaId deviceSrc{ - .monitorId = { .deviceId = { .id = L"Device1", .instanceId = L"" }, .serialNumber = L"" }, - .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{00000000-0000-0000-0000-000000000000}").value() - }; - FancyZonesDataTypes::WorkAreaId deviceDst{ - .monitorId = { .deviceId = { .id = L"Device2", .instanceId = L"" }, .serialNumber = L"" }, - .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{00000000-0000-0000-0000-000000000000}").value() - }; - - Assert::IsTrue(AppliedLayouts::instance().ApplyDefaultLayout(deviceSrc)); - - AppliedLayouts::instance().CloneLayout(deviceSrc, deviceDst); - - auto actualMap = AppliedLayouts::instance().GetAppliedLayoutMap(); - Assert::IsFalse(actualMap.find(deviceSrc) == actualMap.end()); - Assert::IsFalse(actualMap.find(deviceDst) == actualMap.end()); - - auto expected = AppliedLayouts::instance().GetDeviceLayout(deviceSrc); - auto actual = AppliedLayouts::instance().GetDeviceLayout(deviceDst); - - Assert::IsTrue(expected.has_value()); - Assert::IsTrue(actual.has_value()); - Assert::IsTrue(expected.value().uuid == actual.value().uuid); + Assert::IsTrue(layout == AppliedLayouts::instance().GetDeviceLayout(deviceSrc)); + Assert::IsTrue(layout == AppliedLayouts::instance().GetDeviceLayout(deviceDst)); } TEST_METHOD (CloneDeviceInfoFromUnknownDevice) { FancyZonesDataTypes::WorkAreaId deviceSrc{ .monitorId = { .deviceId = { .id = L"Device1", .instanceId = L"" }, .serialNumber = L"" }, - .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{00000000-0000-0000-0000-000000000000}").value() + .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{EA6B6934-D55F-49F5-A9A5-CFADE21FFFB8}").value() }; FancyZonesDataTypes::WorkAreaId deviceDst{ .monitorId = { .deviceId = { .id = L"Device2", .instanceId = L"" }, .serialNumber = L"" }, - .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{00000000-0000-0000-0000-000000000000}").value() + .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{EF1A8099-7D1E-4738-805A-571B31B02674}").value() }; AppliedLayouts::instance().LoadData(); - Assert::IsTrue(AppliedLayouts::instance().ApplyDefaultLayout(deviceDst)); Assert::IsFalse(AppliedLayouts::instance().CloneLayout(deviceSrc, deviceDst)); Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(deviceSrc).has_value()); - Assert::IsTrue(AppliedLayouts::instance().GetDeviceLayout(deviceDst).has_value()); + Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(deviceDst).has_value()); } TEST_METHOD (CloneDeviceInfoNullVirtualDesktopId) @@ -389,35 +330,25 @@ namespace FancyZonesUnitTests }; FancyZonesDataTypes::WorkAreaId deviceDst{ .monitorId = { .deviceId = { .id = L"Device2", .instanceId = L"" }, .serialNumber = L"" }, - .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{00000000-0000-0000-0000-000000000000}").value() + .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{EF1A8099-7D1E-4738-805A-571B31B02674}").value() }; - Assert::IsTrue(AppliedLayouts::instance().ApplyDefaultLayout(deviceSrc)); - Assert::IsTrue(AppliedLayouts::instance().ApplyDefaultLayout(deviceDst)); - + LayoutData layout{ .uuid = FancyZonesUtils::GuidFromString(L"{361F96DD-FD10-4D01-ABAC-CC1C857294DD}").value() }; + Assert::IsTrue(AppliedLayouts::instance().ApplyLayout(deviceSrc, layout)); + AppliedLayouts::instance().CloneLayout(deviceSrc, deviceDst); - auto actualMap = AppliedLayouts::instance().GetAppliedLayoutMap(); - Assert::IsFalse(actualMap.find(deviceSrc) == actualMap.end()); - Assert::IsFalse(actualMap.find(deviceDst) == actualMap.end()); - - auto expected = AppliedLayouts::instance().GetDeviceLayout(deviceSrc); - auto actual = AppliedLayouts::instance().GetDeviceLayout(deviceDst); - - Assert::IsTrue(expected.has_value()); - Assert::IsTrue(actual.has_value()); - Assert::IsTrue(expected.value().uuid == actual.value().uuid); + Assert::IsTrue(layout == AppliedLayouts::instance().GetDeviceLayout(deviceSrc)); + Assert::IsTrue(layout == AppliedLayouts::instance().GetDeviceLayout(deviceDst)); } TEST_METHOD (ApplyLayout) { - // prepare - FancyZonesDataTypes::WorkAreaId deviceId { + FancyZonesDataTypes::WorkAreaId workAreaId { .monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" }, .serialNumber = L"" }, .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{61FA9FC0-26A6-4B37-A834-491C148DFC57}").value() }; - // test LayoutData expectedLayout { .uuid = FancyZonesUtils::GuidFromString(L"{33A2B101-06E0-437B-A61E-CDBECF502906}").value(), .type = FancyZonesDataTypes::ZoneSetLayoutType::Focus, @@ -427,47 +358,30 @@ namespace FancyZonesUnitTests .sensitivityRadius = 30 }; - AppliedLayouts::instance().ApplyLayout(deviceId, expectedLayout); + AppliedLayouts::instance().ApplyLayout(workAreaId, expectedLayout); - Assert::IsFalse(AppliedLayouts::instance().GetAppliedLayoutMap().empty()); - Assert::IsTrue(AppliedLayouts::instance().GetDeviceLayout(deviceId).has_value()); - - auto actual = AppliedLayouts::instance().GetAppliedLayoutMap().find(deviceId)->second; - Assert::IsTrue(expectedLayout.type == actual.type); - Assert::AreEqual(expectedLayout.showSpacing, actual.showSpacing); - Assert::AreEqual(expectedLayout.spacing, actual.spacing); - Assert::AreEqual(expectedLayout.zoneCount, actual.zoneCount); - Assert::AreEqual(expectedLayout.sensitivityRadius, actual.sensitivityRadius); + Assert::IsTrue(AppliedLayouts::instance().GetDeviceLayout(workAreaId).has_value()); + Assert::IsTrue(expectedLayout == AppliedLayouts::instance().GetAppliedLayoutMap().find(workAreaId)->second); } TEST_METHOD (ApplyLayoutReplace) { // prepare - FancyZonesDataTypes::WorkAreaId deviceId{ + FancyZonesDataTypes::WorkAreaId workAreaId{ .monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" }, .serialNumber = L"" }, .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{61FA9FC0-26A6-4B37-A834-491C148DFC57}").value() }; + + LayoutData layout{ + .uuid = FancyZonesUtils::GuidFromString(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}").value(), + .type = FancyZonesDataTypes::ZoneSetLayoutType::Rows, + .showSpacing = true, + .spacing = 3, + .zoneCount = 4, + .sensitivityRadius = 22 + }; - json::JsonObject root{}; - json::JsonArray layoutsArray{}; - { - json::JsonObject layout{}; - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}")); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Rows))); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(true)); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(3)); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4)); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(22)); - - json::JsonObject obj{}; - obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceIdID, json::value(L"DELA026#5&10a58c63&0&UID16777488_2194_1234_{61FA9FC0-26A6-4B37-A834-491C148DFC57}")); - obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout); - - layoutsArray.Append(obj); - } - root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray); - json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root); - AppliedLayouts::instance().LoadData(); + AppliedLayouts::instance().SetAppliedLayouts({ {workAreaId, layout} }); // test LayoutData expectedLayout{ @@ -479,18 +393,8 @@ namespace FancyZonesUnitTests .sensitivityRadius = 30 }; - AppliedLayouts::instance().ApplyLayout(deviceId, expectedLayout); - - Assert::AreEqual((size_t)1, AppliedLayouts::instance().GetAppliedLayoutMap().size()); - Assert::IsTrue(AppliedLayouts::instance().GetDeviceLayout(deviceId).has_value()); - - auto actual = AppliedLayouts::instance().GetAppliedLayoutMap().find(deviceId)->second; - Assert::AreEqual(FancyZonesUtils::GuidToString(expectedLayout.uuid).value().c_str(), FancyZonesUtils::GuidToString(actual.uuid).value().c_str()); - Assert::IsTrue(expectedLayout.type == actual.type); - Assert::AreEqual(expectedLayout.showSpacing, actual.showSpacing); - Assert::AreEqual(expectedLayout.spacing, actual.spacing); - Assert::AreEqual(expectedLayout.zoneCount, actual.zoneCount); - Assert::AreEqual(expectedLayout.sensitivityRadius, actual.sensitivityRadius); + AppliedLayouts::instance().ApplyLayout(workAreaId, expectedLayout); + Assert::IsTrue(expectedLayout == AppliedLayouts::instance().GetDeviceLayout(workAreaId)); } TEST_METHOD (ApplyDefaultLayout) @@ -553,4 +457,245 @@ namespace FancyZonesUnitTests Assert::IsFalse(AppliedLayouts::instance().IsLayoutApplied(id2)); } }; + + TEST_CLASS (AppliedLayoutsSyncVirtualDesktops) + { + const GUID virtualDesktop1 = FancyZonesUtils::GuidFromString(L"{30387C86-BB15-476D-8683-AF93F6D73E99}").value(); + const GUID virtualDesktop2 = FancyZonesUtils::GuidFromString(L"{65F6343A-868F-47EE-838E-55A178A7FB7A}").value(); + const GUID deletedVirtualDesktop = FancyZonesUtils::GuidFromString(L"{2D9F3E2D-F61D-4618-B35D-85C9B8DFDFD8}").value(); + + LayoutData layout1{ .uuid = FancyZonesUtils::GuidFromString(L"{D7DBECFA-23FC-4F45-9B56-51CFA9F6ABA2}").value() }; + LayoutData layout2{ .uuid = FancyZonesUtils::GuidFromString(L"{B9EDB48C-EC48-4E82-993F-A15DC1FF09D3}").value() }; + LayoutData layout3{ .uuid = FancyZonesUtils::GuidFromString(L"{94CF0000-7814-4D72-9624-794060FA269C}").value() }; + LayoutData layout4{ .uuid = FancyZonesUtils::GuidFromString(L"{13FA7ADF-1B6C-4FB6-8142-254B77C128E2}").value() }; + + FancyZonesDataTypes::WorkAreaId GetWorkAreaID(int number, GUID virtualDesktop) + { + return FancyZonesDataTypes::WorkAreaId{ + .monitorId = { + .deviceId = { + .id = std::wstring(L"id-") + std::to_wstring(number), + .instanceId = std::wstring(L"id-") + std::to_wstring(number), + .number = number + }, + .serialNumber = std::wstring(L"serial-number-") + std::to_wstring(number) + }, + .virtualDesktopId = virtualDesktop + }; + } + + TEST_METHOD_INITIALIZE(Init) + { + AppliedLayouts::instance().LoadData(); + } + + TEST_METHOD_CLEANUP(CleanUp) + { + std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName()); + } + + TEST_METHOD(SyncVirtualDesktops_SwitchVirtualDesktop) + { + AppliedLayouts::TAppliedLayoutsMap layouts{}; + layouts.insert({ GetWorkAreaID(1, virtualDesktop1), layout1 }); + layouts.insert({ GetWorkAreaID(2, virtualDesktop1), layout2 }); + layouts.insert({ GetWorkAreaID(1, virtualDesktop2), layout3 }); + layouts.insert({ GetWorkAreaID(2, virtualDesktop2), layout4 }); + AppliedLayouts::instance().SetAppliedLayouts(layouts); + + GUID currentVirtualDesktop = virtualDesktop1; + GUID lastUsedVirtualDesktop = virtualDesktop2; + std::optional> virtualDesktopsInRegistry = { { virtualDesktop1, virtualDesktop2 } }; + AppliedLayouts::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry); + + Assert::IsTrue(layout1 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, virtualDesktop1))); + Assert::IsTrue(layout2 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, virtualDesktop1))); + Assert::IsTrue(layout3 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, virtualDesktop2))); + Assert::IsTrue(layout4 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, virtualDesktop2))); + } + + TEST_METHOD (SyncVirtualDesktops_CurrentVirtualDesktopDeleted) + { + AppliedLayouts::TAppliedLayoutsMap layouts{}; + layouts.insert({ GetWorkAreaID(1, virtualDesktop1), layout1 }); + layouts.insert({ GetWorkAreaID(2, virtualDesktop1), layout2 }); + layouts.insert({ GetWorkAreaID(1, deletedVirtualDesktop), layout3 }); + layouts.insert({ GetWorkAreaID(2, deletedVirtualDesktop), layout4 }); + AppliedLayouts::instance().SetAppliedLayouts(layouts); + + GUID currentVirtualDesktop = virtualDesktop1; + GUID lastUsedVirtualDesktop = deletedVirtualDesktop; + std::optional> virtualDesktopsInRegistry = { { virtualDesktop1 } }; + AppliedLayouts::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry); + + Assert::IsTrue(layout1 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, virtualDesktop1))); + Assert::IsTrue(layout2 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, virtualDesktop1))); + Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, deletedVirtualDesktop)).has_value()); + Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, deletedVirtualDesktop)).has_value()); + } + + TEST_METHOD (SyncVirtualDesktops_NotCurrentVirtualDesktopDeleted) + { + AppliedLayouts::TAppliedLayoutsMap layouts{}; + layouts.insert({ GetWorkAreaID(1, virtualDesktop1), layout1 }); + layouts.insert({ GetWorkAreaID(2, virtualDesktop1), layout2 }); + layouts.insert({ GetWorkAreaID(1, deletedVirtualDesktop), layout3 }); + layouts.insert({ GetWorkAreaID(2, deletedVirtualDesktop), layout4 }); + AppliedLayouts::instance().SetAppliedLayouts(layouts); + + GUID currentVirtualDesktop = virtualDesktop1; + GUID lastUsedVirtualDesktop = virtualDesktop1; + std::optional> virtualDesktopsInRegistry = { { virtualDesktop1 } }; + AppliedLayouts::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry); + + Assert::IsTrue(layout1 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, virtualDesktop1))); + Assert::IsTrue(layout2 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, virtualDesktop1))); + Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, deletedVirtualDesktop)).has_value()); + Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, deletedVirtualDesktop)).has_value()); + } + + TEST_METHOD (SyncVirtualDesktops_AllIdsFromRegistryAreNew) + { + AppliedLayouts::TAppliedLayoutsMap layouts{}; + layouts.insert({ GetWorkAreaID(1, deletedVirtualDesktop), layout1 }); + layouts.insert({ GetWorkAreaID(2, deletedVirtualDesktop), layout2 }); + AppliedLayouts::instance().SetAppliedLayouts(layouts); + + GUID currentVirtualDesktop = virtualDesktop1; + GUID lastUsedVirtualDesktop = deletedVirtualDesktop; + std::optional> virtualDesktopsInRegistry = { { virtualDesktop1, virtualDesktop2 } }; + AppliedLayouts::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry); + + Assert::IsTrue(layout1 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, virtualDesktop1))); + Assert::IsTrue(layout2 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, virtualDesktop1))); + Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, virtualDesktop2)).has_value()); + Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, virtualDesktop2)).has_value()); + Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, deletedVirtualDesktop)).has_value()); + Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, deletedVirtualDesktop)).has_value()); + } + + TEST_METHOD (SyncVirtualDesktop_NoDesktopsInRegistry) + { + AppliedLayouts::TAppliedLayoutsMap layouts{}; + layouts.insert({ GetWorkAreaID(1, deletedVirtualDesktop), layout1 }); + layouts.insert({ GetWorkAreaID(2, deletedVirtualDesktop), layout2 }); + AppliedLayouts::instance().SetAppliedLayouts(layouts); + + GUID currentVirtualDesktop = GUID_NULL; + GUID lastUsedVirtualDesktop = deletedVirtualDesktop; + std::optional> virtualDesktopsInRegistry = std::nullopt; + AppliedLayouts::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry); + + Assert::IsTrue(layout1 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, GUID_NULL))); + Assert::IsTrue(layout2 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, GUID_NULL))); + Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, deletedVirtualDesktop)).has_value()); + Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, deletedVirtualDesktop)).has_value()); + } + + TEST_METHOD(SyncVirtualDesktops_SwithVirtualDesktopFirstTime) + { + AppliedLayouts::TAppliedLayoutsMap layouts{}; + layouts.insert({ GetWorkAreaID(1, GUID_NULL), layout1 }); + layouts.insert({ GetWorkAreaID(2, GUID_NULL), layout2 }); + AppliedLayouts::instance().SetAppliedLayouts(layouts); + + GUID currentVirtualDesktop = virtualDesktop2; + GUID lastUsedVirtualDesktop = GUID_NULL; + std::optional> virtualDesktopsInRegistry = { { virtualDesktop1, virtualDesktop2 } }; + AppliedLayouts::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry); + + Assert::IsTrue(layout1 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, virtualDesktop1))); + Assert::IsTrue(layout2 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, virtualDesktop1))); + Assert::IsTrue(layout1 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, virtualDesktop2))); + Assert::IsTrue(layout2 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, virtualDesktop2))); + } + }; + + TEST_CLASS (AppliedLayoutsFromOutdatedFileMappingUnitTests) + { + FancyZonesData& m_fzData = FancyZonesDataInstance(); + std::wstring m_testFolder = L"FancyZonesUnitTests"; + std::wstring m_testFolderPath = PTSettingsHelper::get_module_save_folder_location(m_testFolder); + + TEST_METHOD_INITIALIZE(Init) + { + m_fzData.SetSettingsModulePath(m_testFolder); + } + + TEST_METHOD_CLEANUP(CleanUp) + { + // MoveAppliedLayoutsFromZonesSettings creates all of these files, clean up + std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName()); + std::filesystem::remove(CustomLayouts::CustomLayoutsFileName()); + std::filesystem::remove(LayoutHotkeys::LayoutHotkeysFileName()); + std::filesystem::remove(LayoutTemplates::LayoutTemplatesFileName()); + std::filesystem::remove_all(m_testFolderPath); + AppliedLayouts::instance().LoadData(); // clean data + } + + TEST_METHOD (MoveAppliedLayoutsFromZonesSettings) + { + // prepare + json::JsonObject root{}; + json::JsonArray devicesArray{}, customLayoutsArray{}, templateLayoutsArray{}, quickLayoutKeysArray{}; + + { + json::JsonObject activeZoneset{}; + activeZoneset.SetNamedValue(L"uuid", json::value(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}")); + activeZoneset.SetNamedValue(L"type", json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Rows))); + + json::JsonObject obj{}; + obj.SetNamedValue(L"device-id", json::value(L"VSC9636#5&37ac4db&0&UID160005_3840_2160_{00000000-0000-0000-0000-000000000000}")); + obj.SetNamedValue(L"active-zoneset", activeZoneset); + + obj.SetNamedValue(L"editor-show-spacing", json::value(true)); + obj.SetNamedValue(L"editor-spacing", json::value(3)); + obj.SetNamedValue(L"editor-zone-count", json::value(4)); + obj.SetNamedValue(L"editor-sensitivity-radius", json::value(22)); + + devicesArray.Append(obj); + } + + root.SetNamedValue(L"devices", devicesArray); + root.SetNamedValue(L"custom-zone-sets", customLayoutsArray); + root.SetNamedValue(L"templates", templateLayoutsArray); + root.SetNamedValue(L"quick-layout-keys", quickLayoutKeysArray); + json::to_file(m_fzData.GetZoneSettingsPath(m_testFolder), root); + + // test + m_fzData.ReplaceZoneSettingsFileFromOlderVersions(); + AppliedLayouts::instance().LoadData(); + Assert::AreEqual((size_t)1, AppliedLayouts::instance().GetAppliedLayoutMap().size()); + + FancyZonesDataTypes::WorkAreaId id{ + .monitorId = { .deviceId = { .id = L"VSC9636", .instanceId = L"5&37ac4db&0&UID160005" }, .serialNumber = L"" }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{00000000-0000-0000-0000-000000000000}").value() + }; + Assert::IsTrue(AppliedLayouts::instance().GetDeviceLayout(id).has_value()); + } + + TEST_METHOD (MoveAppliedLayoutsFromZonesSettingsNoAppliedLayoutsData) + { + // prepare + json::JsonObject root{}; + json::JsonArray customLayoutsArray{}, templateLayoutsArray{}, quickLayoutKeysArray{}; + root.SetNamedValue(L"custom-zone-sets", customLayoutsArray); + root.SetNamedValue(L"templates", templateLayoutsArray); + root.SetNamedValue(L"quick-layout-keys", quickLayoutKeysArray); + json::to_file(m_fzData.GetZoneSettingsPath(m_testFolder), root); + + // test + m_fzData.ReplaceZoneSettingsFileFromOlderVersions(); + AppliedLayouts::instance().LoadData(); + Assert::IsTrue(AppliedLayouts::instance().GetAppliedLayoutMap().empty()); + } + + TEST_METHOD (MoveAppliedLayoutsFromZonesSettingsNoFile) + { + // test + m_fzData.ReplaceZoneSettingsFileFromOlderVersions(); + AppliedLayouts::instance().LoadData(); + Assert::IsTrue(AppliedLayouts::instance().GetAppliedLayoutMap().empty()); + } + }; } \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkAreaIdTests.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkAreaIdTests.Spec.cpp index db5c660db0..5b024b718d 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkAreaIdTests.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkAreaIdTests.Spec.cpp @@ -43,11 +43,11 @@ namespace FancyZonesUnitTests Assert::IsFalse(id1 == id2); } - TEST_METHOD (VirtualDesktopNull) + TEST_METHOD (VirtualDesktopDifferent) { FancyZonesDataTypes::WorkAreaId id1{ .monitorId = { .deviceId = { .id = L"device", .instanceId = L"instance-id" }, .serialNumber = L"serial-number" }, - .virtualDesktopId = GUID_NULL + .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{F21F6F29-76FD-4FC1-8970-17AB8AD64847}").value() }; FancyZonesDataTypes::WorkAreaId id2{ @@ -55,14 +55,14 @@ namespace FancyZonesUnitTests .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{E21F6F29-76FD-4FC1-8970-17AB8AD64847}").value() }; - Assert::IsTrue(id1 == id2); + Assert::IsFalse(id1 == id2); } - TEST_METHOD (VirtualDesktopDifferent) + TEST_METHOD (VirtualDesktopNull) { FancyZonesDataTypes::WorkAreaId id1{ .monitorId = { .deviceId = { .id = L"device", .instanceId = L"instance-id" }, .serialNumber = L"serial-number" }, - .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{F21F6F29-76FD-4FC1-8970-17AB8AD64847}").value() + .virtualDesktopId = GUID_NULL }; FancyZonesDataTypes::WorkAreaId id2{ From 422813044d5b5caf62deed3e545111ed947ba6a7 Mon Sep 17 00:00:00 2001 From: Jaime Bernardo Date: Thu, 21 Sep 2023 13:52:08 +0100 Subject: [PATCH 6/8] [QuickAccent]Cache computed all language characters (#28657) --- .../poweraccent/PowerAccent.Core/Languages.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/modules/poweraccent/PowerAccent.Core/Languages.cs b/src/modules/poweraccent/PowerAccent.Core/Languages.cs index 4a611f2082..c20d9c8264 100644 --- a/src/modules/poweraccent/PowerAccent.Core/Languages.cs +++ b/src/modules/poweraccent/PowerAccent.Core/Languages.cs @@ -2,6 +2,7 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Concurrent; using PowerToys.PowerAccentKeyboardService; namespace PowerAccent.Core @@ -80,11 +81,15 @@ namespace PowerAccent.Core }; } + // Store the computed letters for each key, so that subsequent calls don't take as long. + private static ConcurrentDictionary _allLanguagesCache = new ConcurrentDictionary(); + // All private static string[] GetDefaultLetterKeyALL(LetterKey letter) { - // would be even better to loop through Languages and call these functions dynamically, but I don't know how to do that! - return GetDefaultLetterKeyCA(letter) + if (!_allLanguagesCache.ContainsKey(letter)) + { + _allLanguagesCache[letter] = GetDefaultLetterKeyCA(letter) .Union(GetDefaultLetterKeyCUR(letter)) .Union(GetDefaultLetterKeyCY(letter)) .Union(GetDefaultLetterKeyCZ(letter)) @@ -113,7 +118,10 @@ namespace PowerAccent.Core .Union(GetDefaultLetterKeySR(letter)) .Union(GetDefaultLetterKeySV(letter)) .Union(GetDefaultLetterKeyTK(letter)) - .ToArray(); + .ToArray(); + } + + return _allLanguagesCache[letter]; } // Currencies (source: https://www.eurochange.co.uk/travel-money/world-currency-abbreviations-symbols-and-codes-travel-money) From e545291461db6d7708f8f9d09adcd87bcb0001f4 Mon Sep 17 00:00:00 2001 From: Jaime Bernardo Date: Thu, 21 Sep 2023 15:18:22 +0100 Subject: [PATCH 7/8] [Deps]Upgrade Windows App SDK to 1.4.1 (#28676) --- Directory.Packages.props | 2 +- NOTICE.md | 2 +- .../MeasureToolCore/PowerToys.MeasureToolCore.vcxproj | 8 ++++---- src/modules/MeasureTool/MeasureToolCore/packages.config | 2 +- .../powerrename/PowerRenameUILib/PowerRenameUI.vcxproj | 8 ++++---- src/modules/powerrename/PowerRenameUILib/packages.config | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index ff66334176..d26fb2dbfb 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -40,7 +40,7 @@ - + diff --git a/NOTICE.md b/NOTICE.md index cbe7050123..07378214cc 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -341,7 +341,7 @@ SOFTWARE. - Microsoft.Windows.CsWinRT 2.0.3 - Microsoft.Windows.SDK.BuildTools 10.0.22621.756 - Microsoft.Windows.SDK.Contracts 10.0.19041.1 -- Microsoft.WindowsAppSDK 1.4.230822000 +- Microsoft.WindowsAppSDK 1.4.230913002 - Microsoft.Xaml.Behaviors.WinUI.Managed 2.0.9 - Microsoft.Xaml.Behaviors.Wpf 1.1.39 - ModernWpfUI 0.9.4 diff --git a/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj b/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj index 4271e03a55..ad51f8998b 100644 --- a/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj +++ b/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj @@ -1,6 +1,6 @@  - + @@ -147,7 +147,7 @@ - + @@ -158,7 +158,7 @@ - - + + \ No newline at end of file diff --git a/src/modules/MeasureTool/MeasureToolCore/packages.config b/src/modules/MeasureTool/MeasureToolCore/packages.config index 817fac0812..ca3592e5b0 100644 --- a/src/modules/MeasureTool/MeasureToolCore/packages.config +++ b/src/modules/MeasureTool/MeasureToolCore/packages.config @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/src/modules/powerrename/PowerRenameUILib/PowerRenameUI.vcxproj b/src/modules/powerrename/PowerRenameUILib/PowerRenameUI.vcxproj index 8287fd9597..c4051b8f03 100644 --- a/src/modules/powerrename/PowerRenameUILib/PowerRenameUI.vcxproj +++ b/src/modules/powerrename/PowerRenameUILib/PowerRenameUI.vcxproj @@ -1,7 +1,7 @@  - + true @@ -208,7 +208,7 @@ - + @@ -220,8 +220,8 @@ - - + + diff --git a/src/modules/powerrename/PowerRenameUILib/packages.config b/src/modules/powerrename/PowerRenameUILib/packages.config index eb4be241a8..53ee497bdd 100644 --- a/src/modules/powerrename/PowerRenameUILib/packages.config +++ b/src/modules/powerrename/PowerRenameUILib/packages.config @@ -5,5 +5,5 @@ - + \ No newline at end of file From 1de6c7d19b2a226c9c91cf78653e7cb7174910b5 Mon Sep 17 00:00:00 2001 From: Dylan Briedis Date: Thu, 21 Sep 2023 08:04:02 -0700 Subject: [PATCH 8/8] [Registry Preview] Better preview in data grid like in Regedit (#28488) * Fix typo * Better data grid preview like regedit * Fix sorting of resource strings * Add error icons back in * Remove comments then trim whitespace * Better string detection --- .../RegistryPreviewUI/MainWindow.Utilities.cs | 124 +++++++++++++++++- .../RegistryPreviewUI/RegistryValue.xaml.cs | 2 +- .../Strings/en-US/Resources.resw | 11 +- 3 files changed, 131 insertions(+), 6 deletions(-) diff --git a/src/modules/registrypreview/RegistryPreviewUI/MainWindow.Utilities.cs b/src/modules/registrypreview/RegistryPreviewUI/MainWindow.Utilities.cs index 07f0837fdd..bffaa7a3b6 100644 --- a/src/modules/registrypreview/RegistryPreviewUI/MainWindow.Utilities.cs +++ b/src/modules/registrypreview/RegistryPreviewUI/MainWindow.Utilities.cs @@ -8,7 +8,9 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; +using System.Linq; using System.Reflection; +using System.Text; using System.Threading.Tasks; using Microsoft.UI.Input; using Microsoft.UI.Xaml; @@ -413,21 +415,75 @@ namespace RegistryPreview value += registryLine; } - // Clean out any escaped characters in the value, only for the preview - value = StripEscapedCharacters(value); - // update the ListViewItem with the loaded value, based off REG value type switch (registryValue.Type) { case "ERROR": // do nothing break; + case "REG_SZ": + if (value == "\"") + { + // Value is most likely missing an end quote + registryValue.Type = "ERROR"; + value = resourceLoader.GetString("InvalidString"); + } + else + { + for (int i = 1; i < value.Length; i++) + { + if (value[i - 1] == '\\') + { + // Only allow these escape characters + if (value[i] != '"' && value[i] != '\\') + { + registryValue.Type = "ERROR"; + value = resourceLoader.GetString("InvalidString"); + break; + } + + i++; + } + + if (value[i - 1] != '\\' && value[i] == '"') + { + // Don't allow non-escaped quotes + registryValue.Type = "ERROR"; + value = resourceLoader.GetString("InvalidString"); + break; + } + } + + if (registryValue.Type != "ERROR") + { + // Clean out any escaped characters in the value, only for the preview + value = StripEscapedCharacters(value); + } + } + + registryValue.Value = value; + break; case "REG_BINARY": case "REG_NONE": if (value.Length <= 0) { value = resourceLoader.GetString("ZeroLength"); } + else + { + try + { + // Hexes are usually two characters (00), it's invalid if less or more than 2 + var bytes = value.Split(',').Select( + c => c.Length == 2 ? byte.Parse(c, NumberStyles.HexNumber, CultureInfo.InvariantCulture) : throw null); + value = string.Join(' ', bytes.Select(b => b.ToString("x2", CultureInfo.CurrentCulture))); + } + catch + { + registryValue.Type = "ERROR"; + value = resourceLoader.GetString("InvalidBinary"); + } + } registryValue.Value = value; @@ -436,6 +492,19 @@ namespace RegistryPreview if (value.Length <= 0) { registryValue.Type = "ERROR"; + value = resourceLoader.GetString("InvalidDword"); + } + else + { + if (uint.TryParse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint dword)) + { + value = $"0x{dword:x8} ({dword})"; + } + else + { + registryValue.Type = "ERROR"; + value = resourceLoader.GetString("InvalidDword"); + } } registryValue.Value = value; @@ -444,8 +513,55 @@ namespace RegistryPreview case "REG_QWORD": if (value.Length <= 0) { + registryValue.Type = "ERROR"; value = resourceLoader.GetString("InvalidQword"); } + else + { + try + { + // Hexes are usually two characters (00), it's invalid if less or more than 2 + var bytes = value.Split(',').Select( + c => c.Length == 2 ? byte.Parse(c, NumberStyles.HexNumber, CultureInfo.InvariantCulture) : throw null).ToArray(); + ulong qword = BitConverter.ToUInt64(bytes); + value = $"0x{qword:x8} ({qword})"; + } + catch + { + registryValue.Type = "ERROR"; + value = resourceLoader.GetString("InvalidQword"); + } + } + + registryValue.Value = value; + break; + case "REG_EXPAND_SZ": + case "REG_MULTI_SZ": + try + { + // Hexes are usually two characters (00), it's invalid if less or more than 2 + var bytes = value.Split(',').Select( + c => c.Length == 2 ? byte.Parse(c, NumberStyles.HexNumber, CultureInfo.InvariantCulture) : throw null).ToArray(); + + if (registryValue.Type == "REG_MULTI_SZ") + { + // Replace zeros (00,00) with spaces + for (int i = 0; i < bytes.Length; i += 2) + { + if (bytes[i] == 0 && bytes[i + 1] == 0) + { + bytes[i] = 0x20; + } + } + } + + value = Encoding.Unicode.GetString(bytes); + } + catch + { + registryValue.Type = "ERROR"; + value = resourceLoader.GetString("InvalidString"); + } registryValue.Value = value; break; @@ -1036,7 +1152,7 @@ namespace RegistryPreview value = value.Remove(indexOf, value.Length - indexOf); } - return value; + return value.TrimEnd(); } /// diff --git a/src/modules/registrypreview/RegistryPreviewUI/RegistryValue.xaml.cs b/src/modules/registrypreview/RegistryPreviewUI/RegistryValue.xaml.cs index a7df53f1f8..161e9cbedd 100644 --- a/src/modules/registrypreview/RegistryPreviewUI/RegistryValue.xaml.cs +++ b/src/modules/registrypreview/RegistryPreviewUI/RegistryValue.xaml.cs @@ -33,7 +33,7 @@ namespace RegistryPreview switch (Type) { case "REG_SZ": - case "REG_EXAND_SZ": + case "REG_EXPAND_SZ": case "REG_MULTI_SZ": return uriStringValue; case "ERROR": diff --git a/src/modules/registrypreview/RegistryPreviewUI/Strings/en-US/Resources.resw b/src/modules/registrypreview/RegistryPreviewUI/Strings/en-US/Resources.resw index 6b766a6570..d637b2e187 100644 --- a/src/modules/registrypreview/RegistryPreviewUI/Strings/en-US/Resources.resw +++ b/src/modules/registrypreview/RegistryPreviewUI/Strings/en-US/Resources.resw @@ -138,6 +138,12 @@ Registry files (*.reg) + + (Invalid binary value) + + + (Invalid DWORD (32-bit) value) + (Invalid QWORD (64-bit) value) @@ -147,6 +153,9 @@ File was not a Registry file + + (Invalid string value) + is larger than 10MB which is too large for this application. @@ -228,7 +237,7 @@ User Account Control - Value + Data Write to Registry