From f20cecdd68a7aa738f101628ee356c52c6634dfb Mon Sep 17 00:00:00 2001 From: Stefan S Date: Wed, 29 Jan 2020 16:42:26 +0100 Subject: [PATCH 01/32] Migrate FancyZones data persisting from Registry to JSON file --- src/common/settings_helpers.h | 2 + .../fancyzones/dll/FancyZonesModule.vcxproj | 3 - .../dll/FancyZonesModule.vcxproj.filters | 1 - src/modules/fancyzones/dll/dllmain.cpp | 75 +- src/modules/fancyzones/dll/fancyzones.def | 4 - .../editor/FancyZonesEditor/App.xaml.cs | 34 +- .../FancyZonesEditor/EditorOverlay.xaml.cs | 19 +- .../FancyZonesEditor/FancyZonesEditor.csproj | 6 +- .../FancyZonesEditor/MainWindow.xaml.cs | 18 +- .../Models/CanvasLayoutModel.cs | 118 +- .../Models/GridLayoutModel.cs | 387 ++--- .../FancyZonesEditor/Models/LayoutModel.cs | 216 ++- .../FancyZonesEditor/Models/Settings.cs | 198 ++- src/modules/fancyzones/lib/FancyZones.cpp | 209 ++- src/modules/fancyzones/lib/FancyZones.h | 14 +- .../fancyzones/lib/FancyZonesLib.vcxproj | 3 + .../lib/FancyZonesLib.vcxproj.filters | 10 +- src/modules/fancyzones/lib/JsonHelpers.cpp | 951 +++++++++++ src/modules/fancyzones/lib/JsonHelpers.h | 249 +++ src/modules/fancyzones/lib/RegistryHelpers.h | 126 -- src/modules/fancyzones/lib/Settings.cpp | 41 +- src/modules/fancyzones/lib/Settings.h | 2 +- src/modules/fancyzones/lib/ZoneSet.cpp | 531 +++++- src/modules/fancyzones/lib/ZoneSet.h | 34 +- src/modules/fancyzones/lib/ZoneWindow.cpp | 1519 +++++++++-------- src/modules/fancyzones/lib/ZoneWindow.h | 10 +- src/modules/fancyzones/lib/util.cpp | 27 + src/modules/fancyzones/lib/util.h | 12 +- .../tests/UnitTests/FancyZones.Spec.cpp | 492 ++++++ .../UnitTests/FancyZonesSettings.Spec.cpp | 734 ++++++++ .../tests/UnitTests/JsonHelpers.Tests.cpp | 1487 ++++++++++++++++ .../tests/UnitTests/RegistryHelpers.Spec.cpp | 35 - .../tests/UnitTests/UnitTests.vcxproj | 5 +- .../tests/UnitTests/UnitTests.vcxproj.filters | 17 +- .../fancyzones/tests/UnitTests/Util.cpp | 149 ++ src/modules/fancyzones/tests/UnitTests/Util.h | 9 +- .../fancyzones/tests/UnitTests/Zone.Spec.cpp | 450 ++++- .../tests/UnitTests/ZoneSet.Spec.cpp | 1322 ++++++++++++-- .../tests/UnitTests/ZoneWindow.Spec.cpp | 679 +++++++- 39 files changed, 8342 insertions(+), 1856 deletions(-) delete mode 100644 src/modules/fancyzones/dll/fancyzones.def create mode 100644 src/modules/fancyzones/lib/JsonHelpers.cpp create mode 100644 src/modules/fancyzones/lib/JsonHelpers.h create mode 100644 src/modules/fancyzones/lib/util.cpp create mode 100644 src/modules/fancyzones/tests/UnitTests/FancyZones.Spec.cpp create mode 100644 src/modules/fancyzones/tests/UnitTests/FancyZonesSettings.Spec.cpp create mode 100644 src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp delete mode 100644 src/modules/fancyzones/tests/UnitTests/RegistryHelpers.Spec.cpp create mode 100644 src/modules/fancyzones/tests/UnitTests/Util.cpp diff --git a/src/common/settings_helpers.h b/src/common/settings_helpers.h index 6143bc2e5c12..9dba9359caab 100644 --- a/src/common/settings_helpers.h +++ b/src/common/settings_helpers.h @@ -6,6 +6,8 @@ namespace PTSettingsHelper { + std::wstring get_module_save_folder_location(std::wstring_view powertoy_name); + void save_module_settings(std::wstring_view powertoy_name, json::JsonObject& settings); json::JsonObject load_module_settings(std::wstring_view powertoy_name); void save_general_settings(const json::JsonObject& settings); diff --git a/src/modules/fancyzones/dll/FancyZonesModule.vcxproj b/src/modules/fancyzones/dll/FancyZonesModule.vcxproj index ce37e749c2df..ad6aefab2a99 100644 --- a/src/modules/fancyzones/dll/FancyZonesModule.vcxproj +++ b/src/modules/fancyzones/dll/FancyZonesModule.vcxproj @@ -71,7 +71,6 @@ true $(OutDir)$(TargetName)$(TargetExt) gdiplus.lib;dwmapi.lib;shlwapi.lib;uxtheme.lib;shcore.lib;%(AdditionalDependencies) - fancyzones.def @@ -96,7 +95,6 @@ true $(OutDir)$(TargetName)$(TargetExt) gdiplus.lib;dwmapi.lib;shlwapi.lib;uxtheme.lib;shcore.lib;%(AdditionalDependencies) - fancyzones.def @@ -121,7 +119,6 @@ - diff --git a/src/modules/fancyzones/dll/FancyZonesModule.vcxproj.filters b/src/modules/fancyzones/dll/FancyZonesModule.vcxproj.filters index 33fe26da87b3..10d25e876246 100644 --- a/src/modules/fancyzones/dll/FancyZonesModule.vcxproj.filters +++ b/src/modules/fancyzones/dll/FancyZonesModule.vcxproj.filters @@ -26,7 +26,6 @@ - diff --git a/src/modules/fancyzones/dll/dllmain.cpp b/src/modules/fancyzones/dll/dllmain.cpp index e5056c13e161..9c481d441e74 100644 --- a/src/modules/fancyzones/dll/dllmain.cpp +++ b/src/modules/fancyzones/dll/dllmain.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include @@ -33,76 +32,6 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReser return TRUE; } -// This function is exported and called from FancyZonesEditor.exe to save a layout from the editor. -STDAPI PersistZoneSet( - PCWSTR activeKey, // Registry key holding ActiveZoneSet - PCWSTR resolutionKey, // Registry key to persist ZoneSet to - HMONITOR monitor, - WORD layoutId, // LayoutModel Id - int zoneCount, // Number of zones in zones - int zones[]) // Array of zones serialized in left/top/right/bottom chunks -{ - // See if we have already persisted this layout we can update. - UUID id{GUID_NULL}; - if (wil::unique_hkey key{ RegistryHelpers::OpenKey(resolutionKey) }) - { - ZoneSetPersistedData data{}; - DWORD dataSize = sizeof(data); - wchar_t value[256]{}; - DWORD valueLength = ARRAYSIZE(value); - DWORD i = 0; - while (RegEnumValueW(key.get(), i++, value, &valueLength, nullptr, nullptr, reinterpret_cast(&data), &dataSize) == ERROR_SUCCESS) - { - if (data.LayoutId == layoutId) - { - if (data.ZoneCount == zoneCount) - { - CLSIDFromString(value, &id); - break; - } - } - valueLength = ARRAYSIZE(value); - dataSize = sizeof(data); - } - } - - if (id == GUID_NULL) - { - // No existing layout found so let's create a new one. - UuidCreate(&id); - } - - if (id != GUID_NULL) - { - winrt::com_ptr zoneSet = MakeZoneSet( - ZoneSetConfig( - id, - layoutId, - MonitorFromPoint({}, MONITOR_DEFAULTTOPRIMARY), - resolutionKey)); - - for (int i = 0; i < zoneCount; i++) - { - const int baseIndex = i * 4; - const int left = zones[baseIndex]; - const int top = zones[baseIndex+1]; - const int right = zones[baseIndex+2]; - const int bottom = zones[baseIndex+3]; - zoneSet->AddZone(MakeZone({ left, top, right, bottom })); - } - zoneSet->Save(); - - wil::unique_cotaskmem_string zoneSetId; - if (SUCCEEDED_LOG(StringFromCLSID(id, &zoneSetId))) - { - RegistryHelpers::SetString(activeKey, L"ActiveZoneSetId", zoneSetId.get()); - } - - return S_OK; - } - return E_FAIL; -} - class FancyZonesModule : public PowertoyModuleIface { public: @@ -147,7 +76,7 @@ class FancyZonesModule : public PowertoyModuleIface if (!m_app) { Trace::FancyZones::EnableFancyZones(true); - m_app = MakeFancyZones(reinterpret_cast(&__ImageBase), m_settings.get()); + m_app = MakeFancyZones(reinterpret_cast(&__ImageBase), m_settings); if (m_app) { m_app->Run(); @@ -200,12 +129,14 @@ class FancyZonesModule : public PowertoyModuleIface { app_name = GET_RESOURCE_STRING(IDS_FANCYZONES); m_settings = MakeFancyZonesSettings(reinterpret_cast(&__ImageBase), FancyZonesModule::get_name()); + JSONHelpers::FancyZonesDataInstance().LoadFancyZonesData(); } private: void Disable(bool const traceEvent) { if (m_app) { + JSONHelpers::FancyZonesDataInstance().SaveFancyZonesData(); if (traceEvent) { Trace::FancyZones::EnableFancyZones(false); diff --git a/src/modules/fancyzones/dll/fancyzones.def b/src/modules/fancyzones/dll/fancyzones.def deleted file mode 100644 index 32441a425a5b..000000000000 --- a/src/modules/fancyzones/dll/fancyzones.def +++ /dev/null @@ -1,4 +0,0 @@ -LIBRARY fancyzones.dll - -EXPORTS - PersistZoneSet PRIVATE diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs index 72e529c85d33..185dbc2d1299 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/App.xaml.cs @@ -15,8 +15,6 @@ public partial class App : Application { public Settings ZoneSettings { get; } - private ushort _idInitial = 0; - public App() { ZoneSettings = new Settings(); @@ -24,37 +22,29 @@ public App() private void OnStartup(object sender, StartupEventArgs e) { - if (e.Args.Length > 1) + LayoutModel foundModel = null; + + foreach (LayoutModel model in ZoneSettings.DefaultModels) { - ushort.TryParse(e.Args[1], out _idInitial); + if (model.Type == Settings.ActiveZoneSetLayoutType) + { + // found match + foundModel = model; + break; + } } - LayoutModel foundModel = null; - - if (_idInitial != 0) + if (foundModel == null) { - foreach (LayoutModel model in ZoneSettings.DefaultModels) + foreach (LayoutModel model in Settings.CustomModels) { - if (model.Id == _idInitial) + if ("{" + model.Guid.ToString().ToUpper() + "}" == Settings.ActiveZoneSetUUid.ToUpper()) { // found match foundModel = model; break; } } - - if (foundModel == null) - { - foreach (LayoutModel model in ZoneSettings.CustomModels) - { - if (model.Id == _idInitial) - { - // found match - foundModel = model; - break; - } - } - } } if (foundModel == null) diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/EditorOverlay.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/EditorOverlay.xaml.cs index 4197c89a43bb..f51ae3ef508f 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/EditorOverlay.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/EditorOverlay.xaml.cs @@ -18,8 +18,11 @@ public partial class EditorOverlay : Window private readonly Settings _settings = ((App)Application.Current).ZoneSettings; private LayoutPreview _layoutPreview; + private UserControl _editor; + private static MainWindow _mainWindow = new MainWindow(); + public Int32Rect[] GetZoneRects() { if (_editor != null) @@ -79,27 +82,23 @@ private void OnLoaded(object sender, RoutedEventArgs e) public void ShowLayoutPicker() { - DataContext = null; - _editor = null; _layoutPreview = new LayoutPreview { IsActualSize = true, Opacity = 0.5, }; + Content = _layoutPreview; - MainWindow window = new MainWindow - { - Owner = this, - ShowActivated = true, - Topmost = true, - }; - window.Show(); + _mainWindow.Owner = this; + _mainWindow.ShowActivated = true; + _mainWindow.Topmost = true; + _mainWindow.Show(); // window is set to topmost to make sure it shows on top of PowerToys settings page // we can reset topmost flag now - window.Topmost = false; + _mainWindow.Topmost = false; } // These event handlers are used to track the current state of the Shift and Ctrl keys on the keyboard diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/FancyZonesEditor.csproj b/src/modules/fancyzones/editor/FancyZonesEditor/FancyZonesEditor.csproj index d110357db723..15dcade86e9a 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/FancyZonesEditor.csproj +++ b/src/modules/fancyzones/editor/FancyZonesEditor/FancyZonesEditor.csproj @@ -47,6 +47,7 @@ + @@ -194,6 +195,9 @@ 2.0.0-alpha0455 + + 4.6.0 + 1.1.118 runtime; build; native; contentfiles; analyzers; buildtransitive @@ -208,4 +212,4 @@ - \ No newline at end of file + diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs index b646ab6b00a0..8481a5b11398 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs @@ -21,7 +21,6 @@ public partial class MainWindow : MetroWindow public const int MaxZones = 40; private readonly Settings _settings = ((App)Application.Current).ZoneSettings; private static readonly string _defaultNamePrefix = "Custom Layout "; - private bool _editing = false; public int WrapPanelItemSize { get; set; } = 262; @@ -67,7 +66,7 @@ private void NewCustomLayoutButton_Click(object sender, RoutedEventArgs e) { WindowLayout window = new WindowLayout(); window.Show(); - Close(); + Hide(); } private void LayoutItem_Click(object sender, MouseButtonEventArgs e) @@ -95,12 +94,11 @@ private void EditLayout_Click(object sender, RoutedEventArgs e) } model.IsSelected = false; - _editing = true; - Close(); + Hide(); bool isPredefinedLayout = Settings.IsPredefinedLayout(model); - if (!_settings.CustomModels.Contains(model) || isPredefinedLayout) + if (!Settings.CustomModels.Contains(model) || isPredefinedLayout) { if (isPredefinedLayout) { @@ -110,7 +108,7 @@ private void EditLayout_Click(object sender, RoutedEventArgs e) } int maxCustomIndex = 0; - foreach (LayoutModel customModel in _settings.CustomModels) + foreach (LayoutModel customModel in Settings.CustomModels) { string name = customModel.Name; if (name.StartsWith(_defaultNamePrefix)) @@ -165,10 +163,8 @@ private void Apply_Click(object sender, RoutedEventArgs e) private void OnClosing(object sender, EventArgs e) { - if (!_editing) - { - EditorOverlay.Current.Close(); - } + LayoutModel.SerializeDeletedCustomZoneSets(); + EditorOverlay.Current.Close(); } private void OnInitialized(object sender, EventArgs e) @@ -178,7 +174,7 @@ private void OnInitialized(object sender, EventArgs e) private void SetSelectedItem() { - foreach (LayoutModel model in _settings.CustomModels) + foreach (LayoutModel model in Settings.CustomModels) { if (model.IsSelected) { diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/CanvasLayoutModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/CanvasLayoutModel.cs index 548463f22286..642e341619dd 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/CanvasLayoutModel.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/CanvasLayoutModel.cs @@ -2,7 +2,10 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; +using System.IO; +using System.Text.Json; using System.Windows; namespace FancyZonesEditor.Models @@ -11,40 +14,27 @@ namespace FancyZonesEditor.Models // Free form Layout Model, which specifies independent zone rects public class CanvasLayoutModel : LayoutModel { - private static readonly ushort _latestVersion = 0; - - public CanvasLayoutModel(ushort version, string name, ushort id, byte[] data) - : base(name, id) + public CanvasLayoutModel(string uuid, string name, LayoutType type, int referenceWidth, int referenceHeight, IList zones) + : base(uuid, name, type) { - if (version == _latestVersion) - { - Load(data); - } + _referenceWidth = referenceWidth; + _referenceHeight = referenceHeight; + Zones = zones; } - public CanvasLayoutModel(string name, ushort id, int referenceWidth, int referenceHeight) - : base(name, id) + public CanvasLayoutModel(string name, LayoutType type, int referenceWidth, int referenceHeight) + : base(name, type) { // Initialize Reference Size _referenceWidth = referenceWidth; _referenceHeight = referenceHeight; } - public CanvasLayoutModel(string name, ushort id) - : base(name, id) - { - } - public CanvasLayoutModel(string name) : base(name) { } - public CanvasLayoutModel() - : base() - { - } - // ReferenceWidth - the reference width for the layout rect that all Zones are relative to public int ReferenceWidth { @@ -104,26 +94,6 @@ public void AddZone(Int32Rect zone) FirePropertyChanged("Zones"); } - private void Load(byte[] data) - { - // Initialize this CanvasLayoutModel based on the given persistence data - // Skip version (2 bytes), id (2 bytes), and type (1 bytes) - int i = 5; - _referenceWidth = (data[i++] * 256) + data[i++]; - _referenceHeight = (data[i++] * 256) + data[i++]; - - int count = data[i++]; - - while (count-- > 0) - { - Zones.Add(new Int32Rect( - (data[i++] * 256) + data[i++], - (data[i++] * 256) + data[i++], - (data[i++] * 256) + data[i++], - (data[i++] * 256) + data[i++])); - } - } - // Clone // Implements the LayoutModel.Clone abstract method // Clones the data from this CanvasLayoutModel to a new CanvasLayoutModel @@ -143,44 +113,50 @@ public override LayoutModel Clone() return layout; } - // GetPersistData - // Implements the LayoutModel.GetPersistData abstract method - // Returns the state of this GridLayoutModel in persisted format - protected override byte[] GetPersistData() + // PersistData + // Implements the LayoutModel.PersistData abstract method + protected override void PersistData() { - byte[] data = new byte[10 + (Zones.Count * 8)]; - int i = 0; - - // Common persisted values between all layout types - data[i++] = (byte)(_latestVersion / 256); - data[i++] = (byte)(_latestVersion % 256); - data[i++] = 1; // LayoutModelType: 1 == CanvasLayoutModel - data[i++] = (byte)(Id / 256); - data[i++] = (byte)(Id % 256); - - // End common - data[i++] = (byte)(_referenceWidth / 256); - data[i++] = (byte)(_referenceWidth % 256); - data[i++] = (byte)(_referenceHeight / 256); - data[i++] = (byte)(_referenceHeight % 256); - data[i++] = (byte)Zones.Count; - - foreach (Int32Rect rect in Zones) + FileStream outputStream = File.Open(Settings.AppliedZoneSetTmpFile, FileMode.Create); + JsonWriterOptions writerOptions = new JsonWriterOptions + { + SkipValidation = true, + }; + using (var writer = new Utf8JsonWriter(outputStream, writerOptions)) { - data[i++] = (byte)(rect.X / 256); - data[i++] = (byte)(rect.X % 256); + writer.WriteStartObject(); + writer.WriteString("uuid", Guid.ToString()); + writer.WriteString("name", Name); + + writer.WriteString("type", "canvas"); + + writer.WriteStartObject("info"); + + writer.WriteNumber("ref-width", _referenceWidth); + writer.WriteNumber("ref-height", _referenceHeight); + + writer.WriteStartArray("zones"); + foreach (Int32Rect rect in Zones) + { + writer.WriteStartObject(); + writer.WriteNumber("X", rect.X); + writer.WriteNumber("Y", rect.Y); + writer.WriteNumber("width", rect.Width); + writer.WriteNumber("height", rect.Height); + writer.WriteEndObject(); + } - data[i++] = (byte)(rect.Y / 256); - data[i++] = (byte)(rect.Y % 256); + writer.WriteEndArray(); - data[i++] = (byte)(rect.Width / 256); - data[i++] = (byte)(rect.Width % 256); + // end info object + writer.WriteEndObject(); - data[i++] = (byte)(rect.Height / 256); - data[i++] = (byte)(rect.Height % 256); + // end root object + writer.WriteEndObject(); + writer.Flush(); } - return data; + outputStream.Close(); } } } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs index 54860f246685..510c20d30647 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs @@ -2,19 +2,20 @@ // 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.Generic; - -namespace FancyZonesEditor.Models -{ - // GridLayoutModel - // Grid-styled Layout Model, which specifies rows, columns, percentage sizes, and row/column spans - public class GridLayoutModel : LayoutModel - { - private static readonly ushort _latestVersion = 0; - - // Rows - number of rows in the Grid - public int Rows - { +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.Json; + +namespace FancyZonesEditor.Models +{ + // GridLayoutModel + // Grid-styled Layout Model, which specifies rows, columns, percentage sizes, and row/column spans + public class GridLayoutModel : LayoutModel + { + // Rows - number of rows in the Grid + public int Rows + { get { return _rows; @@ -22,19 +23,19 @@ public int Rows set { - if (_rows != value) - { - _rows = value; - FirePropertyChanged("Rows"); + if (_rows != value) + { + _rows = value; + FirePropertyChanged("Rows"); } } - } - - private int _rows = 1; - - // Columns - number of columns in the Grid - public int Columns - { + } + + private int _rows = 1; + + // Columns - number of columns in the Grid + public int Columns + { get { return _cols; @@ -42,19 +43,19 @@ public int Columns set { - if (_cols != value) - { - _cols = value; - FirePropertyChanged("Columns"); + if (_cols != value) + { + _cols = value; + FirePropertyChanged("Columns"); } } - } - - private int _cols = 1; - - // CellChildMap - represents which "children" belong in which grid cells; - // shows spanning children by the same index appearing in adjacent cells - // TODO: ideally no setter here - this means moving logic like "split" over to model + } + + private int _cols = 1; + + // CellChildMap - represents which "children" belong in which grid cells; + // shows spanning children by the same index appearing in adjacent cells + // TODO: ideally no setter here - this means moving logic like "split" over to model public int[,] CellChildMap { get; set; } // RowPercents - represents the %age height of each row in the grid @@ -69,179 +70,159 @@ public int Columns public IList FreeZones { get; } = new List(); public GridLayoutModel() - : base() - { - } + : base() + { + } public GridLayoutModel(string name) : base(name) { } - public GridLayoutModel(string name, ushort id) - : base(name, id) - { - } - - public GridLayoutModel(ushort version, string name, ushort id, byte[] data) - : base(name, id) - { - if (version == _latestVersion) - { - Reload(data); - } + public GridLayoutModel(string name, LayoutType type) + : base(name, type) + { + } + + public GridLayoutModel(string uuid, string name, LayoutType type, int rows, int cols, int[] rowPercents, int[] colsPercents, int[,] cellChildMap) + : base(uuid, name, type) + { + _rows = rows; + _cols = cols; + RowPercents = rowPercents; + ColumnPercents = colsPercents; + CellChildMap = cellChildMap; + } + + public void Reload(byte[] data) + { + // Skip version (2 bytes), id (2 bytes), and type (1 bytes) + int i = 5; + + Rows = data[i++]; + Columns = data[i++]; + + RowPercents = new int[Rows]; + for (int row = 0; row < Rows; row++) + { + RowPercents[row] = (data[i++] * 256) + data[i++]; + } + + ColumnPercents = new int[Columns]; + for (int col = 0; col < Columns; col++) + { + ColumnPercents[col] = (data[i++] * 256) + data[i++]; + } + + CellChildMap = new int[Rows, Columns]; + for (int row = 0; row < Rows; row++) + { + for (int col = 0; col < Columns; col++) + { + CellChildMap[row, col] = data[i++]; + } + } } - public void Reload(byte[] data) - { - // Skip version (2 bytes), id (2 bytes), and type (1 bytes) - int i = 5; - - Rows = data[i++]; - Columns = data[i++]; - - RowPercents = new int[Rows]; - for (int row = 0; row < Rows; row++) - { - RowPercents[row] = (data[i++] * 256) + data[i++]; - } - - ColumnPercents = new int[Columns]; - for (int col = 0; col < Columns; col++) - { - ColumnPercents[col] = (data[i++] * 256) + data[i++]; - } - - CellChildMap = new int[Rows, Columns]; - for (int row = 0; row < Rows; row++) - { - for (int col = 0; col < Columns; col++) - { - CellChildMap[row, col] = data[i++]; - } - } - } - - // Clone - // Implements the LayoutModel.Clone abstract method - // Clones the data from this GridLayoutModel to a new GridLayoutModel - public override LayoutModel Clone() - { - GridLayoutModel layout = new GridLayoutModel(Name); - int rows = Rows; - int cols = Columns; - - layout.Rows = rows; - layout.Columns = cols; - - int[,] cellChildMap = new int[rows, cols]; - for (int row = 0; row < rows; row++) - { - for (int col = 0; col < cols; col++) - { - cellChildMap[row, col] = CellChildMap[row, col]; - } - } - - layout.CellChildMap = cellChildMap; - - int[] rowPercents = new int[rows]; - for (int row = 0; row < rows; row++) - { - rowPercents[row] = RowPercents[row]; - } - - layout.RowPercents = rowPercents; - - int[] colPercents = new int[cols]; - for (int col = 0; col < cols; col++) - { - colPercents[col] = ColumnPercents[col]; - } - - layout.ColumnPercents = colPercents; - - return layout; - } - - // GetPersistData - // Implements the LayoutModel.GetPersistData abstract method - // Returns the state of this GridLayoutModel in persisted format - protected override byte[] GetPersistData() - { - int rows = Rows; - int cols = Columns; - - int[,] cellChildMap; - - if (FreeZones.Count == 0) - { - // no unused indices -- so we can just use the _cellChildMap as is - cellChildMap = CellChildMap; - } - else - { - // compress cellChildMap to not have gaps for unused child indices; - List mapping = new List(); - - cellChildMap = new int[rows, cols]; - - for (int row = 0; row < rows; row++) - { - for (int col = 0; col < cols; col++) - { - int source = CellChildMap[row, col]; - - int index = mapping.IndexOf(source); - if (index == -1) - { - index = mapping.Count; - mapping.Add(source); - } - - cellChildMap[row, col] = index; - } - } - } - - byte[] data = new byte[7 + (Rows * 2) + (Columns * 2) + (Rows * Columns)]; - - int i = 0; - - // Common persisted values between all layout types - data[i++] = (byte)(_latestVersion / 256); - data[i++] = (byte)(_latestVersion % 256); - data[i++] = 0; // LayoutModelType: 0 == GridLayoutModel - data[i++] = (byte)(Id / 256); - data[i++] = (byte)(Id % 256); - - // End common - data[i++] = (byte)Rows; - data[i++] = (byte)Columns; - - for (int row = 0; row < Rows; row++) - { - int rowPercent = RowPercents[row]; - data[i++] = (byte)(rowPercent / 256); - data[i++] = (byte)(rowPercent % 256); - } - - for (int col = 0; col < Columns; col++) - { - int colPercent = ColumnPercents[col]; - data[i++] = (byte)(colPercent / 256); - data[i++] = (byte)(colPercent % 256); - } - - for (int row = 0; row < Rows; row++) - { - for (int col = 0; col < Columns; col++) - { - data[i++] = (byte)cellChildMap[row, col]; - } - } - - return data; - } - } -} + // Clone + // Implements the LayoutModel.Clone abstract method + // Clones the data from this GridLayoutModel to a new GridLayoutModel + public override LayoutModel Clone() + { + GridLayoutModel layout = new GridLayoutModel(Name); + int rows = Rows; + int cols = Columns; + + layout.Rows = rows; + layout.Columns = cols; + + int[,] cellChildMap = new int[rows, cols]; + for (int row = 0; row < rows; row++) + { + for (int col = 0; col < cols; col++) + { + cellChildMap[row, col] = CellChildMap[row, col]; + } + } + + layout.CellChildMap = cellChildMap; + + int[] rowPercents = new int[rows]; + for (int row = 0; row < rows; row++) + { + rowPercents[row] = RowPercents[row]; + } + + layout.RowPercents = rowPercents; + + int[] colPercents = new int[cols]; + for (int col = 0; col < cols; col++) + { + colPercents[col] = ColumnPercents[col]; + } + + layout.ColumnPercents = colPercents; + + return layout; + } + + // PersistData + // Implements the LayoutModel.PersistData abstract method + protected override void PersistData() + { + FileStream outputStream = File.Open(Settings.AppliedZoneSetTmpFile, FileMode.Create); + using (var writer = new Utf8JsonWriter(outputStream, options: default)) + { + writer.WriteStartObject(); + writer.WriteString("uuid", Guid.ToString()); + writer.WriteString("name", Name); + + writer.WriteString("type", "grid"); + + writer.WriteStartObject("info"); + + writer.WriteNumber("rows", Rows); + writer.WriteNumber("columns", Columns); + + writer.WriteStartArray("rows-percentage"); + for (int row = 0; row < Rows; row++) + { + writer.WriteNumberValue(RowPercents[row]); + } + + writer.WriteEndArray(); + + writer.WriteStartArray("columns-percentage"); + for (int col = 0; col < Columns; col++) + { + writer.WriteNumberValue(ColumnPercents[col]); + } + + writer.WriteEndArray(); + + writer.WriteStartArray("cell-child-map"); + for (int row = 0; row < Rows; row++) + { + writer.WriteStartArray(); + for (int col = 0; col < Columns; col++) + { + writer.WriteNumberValue(CellChildMap[row, col]); + } + + writer.WriteEndArray(); + } + + writer.WriteEndArray(); + + // end info object + writer.WriteEndObject(); + + // end root object + writer.WriteEndObject(); + writer.Flush(); + } + + outputStream.Close(); + } + } +} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs index 1d431c913f2b..db9955816e2e 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs @@ -1,15 +1,28 @@ -// Copyright (c) Microsoft Corporation +// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; -using System.Runtime.InteropServices; -using Microsoft.Win32; +using System.IO; +using System.Text.Json; +using System.Windows; namespace FancyZonesEditor.Models { + public enum LayoutType + { + Focus, + Columns, + Rows, + Grid, + PriorityGrid, + Custom, + Blank, + } + // Base LayoutModel // Manages common properties and base persistence public abstract class LayoutModel : INotifyPropertyChanged @@ -19,18 +32,30 @@ public abstract class LayoutModel : INotifyPropertyChanged protected LayoutModel() { + _guid = Guid.NewGuid(); + Type = LayoutType.Custom; } protected LayoutModel(string name) : this() { + _guid = Guid.NewGuid(); + Name = name; + } + + protected LayoutModel(string uuid, string name, LayoutType type) + : this() + { + _guid = Guid.Parse(uuid); Name = name; + Type = type; } - protected LayoutModel(string name, ushort id) + protected LayoutModel(string name, LayoutType type) : this(name) { - _id = id; + _guid = Guid.NewGuid(); + Type = type; } // Name - the display name for this layout model - is also used as the key in the registry @@ -53,22 +78,17 @@ public string Name private string _name; - // Id - the unique ID for this layout model - is used to connect fancy zones' ZonesSets with the editor's Layouts - // - note: 0 means this is a new layout, which means it will have its ID auto-assigned on persist - public ushort Id + public LayoutType Type { get; set; } + + public Guid Guid { get { - if (_id == 0) - { - _id = ++_maxId; - } - - return _id; + return _guid; } } - private ushort _id = 0; + private Guid _guid; // IsSelected (not-persisted) - tracks whether or not this LayoutModel is selected in the picker // TODO: once we switch to a picker per monitor, we need to move this state to the view @@ -103,51 +123,99 @@ protected virtual void FirePropertyChanged(string propertyName) // Removes this Layout from the registry and the loaded CustomModels list public void Delete() { - RegistryKey key = Registry.CurrentUser.OpenSubKey(_registryPath, true); - if (key != null) - { - key.DeleteValue(Name); - } - int i = _customModels.IndexOf(this); if (i != -1) { _customModels.RemoveAt(i); + _deletedCustomModels.Add(Guid.ToString().ToUpper()); } } - // Loads all the Layouts persisted under the Layouts key in the registry + public static void SerializeDeletedCustomZoneSets() + { + FileStream outputStream = File.Open(Settings.CustomZoneSetsTmpFile, FileMode.Create); + var writer = new Utf8JsonWriter(outputStream, options: default); + writer.WriteStartObject(); + writer.WriteStartArray("deleted-custom-zone-sets"); + foreach (string zoneSet in _deletedCustomModels) + { + writer.WriteStringValue(zoneSet); + } + + writer.WriteEndArray(); + writer.WriteEndObject(); + writer.Flush(); + outputStream.Close(); + } + + // Loads all the custom Layouts from tmp file passed by FancuZonesLib public static ObservableCollection LoadCustomModels() { _customModels = new ObservableCollection(); - RegistryKey key = Registry.CurrentUser.OpenSubKey(_registryPath); - if (key != null) + FileStream inputStream = File.Open(Settings.CustomZoneSetsTmpFile, FileMode.Open); + var jsonObject = JsonDocument.Parse(inputStream, options: default); + JsonElement.ArrayEnumerator customZoneSetsEnumerator = jsonObject.RootElement.GetProperty("custom-zone-sets").EnumerateArray(); + while (customZoneSetsEnumerator.MoveNext()) { - foreach (string name in key.GetValueNames()) + var current = customZoneSetsEnumerator.Current; + string name = current.GetProperty("name").GetString(); + string type = current.GetProperty("type").GetString(); + string uuid = current.GetProperty("uuid").GetString(); + var info = current.GetProperty("info"); + if (type.Equals("grid")) { - LayoutModel model = null; - byte[] data = (byte[])Registry.GetValue(_fullRegistryPath, name, null); - - ushort version = (ushort)((data[0] * 256) + data[1]); - byte type = data[2]; - ushort id = (ushort)((data[3] * 256) + data[4]); + int rows = info.GetProperty("rows").GetInt32(); + int columns = info.GetProperty("columns").GetInt32(); + int[] rowsPercentage = new int[rows]; + JsonElement.ArrayEnumerator rowsPercentageEnumerator = info.GetProperty("rows-percentage").EnumerateArray(); + int i = 0; + while (rowsPercentageEnumerator.MoveNext()) + { + rowsPercentage[i++] = rowsPercentageEnumerator.Current.GetInt32(); + } - switch (type) + i = 0; + int[] columnsPercentage = new int[columns]; + JsonElement.ArrayEnumerator columnsPercentageEnumerator = info.GetProperty("columns-percentage").EnumerateArray(); + while (columnsPercentageEnumerator.MoveNext()) { - case 0: model = new GridLayoutModel(version, name, id, data); break; - case 1: model = new CanvasLayoutModel(version, name, id, data); break; + columnsPercentage[i++] = columnsPercentageEnumerator.Current.GetInt32(); } - if (model != null) + i = 0; + JsonElement.ArrayEnumerator cellChildMapRows = info.GetProperty("cell-child-map").EnumerateArray(); + int[,] cellChildMap = new int[rows, columns]; + while (cellChildMapRows.MoveNext()) { - if (_maxId < id) + int j = 0; + JsonElement.ArrayEnumerator cellChildMapRowElems = cellChildMapRows.Current.EnumerateArray(); + while (cellChildMapRowElems.MoveNext()) { - _maxId = id; + cellChildMap[i, j++] = cellChildMapRowElems.Current.GetInt32(); } - _customModels.Add(model); + i++; + } + + _customModels.Add(new GridLayoutModel(uuid, name, LayoutType.Custom, rows, columns, rowsPercentage, columnsPercentage, cellChildMap)); + } + else if (type.Equals("canvas")) + { + int referenceWidth = info.GetProperty("ref-width").GetInt32(); + int referenceHeight = info.GetProperty("ref-height").GetInt32(); + JsonElement.ArrayEnumerator zonesEnumerator = info.GetProperty("zones").EnumerateArray(); + IList zones = new List(); + while (zonesEnumerator.MoveNext()) + { + int x = zonesEnumerator.Current.GetProperty("X").GetInt32(); + int y = zonesEnumerator.Current.GetProperty("Y").GetInt32(); + int width = zonesEnumerator.Current.GetProperty("width").GetInt32(); + int height = zonesEnumerator.Current.GetProperty("height").GetInt32(); + zones.Add(new Int32Rect(x, y, width, height)); } + + _customModels.Add(new CanvasLayoutModel(uuid, name, LayoutType.Custom, referenceWidth, referenceHeight, zones)); } } @@ -155,55 +223,67 @@ public static ObservableCollection LoadCustomModels() } private static ObservableCollection _customModels = null; - - private static ushort _maxId = 0; + private static List _deletedCustomModels = new List(); // Callbacks that the base LayoutModel makes to derived types - protected abstract byte[] GetPersistData(); + protected abstract void PersistData(); public abstract LayoutModel Clone(); public void Persist(System.Windows.Int32Rect[] zones) { - // Persist the editor data - Registry.SetValue(_fullRegistryPath, Name, GetPersistData(), Microsoft.Win32.RegistryValueKind.Binary); + PersistData(); Apply(zones); } public void Apply(System.Windows.Int32Rect[] zones) { - // Persist the zone data back into FZ - var module = Native.LoadLibrary("fancyzones.dll"); - if (module == IntPtr.Zero) - { - return; - } + int zoneCount = zones.Length; + FileStream outputStream = File.Open(Settings.ActiveZoneSetTmpFile, FileMode.Create); + var writer = new Utf8JsonWriter(outputStream, options: default); + + writer.WriteStartObject(); + writer.WriteString("device-id", Settings.UniqueKey); - var pfn = Native.GetProcAddress(module, "PersistZoneSet"); - if (pfn == IntPtr.Zero) + writer.WriteStartObject("active-zoneset"); + writer.WriteString("uuid", "{" + Guid.ToString().ToUpper() + "}"); + bool custom = false; + switch (Type) { - return; + case LayoutType.Focus: + writer.WriteString("type", "focus"); + break; + case LayoutType.Rows: + writer.WriteString("type", "rows"); + break; + case LayoutType.Columns: + writer.WriteString("type", "columns"); + break; + case LayoutType.Grid: + writer.WriteString("type", "grid"); + break; + case LayoutType.PriorityGrid: + writer.WriteString("type", "priority-grid"); + break; + case LayoutType.Custom: + writer.WriteString("type", "custom"); + custom = true; + break; } - // Scale all the zones to the DPI and then pack them up to be marshalled. - int zoneCount = zones.Length; - var zoneArray = new int[zoneCount * 4]; - for (int i = 0; i < zones.Length; i++) + if (!custom) { - var left = (int)(zones[i].X * Settings.Dpi); - var top = (int)(zones[i].Y * Settings.Dpi); - var right = left + (int)(zones[i].Width * Settings.Dpi); - var bottom = top + (int)(zones[i].Height * Settings.Dpi); - - var index = i * 4; - zoneArray[index] = left; - zoneArray[index + 1] = top; - zoneArray[index + 2] = right; - zoneArray[index + 3] = bottom; + writer.WriteNumber("zone-count", zoneCount); } - var persistZoneSet = Marshal.GetDelegateForFunctionPointer(pfn); - persistZoneSet(Settings.UniqueKey, Settings.WorkAreaKey, Settings.Monitor, _id, zoneCount, zoneArray); + writer.WriteEndObject(); + + writer.WriteBoolean("editor-show-spacing", Settings._settingsToPersist.ShowSpacing); + writer.WriteNumber("editor-spacing", Settings._settingsToPersist.Spacing); + writer.WriteNumber("editor-zone-count", Settings._settingsToPersist.ZoneCount); + writer.WriteEndObject(); + writer.Flush(); + outputStream.Close(); } } } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs index d47811bb2b4c..35bca6e9f18b 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs @@ -7,9 +7,10 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.Globalization; +using System.IO; +using System.Text.Json; using System.Windows; using FancyZonesEditor.Models; -using Microsoft.Win32; namespace FancyZonesEditor { @@ -18,20 +19,20 @@ namespace FancyZonesEditor // Other UIs in the editor will subscribe to change events on the properties to stay up to date as these properties change public class Settings : INotifyPropertyChanged { - private readonly CanvasLayoutModel _blankCustomModel; + private static CanvasLayoutModel _blankCustomModel; private readonly CanvasLayoutModel _focusModel; private readonly GridLayoutModel _rowsModel; private readonly GridLayoutModel _columnsModel; private readonly GridLayoutModel _gridModel; private readonly GridLayoutModel _priorityGridModel; - private static readonly ushort _focusModelId = 0xFFFF; - private static readonly ushort _rowsModelId = 0xFFFE; - private static readonly ushort _columnsModelId = 0xFFFD; - private static readonly ushort _gridModelId = 0xFFFC; - private static readonly ushort _priorityGridModelId = 0xFFFB; - private static readonly ushort _blankCustomModelId = 0xFFFA; - private static readonly ushort _lastPrefinedId = _blankCustomModelId; + public const ushort _focusModelId = 0xFFFF; + public const ushort _rowsModelId = 0xFFFE; + public const ushort _columnsModelId = 0xFFFD; + public const ushort _gridModelId = 0xFFFC; + public const ushort _priorityGridModelId = 0xFFFB; + public const ushort _blankCustomModelId = 0xFFFA; + public const ushort _lastPrefinedId = _blankCustomModelId; // hard coded data for all the "Priority Grid" configurations that are unique to "Grid" private static readonly byte[][] _priorityData = new byte[][] @@ -73,34 +74,32 @@ public Settings() // Initialize the five default layout models: Focus, Columns, Rows, Grid, and PriorityGrid DefaultModels = new List(5); - _focusModel = new CanvasLayoutModel("Focus", _focusModelId, (int)_workArea.Width, (int)_workArea.Height); + _focusModel = new CanvasLayoutModel("Focus", LayoutType.Focus, (int)_workArea.Width, (int)_workArea.Height); DefaultModels.Add(_focusModel); - _columnsModel = new GridLayoutModel("Columns", _columnsModelId) + _columnsModel = new GridLayoutModel("Columns", LayoutType.Columns) { Rows = 1, RowPercents = new int[1] { _multiplier }, }; DefaultModels.Add(_columnsModel); - _rowsModel = new GridLayoutModel("Rows", _rowsModelId) + _rowsModel = new GridLayoutModel("Rows", LayoutType.Rows) { Columns = 1, ColumnPercents = new int[1] { _multiplier }, }; DefaultModels.Add(_rowsModel); - _gridModel = new GridLayoutModel("Grid", _gridModelId); + _gridModel = new GridLayoutModel("Grid", LayoutType.Grid); DefaultModels.Add(_gridModel); - _priorityGridModel = new GridLayoutModel("Priority Grid", _priorityGridModelId); + _priorityGridModel = new GridLayoutModel("Priority Grid", LayoutType.PriorityGrid); DefaultModels.Add(_priorityGridModel); - _blankCustomModel = new CanvasLayoutModel("Create new custom", _blankCustomModelId, (int)_workArea.Width, (int)_workArea.Height); + _blankCustomModel = new CanvasLayoutModel("Create new custom", LayoutType.Blank, (int)_workArea.Width, (int)_workArea.Height); - _zoneCount = ReadRegistryInt("ZoneCount", 3); - _spacing = ReadRegistryInt("Spacing", 16); - _showSpacing = ReadRegistryInt("ShowSpacing", 1) == 1; + _settingsToPersist = new SettingsToPersist(_showSpacing, _spacing, _zoneCount); UpdateLayoutModels(); } @@ -118,7 +117,7 @@ public int ZoneCount if (_zoneCount != value) { _zoneCount = value; - Registry.SetValue(_uniqueRegistryPath, "ZoneCount", _zoneCount, RegistryValueKind.DWord); + _settingsToPersist.ZoneCount = value; UpdateLayoutModels(); FirePropertyChanged("ZoneCount"); } @@ -140,7 +139,7 @@ public int Spacing if (_spacing != value) { _spacing = value; - Registry.SetValue(_uniqueRegistryPath, "Spacing", _spacing, RegistryValueKind.DWord); + _settingsToPersist.Spacing = value; FirePropertyChanged("Spacing"); } } @@ -161,7 +160,7 @@ public bool ShowSpacing if (_showSpacing != value) { _showSpacing = value; - Registry.SetValue(_uniqueRegistryPath, "ShowSpacing", _showSpacing, RegistryValueKind.DWord); + _settingsToPersist.ShowSpacing = value; FirePropertyChanged("ShowSpacing"); } } @@ -169,6 +168,42 @@ public bool ShowSpacing private bool _showSpacing; + public class SettingsToPersist + { + public SettingsToPersist(bool showSpacing, int spacing, int zoneCount) + { + _showSpacing = showSpacing; + _spacing = spacing; + _zoneCount = zoneCount; + } + + private bool _showSpacing; + + public bool ShowSpacing + { + get { return _showSpacing; } + set { _showSpacing = value; } + } + + private int _spacing; + + public int Spacing + { + get { return _spacing; } + set { _spacing = value; } + } + + private int _zoneCount; + + public int ZoneCount + { + get { return _zoneCount; } + set { _zoneCount = value; } + } + } + + public static SettingsToPersist _settingsToPersist; + // IsShiftKeyPressed - is the shift key currently being held down public bool IsShiftKeyPressed { @@ -220,18 +255,35 @@ public Rect WorkArea public static string UniqueKey { get; private set; } - private string _uniqueRegistryPath; + public static string ActiveZoneSetUUid { get; private set; } - public static string WorkAreaKey { get; private set; } + public static LayoutType ActiveZoneSetLayoutType { get; private set; } - public static float Dpi { get; private set; } + public static string ActiveZoneSetTmpFile + { + get { return _activeZoneSetTmpFile; } + } - private int ReadRegistryInt(string valueName, int defaultValue) + private static string _activeZoneSetTmpFile; + + public static string AppliedZoneSetTmpFile { - object obj = Registry.GetValue(_uniqueRegistryPath, valueName, defaultValue); - return (obj != null) ? (int)obj : defaultValue; + get { return _appliedZoneSetTmpFile; } } + private static string _appliedZoneSetTmpFile; + + public static string CustomZoneSetsTmpFile + { + get { return _customZoneSetsTmpFile; } + } + + private static string _customZoneSetsTmpFile; + + public static string WorkAreaKey { get; private set; } + + public static float Dpi { get; private set; } + // UpdateLayoutModels // Update the five default layouts based on the new ZoneCount private void UpdateLayoutModels() @@ -327,40 +379,87 @@ private void UpdateLayoutModels() } } + private void ParseDeviceInfoData() + { + FileStream inputStream = File.Open(Settings.ActiveZoneSetTmpFile, FileMode.Open); + var jsonObject = JsonDocument.Parse(inputStream, options: default).RootElement; + + UniqueKey = jsonObject.GetProperty("device-id").GetString(); + ActiveZoneSetUUid = jsonObject.GetProperty("active-zoneset").GetProperty("uuid").GetString(); + string layoutType = jsonObject.GetProperty("active-zoneset").GetProperty("type").GetString(); + + if (ActiveZoneSetUUid == "null") + { + // Default selection is Focus + ActiveZoneSetLayoutType = LayoutType.Focus; + } + else + { + switch (layoutType) + { + case "focus": + ActiveZoneSetLayoutType = LayoutType.Focus; + break; + case "columns": + ActiveZoneSetLayoutType = LayoutType.Columns; + break; + case "rows": + ActiveZoneSetLayoutType = LayoutType.Rows; + break; + case "grid": + ActiveZoneSetLayoutType = LayoutType.Grid; + break; + case "priority-grid": + ActiveZoneSetLayoutType = LayoutType.PriorityGrid; + break; + case "custom": + ActiveZoneSetLayoutType = LayoutType.Custom; + break; + } + } + + _showSpacing = jsonObject.GetProperty("editor-show-spacing").GetBoolean(); + _spacing = jsonObject.GetProperty("editor-spacing").GetInt32(); + _zoneCount = jsonObject.GetProperty("editor-zone-count").GetInt32(); + } + private void ParseCommandLineArgs() { _workArea = SystemParameters.WorkArea; Monitor = 0; - _uniqueRegistryPath = FullRegistryPath; - UniqueKey = string.Empty; Dpi = 1; string[] args = Environment.GetCommandLineArgs(); - if (args.Length == 7) + if (args.Length == 8) { - // 1 = unique key for per-monitor settings - // 2 = layoutid used to generate current layout (used to pick the default layout to show) - // 3 = handle to monitor (passed back to engine to persist data) - // 4 = X_Y_Width_Height in a dpi-scaled-but-unaware coords (where EditorOverlay shows up) - // 5 = resolution key (passed back to engine to persist data) - // 6 = monitor DPI (float) - UniqueKey = args[1]; - _uniqueRegistryPath += "\\" + UniqueKey; - - var parsedLocation = args[4].Split('_'); + // 1 = handle to monitor (passed back to engine to persist data) + // 2 = X_Y_Width_Height in a dpi-scaled-but-unaware coords (where EditorOverlay shows up) + // 3 = resolution key (passed back to engine to persist data) + // 4 = monitor DPI (float) + // 5 = temp file for active zone set + // 6 = temp file for applied zone set + // 7 = temp file for custom zone sets + if (uint.TryParse(args[1], out uint monitor)) + { + Monitor = monitor; + } + + var parsedLocation = args[2].Split('_'); var x = int.Parse(parsedLocation[0]); var y = int.Parse(parsedLocation[1]); var width = int.Parse(parsedLocation[2]); var height = int.Parse(parsedLocation[3]); - WorkAreaKey = args[5]; + _workArea = new Rect(x, y, width, height); + + WorkAreaKey = args[3]; // Try invariant culture first, caller likely uses invariant i.e. "C" locale to construct parameters foreach (var cultureInfo in new[] { CultureInfo.InvariantCulture, CultureInfo.CurrentCulture, CultureInfo.CurrentUICulture }) { try { - Dpi = float.Parse(args[6], cultureInfo); + Dpi = float.Parse(args[4], cultureInfo); break; } catch (FormatException) @@ -368,18 +467,17 @@ private void ParseCommandLineArgs() } } - _workArea = new Rect(x, y, width, height); + _activeZoneSetTmpFile = args[5]; + _appliedZoneSetTmpFile = args[6]; + _customZoneSetsTmpFile = args[7]; - if (uint.TryParse(args[4], out uint monitor)) - { - Monitor = monitor; - } + ParseDeviceInfoData(); } } public IList DefaultModels { get; } - public ObservableCollection CustomModels + public static ObservableCollection CustomModels { get { @@ -393,14 +491,14 @@ public ObservableCollection CustomModels } } - private ObservableCollection _customModels; + private static ObservableCollection _customModels; public static readonly string RegistryPath = "SOFTWARE\\SuperFancyZones"; public static readonly string FullRegistryPath = "HKEY_CURRENT_USER\\" + RegistryPath; public static bool IsPredefinedLayout(LayoutModel model) { - return model.Id >= _lastPrefinedId; + return model.Type != LayoutType.Custom; } // implementation of INotifyProeprtyChanged diff --git a/src/modules/fancyzones/lib/FancyZones.cpp b/src/modules/fancyzones/lib/FancyZones.cpp index 489bf2e2421a..b5c1b96b4801 100644 --- a/src/modules/fancyzones/lib/FancyZones.cpp +++ b/src/modules/fancyzones/lib/FancyZones.cpp @@ -6,14 +6,27 @@ #include "lib/Settings.h" #include "lib/ZoneWindow.h" #include "lib/RegistryHelpers.h" +#include "lib/JsonHelpers.h" +#include "lib/ZoneSet.h" #include "trace.h" #include #include +#include + +enum class DisplayChangeType +{ + WorkArea, + DisplayChange, + VirtualDesktop, + Editor, + Initialization +}; namespace std { - template<> struct hash + template<> + struct hash { size_t operator()(const GUID& Value) const { @@ -26,9 +39,9 @@ namespace std struct FancyZones : public winrt::implements { public: - FancyZones(HINSTANCE hinstance, IFancyZonesSettings* settings) noexcept - : m_hinstance(hinstance) - , m_settings(settings) + FancyZones(HINSTANCE hinstance, const winrt::com_ptr& settings) noexcept : + m_hinstance(hinstance), + m_settings(settings) { m_settings->SetCallback(this); } @@ -38,7 +51,11 @@ struct FancyZones : public winrt::implementssecond->ActiveZoneSet()) { - return it->second->ActiveZoneSet()->Id(); + //NOTE: as public method it's unsafe without lock, but it's called from AddZoneWindow through making ZoneWindow that causes deadlock + //TODO: needs refactoring + if (auto it = m_zoneWindowMap.find(monitor); it != m_zoneWindowMap.end() && it->second->ActiveZoneSet()) + { + return it->second->ActiveZoneSet(); } - return GUID_NULL; + return nullptr; } IFACEMETHODIMP_(int) GetZoneHighlightOpacity() noexcept { @@ -85,16 +105,25 @@ struct FancyZones : public winrt::implements - require_read_lock(const std::shared_lock& lock) { lock; } + require_read_lock(const std::shared_lock& lock) + { + lock; + } template - require_read_lock(const std::unique_lock& lock) { lock; } + require_read_lock(const std::unique_lock& lock) + { + lock; + } }; struct require_write_lock { template - require_write_lock(const std::unique_lock& lock) { lock; } + require_write_lock(const std::unique_lock& lock) + { + lock; + } }; bool IsInterestingWindow(HWND window) noexcept; @@ -115,11 +144,11 @@ struct FancyZones : public winrt::implements> m_zoneWindowMap; // Map of monitor to ZoneWindow (one per monitor) winrt::com_ptr m_zoneWindowMoveSize; // "Active" ZoneWindow, where the move/size is happening. Will update as drag moves between monitors. - IFancyZonesSettings* m_settings{}; + winrt::com_ptr m_settings{}; GUID m_currentVirtualDesktopId{}; // UUID of the current virtual desktop. Is GUID_NULL until first VD switch per session. std::unordered_map m_virtualDesktopIds; wil::unique_handle m_terminateEditorEvent; // Handle of FancyZonesEditor.exe we launch and wait on @@ -140,6 +169,9 @@ struct FancyZones : public winrt::implementsGetSettings().editorHotkey.get_modifiers(), m_settings->GetSettings().editorHotkey.get_code()); VirtualDesktopInitialize(); - m_dpiUnawareThread.submit(OnThreadExecutor::task_t{[]{ - SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE); - SetThreadDpiHostingBehavior(DPI_HOSTING_BEHAVIOR_MIXED); - }}).wait(); + m_dpiUnawareThread.submit(OnThreadExecutor::task_t{ [] { + SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE); + SetThreadDpiHostingBehavior(DPI_HOSTING_BEHAVIOR_MIXED); + } }) + .wait(); - if (RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\VirtualDesktops", 0, KEY_ALL_ACCESS, &m_virtualDesktopsRegKey) == ERROR_SUCCESS) { + if (RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\VirtualDesktops", 0, KEY_ALL_ACCESS, &m_virtualDesktopsRegKey) == ERROR_SUCCESS) + { m_terminateVirtualDesktopTrackerEvent.reset(CreateEvent(nullptr, FALSE, FALSE, nullptr)); m_virtualDesktopTrackerThread.submit( OnThreadExecutor::task_t{ std::bind(&FancyZones::HandleVirtualDesktopUpdates, this, m_terminateVirtualDesktopTrackerEvent.get()) }); @@ -188,10 +223,12 @@ IFACEMETHODIMP_(void) FancyZones::Destroy() noexcept DestroyWindow(m_window); m_window = nullptr; } - if (m_terminateVirtualDesktopTrackerEvent) { + if (m_terminateVirtualDesktopTrackerEvent) + { SetEvent(m_terminateVirtualDesktopTrackerEvent.get()); } - if (m_virtualDesktopsRegKey) { + if (m_virtualDesktopsRegKey) + { RegCloseKey(m_virtualDesktopsRegKey); m_virtualDesktopsRegKey = nullptr; } @@ -229,6 +266,7 @@ IFACEMETHODIMP_(void) FancyZones::VirtualDesktopChanged() noexcept { // VirtualDesktopChanged is called from another thread but results in new windows being created. // Jump over to the UI thread to handle it. + std::shared_lock readLock(m_lock); PostMessage(m_window, WM_PRIV_VDCHANGED, 0, 0); } @@ -244,11 +282,11 @@ IFACEMETHODIMP_(void) FancyZones::WindowCreated(HWND window) noexcept if (m_settings->GetSettings().appLastZone_moveWindows && IsInterestingWindow(window)) { auto processPath = get_process_path(window); - if (!processPath.empty()) + if (!processPath.empty()) { - INT zoneIndex = -1; - LRESULT res = RegistryHelpers::GetAppLastZone(window, processPath.data(), &zoneIndex); - if ((res == ERROR_SUCCESS) && (zoneIndex != -1)) + int zoneIndex = JSONHelpers::FancyZonesDataInstance().GetAppLastZone(window, processPath.data()); + + if (zoneIndex != -1) { MoveWindowIntoZoneByIndex(window, zoneIndex); } @@ -346,11 +384,12 @@ void FancyZones::ToggleEditor() noexcept MONITORINFOEX mi; mi.cbSize = sizeof(mi); - m_dpiUnawareThread.submit(OnThreadExecutor::task_t{[&]{ - GetMonitorInfo(monitor, &mi); - }}).wait(); + m_dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] { + GetMonitorInfo(monitor, &mi); + } }) + .wait(); - if(use_cursorpos_editor_startupscreen) + if (use_cursorpos_editor_startupscreen) { DPIAware::GetScreenDPIForPoint(currentCursorPos, dpi_x, dpi_y); } @@ -359,6 +398,10 @@ void FancyZones::ToggleEditor() noexcept DPIAware::GetScreenDPIForWindow(foregroundWindow, dpi_x, dpi_y); } + auto zoneWindow = iter->second; + + JSONHelpers::FancyZonesDataInstance().CustomZoneSetsToJsonFile(ZoneWindowUtils::GetCustomZoneSetsTmpPath()); + const auto taskbar_x_offset = MulDiv(mi.rcWork.left - mi.rcMonitor.left, DPIAware::DEFAULT_DPI, dpi_x); const auto taskbar_y_offset = MulDiv(mi.rcWork.top - mi.rcMonitor.top, DPIAware::DEFAULT_DPI, dpi_y); @@ -367,22 +410,25 @@ void FancyZones::ToggleEditor() noexcept const auto y = mi.rcMonitor.top + taskbar_y_offset; const auto width = mi.rcWork.right - mi.rcWork.left; const auto height = mi.rcWork.bottom - mi.rcWork.top; - const std::wstring editorLocation = + const std::wstring editorLocation = std::to_wstring(x) + L"_" + std::to_wstring(y) + L"_" + std::to_wstring(width) + L"_" + std::to_wstring(height); - const auto activeZoneSet = iter->second->ActiveZoneSet(); - const std::wstring layoutID = activeZoneSet ? std::to_wstring(activeZoneSet->LayoutId()) : L"0"; + const auto& deviceInfo = JSONHelpers::FancyZonesDataInstance().GetDeviceInfoMap().at(zoneWindow->UniqueId()); + + JSONHelpers::DeviceInfoJSON deviceInfoJson{ zoneWindow->UniqueId(), deviceInfo }; + JSONHelpers::FancyZonesDataInstance().SerializeDeviceInfoToTmpFile(deviceInfoJson, ZoneWindowUtils::GetActiveZoneSetTmpPath()); const std::wstring params = - iter->second->UniqueId() + L" " + - layoutID + L" " + - std::to_wstring(reinterpret_cast(monitor)) + L" " + - editorLocation + L" " + - iter->second->WorkAreaKey() + L" " + - std::to_wstring(static_cast(dpi_x) / DPIAware::DEFAULT_DPI); + /*1*/ std::to_wstring(reinterpret_cast(monitor)) + L" " + + /*2*/ editorLocation + L" " + + /*3*/ zoneWindow->WorkAreaKey() + L" " + + /*4*/ std::to_wstring(static_cast(dpi_x) / DPIAware::DEFAULT_DPI) + L" " + + /*5*/ ZoneWindowUtils::GetActiveZoneSetTmpPath() + L" " + + /*6*/ ZoneWindowUtils::GetAppliedZoneSetTmpPath() + L" " + + /*7*/ ZoneWindowUtils::GetCustomZoneSetsTmpPath(); SHELLEXECUTEINFO sei{ sizeof(sei) }; sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI }; @@ -394,8 +440,7 @@ void FancyZones::ToggleEditor() noexcept // Launch the editor on a background thread // Wait for the editor's process to exit // Post back to the main thread to update - std::thread waitForEditorThread([window = m_window, processHandle = sei.hProcess, terminateEditorEvent = m_terminateEditorEvent.get()]() - { + std::thread waitForEditorThread([window = m_window, processHandle = sei.hProcess, terminateEditorEvent = m_terminateEditorEvent.get()]() { HANDLE waitEvents[2] = { processHandle, terminateEditorEvent }; auto result = WaitForMultipleObjects(2, waitEvents, false, INFINITE); if (result == WAIT_OBJECT_0 + 0) @@ -419,6 +464,7 @@ void FancyZones::ToggleEditor() noexcept void FancyZones::SettingsChanged() noexcept { + std::shared_lock readLock(m_lock); // Update the hotkey UnregisterHotKey(m_window, 1); RegisterHotKey(m_window, 1, m_settings->GetSettings().editorHotkey.get_modifiers(), m_settings->GetSettings().editorHotkey.get_code()); @@ -504,11 +550,12 @@ void FancyZones::OnDisplayChange(DisplayChangeType changeType) noexcept // the first virtual desktop switch happens. If the user hasn't switched virtual desktops in this session // then this value will be empty. This means loading the first virtual desktop's configuration can be // funky the first time we load up at boot since the user will not have switched virtual desktops yet. - std::shared_lock readLock(m_lock); GUID currentVirtualDesktopId{}; if (SUCCEEDED(RegistryHelpers::GetCurrentVirtualDesktop(¤tVirtualDesktopId))) { + m_lock.lock(); m_currentVirtualDesktopId = currentVirtualDesktopId; + m_lock.unlock(); } else { @@ -548,13 +595,16 @@ void FancyZones::AddZoneWindow(HMONITOR monitor, PCWSTR deviceId) noexcept wil::unique_cotaskmem_string virtualDesktopId; if (SUCCEEDED_LOG(StringFromCLSID(m_currentVirtualDesktopId, &virtualDesktopId))) { + std::wstring uniqueId = GenerateUniqueId(monitor, deviceId, virtualDesktopId.get()); bool newVirtualDesktop = true; - if (auto it = m_virtualDesktopIds.find(m_currentVirtualDesktopId); it != end(m_virtualDesktopIds)) { + if (auto it = m_virtualDesktopIds.find(m_currentVirtualDesktopId); it != end(m_virtualDesktopIds)) + { newVirtualDesktop = it->second; + JSONHelpers::FancyZonesDataInstance().SetActiveDeviceId(uniqueId); } const bool flash = m_settings->GetSettings().zoneSetChange_flashZones && newVirtualDesktop; - if (auto zoneWindow = MakeZoneWindow(this, m_hinstance, monitor, deviceId, virtualDesktopId.get(), flash)) + if (auto zoneWindow = MakeZoneWindow(this, m_hinstance, monitor, uniqueId, flash)) { m_zoneWindowMap[monitor] = std::move(zoneWindow); } @@ -589,7 +639,7 @@ LRESULT CALLBACK FancyZones::s_WndProc(HWND window, UINT message, WPARAM wparam, } return thisRef ? thisRef->WndProc(window, message, wparam, lparam) : - DefWindowProc(window, message, wparam, lparam); + DefWindowProc(window, message, wparam, lparam); } bool FancyZones::IsInterestingWindow(HWND window) noexcept @@ -616,8 +666,7 @@ bool FancyZones::IsInterestingWindow(HWND window) noexcept void FancyZones::UpdateZoneWindows() noexcept { - auto callback = [](HMONITOR monitor, HDC, RECT *, LPARAM data) -> BOOL - { + auto callback = [](HMONITOR monitor, HDC, RECT*, LPARAM data) -> BOOL { MONITORINFOEX mi; mi.cbSize = sizeof(mi); if (GetMonitorInfo(monitor, &mi)) @@ -643,8 +692,8 @@ void FancyZones::UpdateZoneWindows() noexcept if (!deviceId) { deviceId = GetSystemMetrics(SM_REMOTESESSION) ? - L"\\\\?\\DISPLAY#REMOTEDISPLAY#" : - L"\\\\?\\DISPLAY#LOCALDISPLAY#"; + L"\\\\?\\DISPLAY#REMOTEDISPLAY#" : + L"\\\\?\\DISPLAY#LOCALDISPLAY#"; } auto strongThis = reinterpret_cast(data); @@ -659,14 +708,13 @@ void FancyZones::UpdateZoneWindows() noexcept void FancyZones::MoveWindowsOnDisplayChange() noexcept { - auto callback = [](HWND window, LPARAM data) -> BOOL - { + auto callback = [](HWND window, LPARAM data) -> BOOL { int i = static_cast(reinterpret_cast(::GetProp(window, ZONE_STAMP))); if (i != 0) { // i is off by 1 since 0 is special. auto strongThis = reinterpret_cast(data); - strongThis->MoveWindowIntoZoneByIndex(window, i-1); + strongThis->MoveWindowIntoZoneByIndex(window, i - 1); } return TRUE; }; @@ -683,7 +731,7 @@ void FancyZones::UpdateDragState(require_write_lock) noexcept const bool mouseX2 = GetAsyncKeyState(VK_XBUTTON2) & 0x8000; // Note, Middle, X1 and X2 can also be used in addition to R/L - bool mouse = mouseM | mouseX1 | mouseX2; + bool mouse = mouseM | mouseX1 | mouseX2; // If the user has swapped their Right and Left Buttons, use the "Right" equivalent if (GetSystemMetrics(SM_SWAPBUTTON)) { @@ -801,7 +849,7 @@ void FancyZones::MoveSizeEndInternal(HWND window, POINT const& ptScreen, require auto processPath = get_process_path(window); if (!processPath.empty()) { - RegistryHelpers::SaveAppLastZone(window, processPath.data(), -1); + JSONHelpers::FancyZonesDataInstance().SetAppLastZone(window, processPath.data(), -1); } } } @@ -853,38 +901,47 @@ void FancyZones::HandleVirtualDesktopUpdates(HANDLE fancyZonesDestroyedEvent) no { HANDLE regKeyEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); HANDLE events[2] = { regKeyEvent, fancyZonesDestroyedEvent }; - while (1) { - if (RegNotifyChangeKeyValue(HKEY_CURRENT_USER, TRUE, REG_NOTIFY_CHANGE_LAST_SET, regKeyEvent, TRUE) != ERROR_SUCCESS) { + while (1) + { + if (RegNotifyChangeKeyValue(HKEY_CURRENT_USER, TRUE, REG_NOTIFY_CHANGE_LAST_SET, regKeyEvent, TRUE) != ERROR_SUCCESS) + { return; } - if (WaitForMultipleObjects(2, events, FALSE, INFINITE) != (WAIT_OBJECT_0 + 0)) { + if (WaitForMultipleObjects(2, events, FALSE, INFINITE) != (WAIT_OBJECT_0 + 0)) + { // if fancyZonesDestroyedEvent is signalized or WaitForMultipleObjects failed, terminate thread execution return; } DWORD bufferCapacity; const WCHAR* key = L"VirtualDesktopIDs"; // request regkey binary buffer capacity only - if (RegQueryValueExW(m_virtualDesktopsRegKey, key, 0, nullptr, nullptr, &bufferCapacity) != ERROR_SUCCESS) { + if (RegQueryValueExW(m_virtualDesktopsRegKey, key, 0, nullptr, nullptr, &bufferCapacity) != ERROR_SUCCESS) + { return; } std::unique_ptr buffer = std::make_unique(bufferCapacity); // request regkey binary content - if (RegQueryValueExW(m_virtualDesktopsRegKey, key, 0, nullptr, buffer.get(), &bufferCapacity) != ERROR_SUCCESS) { + if (RegQueryValueExW(m_virtualDesktopsRegKey, key, 0, nullptr, buffer.get(), &bufferCapacity) != ERROR_SUCCESS) + { return; } const int guidSize = sizeof(GUID); std::unordered_map temp; temp.reserve(bufferCapacity / guidSize); - for (size_t i = 0; i < bufferCapacity; i += guidSize) { - GUID *guid = reinterpret_cast(buffer.get() + i); + for (size_t i = 0; i < bufferCapacity; i += guidSize) + { + GUID* guid = reinterpret_cast(buffer.get() + i); temp[*guid] = true; } std::unique_lock writeLock(m_lock); - for (auto it = begin(m_virtualDesktopIds); it != end(m_virtualDesktopIds);) { - if (auto iter = temp.find(it->first); iter == temp.end()) { + for (auto it = begin(m_virtualDesktopIds); it != end(m_virtualDesktopIds);) + { + if (auto iter = temp.find(it->first); iter == temp.end()) + { it = m_virtualDesktopIds.erase(it); // virtual desktop closed, remove it from map } - else { + else + { temp.erase(it->first); // virtual desktop already in map, skip it ++it; } @@ -894,7 +951,29 @@ void FancyZones::HandleVirtualDesktopUpdates(HANDLE fancyZonesDestroyedEvent) no } } -winrt::com_ptr MakeFancyZones(HINSTANCE hinstance, IFancyZonesSettings* settings) noexcept +std::wstring GenerateUniqueId(HMONITOR monitor, PCWSTR deviceId, PCWSTR virtualDesktopId) noexcept { + wchar_t uniqueId[256]{}; // Parsed deviceId + resolution + virtualDesktopId + + MONITORINFOEXW mi; + mi.cbSize = sizeof(mi); + if (virtualDesktopId && GetMonitorInfo(monitor, &mi)) + { + wchar_t parsedId[256]{}; + ParseDeviceId(deviceId, parsedId, 256); + + Rect const monitorRect(mi.rcMonitor); + StringCchPrintf(uniqueId, ARRAYSIZE(uniqueId), L"%s_%d_%d_%s", parsedId, monitorRect.width(), monitorRect.height(), virtualDesktopId); + } + return std::wstring{ uniqueId }; +} + +winrt::com_ptr MakeFancyZones(HINSTANCE hinstance, const winrt::com_ptr& settings) noexcept +{ + if (!settings) + { + return nullptr; + } + return winrt::make_self(hinstance, settings); } \ No newline at end of file diff --git a/src/modules/fancyzones/lib/FancyZones.h b/src/modules/fancyzones/lib/FancyZones.h index 035b2e83a3e8..d9a6e039abc6 100644 --- a/src/modules/fancyzones/lib/FancyZones.h +++ b/src/modules/fancyzones/lib/FancyZones.h @@ -2,15 +2,7 @@ interface IZoneWindow; interface IFancyZonesSettings; - -enum class DisplayChangeType -{ - WorkArea, - DisplayChange, - VirtualDesktop, - Editor, - Initialization -}; +interface IZoneSet; interface __declspec(uuid("{50D3F0F5-736E-4186-BDF4-3D6BEE150C3A}")) IFancyZones : public IUnknown { @@ -35,8 +27,8 @@ interface __declspec(uuid("{5C8D99D6-34B2-4F4A-A8E5-7483F6869775}")) IZoneWindow { IFACEMETHOD_(void, MoveWindowsOnActiveZoneSetChange)() = 0; IFACEMETHOD_(COLORREF, GetZoneHighlightColor)() = 0; - IFACEMETHOD_(GUID, GetCurrentMonitorZoneSetId)(HMONITOR monitor) = 0; + IFACEMETHOD_(IZoneSet*, GetCurrentMonitorZoneSet) (HMONITOR monitor) = 0; IFACEMETHOD_(int, GetZoneHighlightOpacity)() = 0; }; -winrt::com_ptr MakeFancyZones(HINSTANCE hinstance, IFancyZonesSettings* settings) noexcept; +winrt::com_ptr MakeFancyZones(HINSTANCE hinstance, const winrt::com_ptr& settings) noexcept; diff --git a/src/modules/fancyzones/lib/FancyZonesLib.vcxproj b/src/modules/fancyzones/lib/FancyZonesLib.vcxproj index f52848cb01f0..6df22f7e2b00 100644 --- a/src/modules/fancyzones/lib/FancyZonesLib.vcxproj +++ b/src/modules/fancyzones/lib/FancyZonesLib.vcxproj @@ -92,6 +92,7 @@ + @@ -104,12 +105,14 @@ + Create Create + diff --git a/src/modules/fancyzones/lib/FancyZonesLib.vcxproj.filters b/src/modules/fancyzones/lib/FancyZonesLib.vcxproj.filters index 31731ca9593a..7478f922f890 100644 --- a/src/modules/fancyzones/lib/FancyZonesLib.vcxproj.filters +++ b/src/modules/fancyzones/lib/FancyZonesLib.vcxproj.filters @@ -45,6 +45,9 @@ Header Files + + Header Files + @@ -68,6 +71,12 @@ Source Files + + Source Files + + + Source Files + @@ -76,6 +85,5 @@ - \ No newline at end of file diff --git a/src/modules/fancyzones/lib/JsonHelpers.cpp b/src/modules/fancyzones/lib/JsonHelpers.cpp new file mode 100644 index 000000000000..7f98d34cbfc8 --- /dev/null +++ b/src/modules/fancyzones/lib/JsonHelpers.cpp @@ -0,0 +1,951 @@ +#include "pch.h" +#include "JsonHelpers.h" +#include "RegistryHelpers.h" +#include "ZoneSet.h" + +#include +#include +#include +#include + +namespace +{ + using TMonitors = std::vector; + + BOOL CALLBACK CollectMonitorsData(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) + { + TMonitors* monitors = reinterpret_cast(dwData); + monitors->push_back(hMonitor); + return true; + } + + // From Settings.cs + constexpr int c_focusModelId = 0xFFFF; + constexpr int c_rowsModelId = 0xFFFE; + constexpr int c_columnsModelId = 0xFFFD; + constexpr int c_gridModelId = 0xFFFC; + constexpr int c_priorityGridModelId = 0xFFFB; + constexpr int c_blankCustomModelId = 0xFFFA; + +} + +namespace JSONHelpers +{ + json::JsonArray NumVecToJsonArray(const std::vector& vec) + { + json::JsonArray arr; + for (const auto& val : vec) + { + arr.Append(json::JsonValue::CreateNumberValue(val)); + } + + return arr; + } + + std::vector JsonArrayToNumVec(const json::JsonArray& arr) + { + std::vector vec; + for (const auto& val : arr) + { + vec.emplace_back(static_cast(val.GetNumber())); + } + + return vec; + } + + ZoneSetLayoutType TypeFromLayoutId(int layoutID) + { + switch (layoutID) + { + case c_focusModelId: + return ZoneSetLayoutType::Focus; + case c_columnsModelId: + return ZoneSetLayoutType::Columns; + case c_rowsModelId: + return ZoneSetLayoutType::Rows; + case c_gridModelId: + return ZoneSetLayoutType::Grid; + case c_priorityGridModelId: + return ZoneSetLayoutType::PriorityGrid; + default: + return ZoneSetLayoutType::Custom; + } + } + + std::wstring TypeToString(ZoneSetLayoutType type) + { + switch (type) + { + case ZoneSetLayoutType::Focus: + return L"focus"; + case ZoneSetLayoutType::Columns: + return L"columns"; + case ZoneSetLayoutType::Rows: + return L"rows"; + case ZoneSetLayoutType::Grid: + return L"grid"; + case ZoneSetLayoutType::PriorityGrid: + return L"priority-grid"; + case ZoneSetLayoutType::Custom: + return L"custom"; + default: + return L"TypeToString_ERROR"; + } + } + + ZoneSetLayoutType TypeFromString(const std::wstring& typeStr) + { + if (typeStr.compare(L"focus") == 0) + { + return JSONHelpers::ZoneSetLayoutType::Focus; + } + else if (typeStr.compare(L"columns") == 0) + { + return JSONHelpers::ZoneSetLayoutType::Columns; + } + else if (typeStr.compare(L"rows") == 0) + { + return JSONHelpers::ZoneSetLayoutType::Rows; + } + else if (typeStr.compare(L"grid") == 0) + { + return JSONHelpers::ZoneSetLayoutType::Grid; + } + else if (typeStr.compare(L"priority-grid") == 0) + { + return JSONHelpers::ZoneSetLayoutType::PriorityGrid; + } + else + { //Custom + return JSONHelpers::ZoneSetLayoutType::Custom; + } + } + + FancyZonesData& FancyZonesDataInstance() + { + static FancyZonesData instance; + return instance; + } + + FancyZonesData::FancyZonesData() + { + std::wstring result = PTSettingsHelper::get_module_save_folder_location(L"FancyZones"); + jsonFilePath = result + L"\\" + std::wstring(FANCY_ZONES_DATA_FILE); + } + + const std::wstring& FancyZonesData::GetPersistFancyZonesJSONPath() const + { + return jsonFilePath; + } + + json::JsonObject FancyZonesData::GetPersistFancyZonesJSON() + { + std::wstring save_file_path = GetPersistFancyZonesJSONPath(); + + auto result = json::from_file(save_file_path); + if (result) + { + return *result; + } + else + { + return json::JsonObject(); + } + } + + int FancyZonesData::GetAppLastZone(HWND window, PCWSTR appPath) const + { + int iZoneIndex = -1; + + if (auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL)) + { + TAppPath path{ appPath }; + if (appZoneHistoryMap.contains(path)) + { + iZoneIndex = appZoneHistoryMap.at(path).zoneIndex; + } + } + return iZoneIndex; + } + + // Pass -1 for the zoneIndex to delete the entry from the map + bool FancyZonesData::SetAppLastZone(HWND window, PCWSTR appPath, DWORD zoneIndex) + { + if (auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL)) + { + if (zoneIndex == -1) + { + appZoneHistoryMap.erase(TAppPath{ appPath }); + } + else + { + //TODO(stefan) provide correct uuid in the future + appZoneHistoryMap[TAppPath{ appPath }] = AppZoneHistoryData{ L"", static_cast(zoneIndex) }; + } + return true; + } + return false; + } + + void FancyZonesData::SetActiveZoneSet(const TDeviceID& deviceId, const TZoneSetUUID& uuid) + { + if (!uuid.empty() && deviceInfoMap.find(deviceId) != deviceInfoMap.end()) + { + deviceInfoMap[deviceId].activeZoneSet.uuid = uuid; + } + } + + void FancyZonesData::SerializeDeviceInfoToTmpFile(const DeviceInfoJSON& deviceInfo, const std::wstring& tmpFilePath) const + { + json::JsonObject deviceInfoJson = DeviceInfoJSON::ToJson(deviceInfo); + json::to_file(tmpFilePath, deviceInfoJson); + } + + void FancyZonesData::ParseDeviceInfoFromTmpFile(const std::wstring& tmpFilePath) + { + if (std::filesystem::exists(tmpFilePath)) + { + auto zoneSetJson = json::from_file(tmpFilePath); + if (zoneSetJson.has_value()) + { + const auto deviceInfo = DeviceInfoJSON::FromJson(*zoneSetJson); + activeDeviceId = deviceInfo->deviceId; + if (deviceInfo.has_value()) + { + deviceInfoMap[activeDeviceId] = deviceInfo->data; + DeleteTmpFile(tmpFilePath); + } + } + } + else + { + activeDeviceId.clear(); + } + } + + bool FancyZonesData::ParseCustomZoneSetFromTmpFile(const std::wstring& tmpFilePath, const TZoneSetUUID& uuid) + { + bool res = true; + if (std::filesystem::exists(tmpFilePath)) + { + try + { + auto customZoneSetJson = json::from_file(tmpFilePath); + if (customZoneSetJson.has_value()) + { + const auto customZoneSet = CustomZoneSetJSON::FromJson(*customZoneSetJson); + if (customZoneSet.has_value()) + { + customZoneSetsMap[uuid] = customZoneSet->data; + } + } + } + catch (const winrt::hresult_error&) + { + res = false; + } + + DeleteTmpFile(tmpFilePath); + } + return res; + } + + bool FancyZonesData::ParseDeletedCustomZoneSetsFromTmpFile(const std::wstring& tmpFilePath) + { + bool res = true; + if (std::filesystem::exists(tmpFilePath)) + { + auto deletedZoneSetsJson = json::from_file(tmpFilePath); + try + { + auto deletedCustomZoneSets = deletedZoneSetsJson->GetNamedArray(L"deleted-custom-zone-sets"); + for (auto zoneSet : deletedCustomZoneSets) + { + std::wstring uuid = L"{" + std::wstring{ zoneSet.GetString() } + L"}"; + customZoneSetsMap.erase(std::wstring{ uuid }); + } + } + catch (const winrt::hresult_error&) + { + res = false; + } + + DeleteTmpFile(tmpFilePath); + } + + return res; + } + + bool FancyZonesData::ParseAppZoneHistory(const json::JsonObject& fancyZonesDataJSON) + { + try + { + auto appLastZones = fancyZonesDataJSON.GetNamedArray(L"app-zone-history"); + + for (uint32_t i = 0; i < appLastZones.Size(); ++i) + { + json::JsonObject appLastZone = appLastZones.GetObjectAt(i); + const auto appZoneHistory = AppZoneHistoryJSON::FromJson(appLastZone); + if (appZoneHistory.has_value()) + { + appZoneHistoryMap[appZoneHistory->appPath] = appZoneHistory->data; + } + else + { + return false; + } + } + + return true; + } + catch (const winrt::hresult_error&) + { + return false; + } + } + + json::JsonArray FancyZonesData::SerializeAppZoneHistory() const + { + json::JsonArray appHistoryArray; + + for (const auto& [appPath, appZoneHistoryData] : appZoneHistoryMap) + { + appHistoryArray.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ appPath, appZoneHistoryData })); + } + + return appHistoryArray; + } + + bool FancyZonesData::ParseDeviceInfos(const json::JsonObject& fancyZonesDataJSON) + { + try + { + auto devices = fancyZonesDataJSON.GetNamedArray(L"devices"); + + for (uint32_t i = 0; i < devices.Size(); ++i) + { + const auto device = DeviceInfoJSON::DeviceInfoJSON::FromJson(devices.GetObjectAt(i)); + if (device.has_value()) + { + deviceInfoMap[device->deviceId] = device->data; + } + else + { + return false; + } + } + + return true; + } + catch (const winrt::hresult_error&) + { + return false; + } + } + + json::JsonArray FancyZonesData::SerializeDeviceInfos() const + { + json::JsonArray DeviceInfosJSON{}; + + for (const auto& [deviceID, deviceData] : deviceInfoMap) + { + DeviceInfosJSON.Append(DeviceInfoJSON::DeviceInfoJSON::ToJson(DeviceInfoJSON{ deviceID, deviceData })); + } + + return DeviceInfosJSON; + } + + bool FancyZonesData::ParseCustomZoneSets(const json::JsonObject& fancyZonesDataJSON) + { + try + { + auto customZoneSets = fancyZonesDataJSON.GetNamedArray(L"custom-zone-sets"); + + for (uint32_t i = 0; i < customZoneSets.Size(); ++i) + { + const auto zoneSet = CustomZoneSetJSON::FromJson(customZoneSets.GetObjectAt(i)); + if (zoneSet.has_value()) + { + customZoneSetsMap[zoneSet->uuid] = zoneSet->data; + } + } + + return true; + } + catch (const winrt::hresult_error&) + { + return false; + } + } + + json::JsonArray FancyZonesData::SerializeCustomZoneSets() const + { + json::JsonArray customZoneSetsJSON{}; + + for (const auto& [zoneSetId, zoneSetData] : customZoneSetsMap) + { + customZoneSetsJSON.Append(CustomZoneSetJSON::ToJson(CustomZoneSetJSON{ zoneSetId, zoneSetData })); + } + + return customZoneSetsJSON; + } + + void FancyZonesData::CustomZoneSetsToJsonFile(const std::wstring& filePath) const + { + const auto& customZoneSetsJson = SerializeCustomZoneSets(); + json::JsonObject root{}; + root.SetNamedValue(L"custom-zone-sets", customZoneSetsJson); + json::to_file(filePath, root); + } + + void FancyZonesData::LoadFancyZonesData() + { + std::wstring jsonFilePath = GetPersistFancyZonesJSONPath(); + + if (!std::filesystem::exists(jsonFilePath)) + { + TmpMigrateAppliedZoneSetsFromRegistry(); + + // Custom zone sets have to be migrated before applied zone sets! + MigrateCustomZoneSetsFromRegistry(); + MigrateAppZoneHistoryFromRegistry(); + + SaveFancyZonesData(); + } + else + { + json::JsonObject fancyZonesDataJSON = GetPersistFancyZonesJSON(); + + ParseAppZoneHistory(fancyZonesDataJSON); + ParseDeviceInfos(fancyZonesDataJSON); + ParseCustomZoneSets(fancyZonesDataJSON); + } + } + + void FancyZonesData::SaveFancyZonesData() const + { + json::JsonObject root{}; + + root.SetNamedValue(L"app-zone-history", SerializeAppZoneHistory()); + root.SetNamedValue(L"devices", SerializeDeviceInfos()); + root.SetNamedValue(L"custom-zone-sets", SerializeCustomZoneSets()); + + json::to_file(jsonFilePath, root); + } + + void FancyZonesData::TmpMigrateAppliedZoneSetsFromRegistry() + { + std::wregex ex(L"^[0-9]{3,4}_[0-9]{3,4}$"); + + wchar_t key[256]; + StringCchPrintf(key, ARRAYSIZE(key), L"%s", RegistryHelpers::REG_SETTINGS); + HKEY hkey; + if (RegOpenKeyExW(HKEY_CURRENT_USER, key, 0, KEY_ALL_ACCESS, &hkey) == ERROR_SUCCESS) + { + wchar_t resolutionKey[256]{}; + DWORD resolutionKeyLength = ARRAYSIZE(resolutionKey); + DWORD i = 0; + while (RegEnumKeyW(hkey, i++, resolutionKey, resolutionKeyLength) == ERROR_SUCCESS) + { + std::wstring resolution{ resolutionKey }; + wchar_t appliedZoneSetskey[256]; + StringCchPrintf(appliedZoneSetskey, ARRAYSIZE(appliedZoneSetskey), L"%s\\%s", RegistryHelpers::REG_SETTINGS, resolutionKey); + HKEY appliedZoneSetsHkey; + if (std::regex_match(resolution, ex) && RegOpenKeyExW(HKEY_CURRENT_USER, appliedZoneSetskey, 0, KEY_ALL_ACCESS, &appliedZoneSetsHkey) == ERROR_SUCCESS) + { + ZoneSetPersistedDataOLD data; + DWORD dataSize = sizeof(data); + wchar_t value[256]{}; + DWORD valueLength = ARRAYSIZE(value); + DWORD i = 0; + + while (RegEnumValueW(appliedZoneSetsHkey, i++, value, &valueLength, nullptr, nullptr, reinterpret_cast(&data), &dataSize) == ERROR_SUCCESS) + { + ZoneSetData appliedZoneSetData; + appliedZoneSetData.type = TypeFromLayoutId(data.LayoutId); + if (appliedZoneSetData.type != ZoneSetLayoutType::Custom) + { + appliedZoneSetData.uuid = std::wstring{ value }; + appliedZoneSetData.zoneCount = data.ZoneCount; + } + else + { + // uuid is changed later to actual uuid when migrating custom zone sets + appliedZoneSetData.uuid = std::to_wstring(data.LayoutId); + } + appliedZoneSetsMap[value] = appliedZoneSetData; + dataSize = sizeof(data); + valueLength = ARRAYSIZE(value); + } + } + resolutionKeyLength = ARRAYSIZE(resolutionKey); + } + } + } + + void FancyZonesData::MigrateAppZoneHistoryFromRegistry() + { + TMonitors monitors; + EnumDisplayMonitors(NULL, NULL, &CollectMonitorsData, reinterpret_cast(&monitors)); + + for (HMONITOR monitor : monitors) + { + wchar_t key[256]; + StringCchPrintf(key, ARRAYSIZE(key), L"%s\\%s\\%x", RegistryHelpers::REG_SETTINGS, RegistryHelpers::APP_ZONE_HISTORY_SUBKEY, monitor); + HKEY hkey; + if (RegOpenKeyExW(HKEY_CURRENT_USER, key, 0, KEY_ALL_ACCESS, &hkey) == ERROR_SUCCESS) + { + DWORD zoneIndex; + DWORD dataSize = sizeof(DWORD); + wchar_t value[256]{}; + DWORD valueLength = ARRAYSIZE(value); + DWORD i = 0; + while (RegEnumValueW(hkey, i++, value, &valueLength, nullptr, nullptr, reinterpret_cast(&zoneIndex), &dataSize) == ERROR_SUCCESS) + { + appZoneHistoryMap[std::wstring{ value }] = AppZoneHistoryData{ L"", static_cast(zoneIndex) }; //TODO(stefan) provide correct uuid in the future + + valueLength = ARRAYSIZE(value); + dataSize = sizeof(zoneIndex); + } + } + } + } + + void FancyZonesData::MigrateDeviceInfoFromRegistry(const TDeviceID& deviceId) + { + wchar_t key[256]; + StringCchPrintf(key, ARRAYSIZE(key), L"%s\\%s", RegistryHelpers::REG_SETTINGS, deviceId.c_str()); + + wchar_t activeZoneSetId[256]; + activeZoneSetId[0] = '\0'; + DWORD bufferSize = sizeof(activeZoneSetId); + DWORD showSpacing = 1; + DWORD spacing = 16; + DWORD zoneCount = 3; + DWORD size = sizeof(DWORD); + + SHRegGetUSValueW(key, L"ActiveZoneSetId", nullptr, &activeZoneSetId, &bufferSize, FALSE, nullptr, 0); + SHRegGetUSValueW(key, L"ShowSpacing", nullptr, &showSpacing, &size, FALSE, nullptr, 0); + SHRegGetUSValueW(key, L"Spacing", nullptr, &spacing, &size, FALSE, nullptr, 0); + SHRegGetUSValueW(key, L"ZoneCount", nullptr, &zoneCount, &size, FALSE, nullptr, 0); + + if (appliedZoneSetsMap.contains(std::wstring{ activeZoneSetId })) + { + deviceInfoMap[deviceId] = DeviceInfoData{ appliedZoneSetsMap.at(std::wstring{ activeZoneSetId }), static_cast(showSpacing), static_cast(spacing), static_cast(zoneCount) }; + } + } + + void FancyZonesData::MigrateCustomZoneSetsFromRegistry() + { + wchar_t key[256]; + StringCchPrintf(key, ARRAYSIZE(key), L"%s\\%s", RegistryHelpers::REG_SETTINGS, L"Layouts"); + HKEY hkey; + if (RegOpenKeyExW(HKEY_CURRENT_USER, key, 0, KEY_ALL_ACCESS, &hkey) == ERROR_SUCCESS) + { + BYTE data[256]; + DWORD dataSize = ARRAYSIZE(data); + wchar_t value[256]{}; + DWORD valueLength = ARRAYSIZE(value); + DWORD i = 0; + while (RegEnumValueW(hkey, i++, value, &valueLength, nullptr, nullptr, reinterpret_cast(&data), &dataSize) == ERROR_SUCCESS) + { + CustomZoneSetData zoneSetData; + zoneSetData.name = std::wstring{ value }; + zoneSetData.type = static_cast(data[2]); + // int version = data[0] * 256 + data[1]; - Not used anymore + + std::wstring uuid = std::to_wstring(data[3] * 256 + data[4]); + auto it = std::find_if(appliedZoneSetsMap.begin(), appliedZoneSetsMap.end(), [&uuid](std::pair zoneSetMap) { + return zoneSetMap.second.uuid.compare(uuid) == 0; + }); + + if (it != appliedZoneSetsMap.end()) + { + it->second.uuid = uuid = it->first; + } + switch (zoneSetData.type) + { + case CustomLayoutType::Grid: + { + int j = 5; + GridLayoutInfo zoneSetInfo(GridLayoutInfo::Minimal{ .rows = data[j++], .columns = data[j++] }); + + for (int row = 0; row < zoneSetInfo.rows(); row++) + { + zoneSetInfo.rowsPercents()[row] = data[j++] * 256 + data[j++]; + } + + for (int col = 0; col < zoneSetInfo.columns(); col++) + { + zoneSetInfo.columnsPercents()[col] = data[j++] * 256 + data[j++]; + } + + for (int row = 0; row < zoneSetInfo.rows(); row++) + { + for (int col = 0; col < zoneSetInfo.columns(); col++) + { + zoneSetInfo.cellChildMap()[row][col] = data[j++]; + } + } + zoneSetData.info = zoneSetInfo; + break; + } + case CustomLayoutType::Canvas: + { + CanvasLayoutInfo info; + + int j = 5; + info.referenceWidth = data[j] * 256 + data[j + 1]; + j += 2; + info.referenceHeight = data[j] * 256 + data[j + 1]; + j += 2; + + int count = data[j++]; + info.zones.reserve(count); + while (count-- > 0) + { + int x = data[j] * 256 + data[j + 1]; + j += 2; + int y = data[j] * 256 + data[j + 1]; + j += 2; + int width = data[j] * 256 + data[j + 1]; + j += 2; + int height = data[j] * 256 + data[j + 1]; + j += 2; + info.zones.push_back(CanvasLayoutInfo::Rect{ + x, y, width, height }); + } + zoneSetData.info = info; + break; + } + default: + abort(); // TODO(stefan): Exception safety + } + customZoneSetsMap[uuid] = zoneSetData; + + valueLength = ARRAYSIZE(value); + dataSize = ARRAYSIZE(data); + } + } + } + + json::JsonObject ZoneSetData::ToJson(const ZoneSetData& zoneSet) + { + json::JsonObject result{}; + + result.SetNamedValue(L"uuid", json::value(zoneSet.uuid)); + result.SetNamedValue(L"type", json::value(TypeToString(zoneSet.type))); + if (zoneSet.type != ZoneSetLayoutType::Custom) + { + result.SetNamedValue(L"zone-count", json::value(*zoneSet.zoneCount)); + } + + return result; + } + + std::optional ZoneSetData::FromJson(const json::JsonObject& zoneSet) + { + try + { + ZoneSetData zoneSetData; + + zoneSetData.uuid = zoneSet.GetNamedString(L"uuid"); + zoneSetData.type = TypeFromString(std::wstring{ zoneSet.GetNamedString(L"type") }); + if (zoneSetData.type != ZoneSetLayoutType::Custom) + { + zoneSetData.zoneCount = static_cast(zoneSet.GetNamedNumber(L"zone-count")); + } + + return zoneSetData; + } + catch (const winrt::hresult_error&) + { + return std::nullopt; + } + } + + json::JsonObject AppZoneHistoryJSON::ToJson(const AppZoneHistoryJSON& appZoneHistory) + { + json::JsonObject result{}; + + result.SetNamedValue(L"app-path", json::value(appZoneHistory.appPath)); + result.SetNamedValue(L"zoneset-uuid", json::value(appZoneHistory.data.zoneSetUuid)); + result.SetNamedValue(L"zone-index", json::value(appZoneHistory.data.zoneIndex)); + + return result; + } + + std::optional AppZoneHistoryJSON::FromJson(const json::JsonObject& zoneSet) + { + try + { + AppZoneHistoryJSON result; + + result.appPath = zoneSet.GetNamedString(L"app-path"); + result.data.zoneSetUuid = zoneSet.GetNamedString(L"zoneset-uuid"); + result.data.zoneIndex = static_cast(zoneSet.GetNamedNumber(L"zone-index")); + + return result; + } + catch (const winrt::hresult_error&) + { + return std::nullopt; + } + } + + json::JsonObject DeviceInfoJSON::ToJson(const DeviceInfoJSON& device) + { + json::JsonObject result{}; + + result.SetNamedValue(L"device-id", json::value(device.deviceId)); + result.SetNamedValue(L"active-zoneset", ZoneSetData::ToJson(device.data.activeZoneSet)); + result.SetNamedValue(L"editor-show-spacing", json::value(device.data.showSpacing)); + result.SetNamedValue(L"editor-spacing", json::value(device.data.spacing)); + result.SetNamedValue(L"editor-zone-count", json::value(device.data.zoneCount)); + + return result; + } + + std::optional DeviceInfoJSON::FromJson(const json::JsonObject& device) + { + try + { + DeviceInfoJSON result; + + result.deviceId = device.GetNamedString(L"device-id"); + + const auto zoneSet = ZoneSetData::FromJson(device.GetNamedObject(L"active-zoneset")); + if (zoneSet.has_value()) + { + result.data.activeZoneSet = *zoneSet; + } + else + { + return std::nullopt; + } + + result.data.showSpacing = device.GetNamedBoolean(L"editor-show-spacing"); + result.data.spacing = static_cast(device.GetNamedNumber(L"editor-spacing")); + result.data.zoneCount = static_cast( + device.GetNamedNumber(L"editor-zone-count")); + + return result; + } + catch (const winrt::hresult_error&) + { + return std::nullopt; + } + } + + json::JsonObject CanvasLayoutInfo::ToJson(const CanvasLayoutInfo& canvasInfo) + { + json::JsonObject infoJson{}; + infoJson.SetNamedValue(L"ref-width", json::value(canvasInfo.referenceWidth)); + infoJson.SetNamedValue(L"ref-height", json::value(canvasInfo.referenceHeight)); + json::JsonArray zonesJson; + + for (const auto& [x, y, width, height] : canvasInfo.zones) + { + json::JsonObject zoneJson; + zoneJson.SetNamedValue(L"X", json::value(x)); + zoneJson.SetNamedValue(L"Y", json::value(y)); + zoneJson.SetNamedValue(L"width", json::value(width)); + zoneJson.SetNamedValue(L"height", json::value(height)); + zonesJson.Append(zoneJson); + } + infoJson.SetNamedValue(L"zones", zonesJson); + return infoJson; + } + + std::optional CanvasLayoutInfo::FromJson(const json::JsonObject& infoJson) + { + try + { + CanvasLayoutInfo info; + info.referenceWidth = static_cast(infoJson.GetNamedNumber(L"ref-width")); + info.referenceHeight = static_cast(infoJson.GetNamedNumber(L"ref-height")); + json::JsonArray zonesJson = infoJson.GetNamedArray(L"zones"); + for (uint32_t i = 0; i < zonesJson.Size(); ++i) + { + json::JsonObject zoneJson = zonesJson.GetObjectAt(i); + const int x = static_cast(zoneJson.GetNamedNumber(L"X")); + const int y = static_cast(zoneJson.GetNamedNumber(L"Y")); + const int width = static_cast(zoneJson.GetNamedNumber(L"width")); + const int height = static_cast(zoneJson.GetNamedNumber(L"height")); + CanvasLayoutInfo::Rect zone{ x, y, width, height }; + info.zones.push_back(zone); + } + return info; + } + catch (const winrt::hresult_error&) + { + return std::nullopt; + } + } + + GridLayoutInfo::GridLayoutInfo(const Minimal& info) : + m_rows(info.rows), + m_columns(info.columns) + { + m_rowsPercents.resize(m_rows, 0); + m_columnsPercents.resize(m_columns, 0); + m_cellChildMap.resize(m_rows, {}); + for (auto& cellRow : m_cellChildMap) + { + cellRow.resize(m_columns, 0); + } + } + + GridLayoutInfo::GridLayoutInfo(const Full& info) : + m_rows(info.rows), + m_columns(info.columns), + m_rowsPercents(info.rowsPercents), + m_columnsPercents(info.columnsPercents), + m_cellChildMap(info.cellChildMap) + { + m_rowsPercents.resize(m_rows, 0); + m_columnsPercents.resize(m_columns, 0); + m_cellChildMap.resize(m_rows, {}); + for (auto& cellRow : m_cellChildMap) + { + cellRow.resize(m_columns, 0); + } + } + + json::JsonObject GridLayoutInfo::ToJson(const GridLayoutInfo& gridInfo) + { + json::JsonObject infoJson; + infoJson.SetNamedValue(L"rows", json::value(gridInfo.m_rows)); + infoJson.SetNamedValue(L"columns", json::value(gridInfo.m_columns)); + infoJson.SetNamedValue(L"rows-percentage", NumVecToJsonArray(gridInfo.m_rowsPercents)); + infoJson.SetNamedValue(L"columns-percentage", NumVecToJsonArray(gridInfo.m_columnsPercents)); + + json::JsonArray cellChildMapJson; + for (int i = 0; i < gridInfo.m_cellChildMap.size(); ++i) + { + cellChildMapJson.Append(NumVecToJsonArray(gridInfo.m_cellChildMap[i])); + } + infoJson.SetNamedValue(L"cell-child-map", cellChildMapJson); + + return infoJson; + } + + std::optional GridLayoutInfo::FromJson(const json::JsonObject& infoJson) + { + try + { + GridLayoutInfo info(GridLayoutInfo::Minimal{}); + + info.m_rows = static_cast(infoJson.GetNamedNumber(L"rows")); + info.m_columns = static_cast(infoJson.GetNamedNumber(L"columns")); + + json::JsonArray rowsPercentage = infoJson.GetNamedArray(L"rows-percentage"); + json::JsonArray columnsPercentage = infoJson.GetNamedArray(L"columns-percentage"); + json::JsonArray cellChildMap = infoJson.GetNamedArray(L"cell-child-map"); + + if (rowsPercentage.Size() != info.m_rows || columnsPercentage.Size() != info.m_columns || cellChildMap.Size() != info.m_rows) + { + return std::nullopt; + } + + info.m_rowsPercents = JsonArrayToNumVec(rowsPercentage); + info.m_columnsPercents = JsonArrayToNumVec(columnsPercentage); + for (const auto& cellsRow : cellChildMap) + { + const auto cellsArray = cellsRow.GetArray(); + if (cellsArray.Size() != info.m_columns) + { + return std::nullopt; + } + info.cellChildMap().push_back(JsonArrayToNumVec(cellsArray)); + } + + return info; + } + catch (const winrt::hresult_error&) + { + return std::nullopt; + } + } + + json::JsonObject CustomZoneSetJSON::ToJson(const CustomZoneSetJSON& customZoneSet) + { + json::JsonObject result{}; + + result.SetNamedValue(L"uuid", json::value(customZoneSet.uuid)); + result.SetNamedValue(L"name", json::value(customZoneSet.data.name)); + switch (customZoneSet.data.type) + { + case CustomLayoutType::Canvas: + { + result.SetNamedValue(L"type", json::value(L"canvas")); + + CanvasLayoutInfo info = std::get(customZoneSet.data.info); + result.SetNamedValue(L"info", CanvasLayoutInfo::ToJson(info)); + + break; + } + case CustomLayoutType::Grid: + { + result.SetNamedValue(L"type", json::value(L"grid")); + + GridLayoutInfo gridInfo = std::get(customZoneSet.data.info); + result.SetNamedValue(L"info", GridLayoutInfo::ToJson(gridInfo)); + + break; + } + } + + return result; + } + + std::optional CustomZoneSetJSON::FromJson(const json::JsonObject& customZoneSet) + { + try + { + CustomZoneSetJSON result; + + result.uuid = customZoneSet.GetNamedString(L"uuid"); + result.data.name = customZoneSet.GetNamedString(L"name"); + + json::JsonObject infoJson = customZoneSet.GetNamedObject(L"info"); + std::wstring zoneSetType = std::wstring{ customZoneSet.GetNamedString(L"type") }; + if (zoneSetType.compare(L"canvas") == 0) + { + const auto info = CanvasLayoutInfo::FromJson(infoJson); + if (info.has_value()) + { + result.data.type = CustomLayoutType::Canvas; + result.data.info = *info; + } + else + { + return std::nullopt; + } + } + else if (zoneSetType.compare(L"grid") == 0) + { + const auto info = GridLayoutInfo::FromJson(infoJson); + if (info.has_value()) + { + result.data.type = CustomLayoutType::Grid; + result.data.info = *info; + } + else + { + return std::nullopt; + } + } + else + { + return std::nullopt; + } + + return result; + } + catch (const winrt::hresult_error&) + { + return std::nullopt; + } + } +} diff --git a/src/modules/fancyzones/lib/JsonHelpers.h b/src/modules/fancyzones/lib/JsonHelpers.h new file mode 100644 index 000000000000..916529e2ad29 --- /dev/null +++ b/src/modules/fancyzones/lib/JsonHelpers.h @@ -0,0 +1,249 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace JSONHelpers +{ + constexpr int MAX_ZONE_COUNT = 50; + + enum class ZoneSetLayoutType : int + { + Focus = 0, + Columns, + Rows, + Grid, + PriorityGrid, + Custom + }; + + enum class CustomLayoutType : int + { + Grid = 0, + Canvas + }; + + std::wstring TypeToString(ZoneSetLayoutType type); + ZoneSetLayoutType TypeFromString(const std::wstring& typeStr); + + ZoneSetLayoutType TypeFromLayoutId(int layoutID); + + using TZoneCount = int; + using TZoneSetUUID = std::wstring; + using TAppPath = std::wstring; + using TDeviceID = std::wstring; + + struct CanvasLayoutInfo + { + int referenceWidth; + int referenceHeight; + struct Rect + { + int x; + int y; + int width; + int height; + }; + std::vector zones; + + static json::JsonObject ToJson(const CanvasLayoutInfo& canvasInfo); + static std::optional FromJson(const json::JsonObject& infoJson); + }; + + class GridLayoutInfo + { + public: + struct Minimal + { + int rows; + int columns; + }; + + struct Full + { + int rows; + int columns; + const std::vector& rowsPercents; + const std::vector& columnsPercents; + const std::vector>& cellChildMap; + }; + + GridLayoutInfo(const Minimal& info); + GridLayoutInfo(const Full& info); + ~GridLayoutInfo() = default; + + static json::JsonObject ToJson(const GridLayoutInfo& gridInfo); + static std::optional FromJson(const json::JsonObject& infoJson); + + inline std::vector& rowsPercents() { return m_rowsPercents; }; + inline std::vector& columnsPercents() { return m_columnsPercents; }; + inline std::vector>& cellChildMap() { return m_cellChildMap; }; + + inline int rows() const { return m_rows; } + inline int columns() const { return m_columns; } + + inline const std::vector& rowsPercents() const { return m_rowsPercents; }; + inline const std::vector& columnsPercents() const { return m_columnsPercents; }; + inline const std::vector>& cellChildMap() const { return m_cellChildMap; }; + + private: + int m_rows; + int m_columns; + std::vector m_rowsPercents; + std::vector m_columnsPercents; + std::vector> m_cellChildMap; + }; + + struct CustomZoneSetData + { + std::wstring name; + CustomLayoutType type; + std::variant info; + }; + + struct CustomZoneSetJSON + { + TZoneSetUUID uuid; + CustomZoneSetData data; + + static json::JsonObject ToJson(const CustomZoneSetJSON& device); + static std::optional FromJson(const json::JsonObject& customZoneSet); + }; + + // TODO(stefan): This needs to be moved to ZoneSet.h (probably) + struct ZoneSetData + { + TZoneSetUUID uuid; + ZoneSetLayoutType type; + std::optional zoneCount; + + static json::JsonObject ToJson(const ZoneSetData& zoneSet); + static std::optional FromJson(const json::JsonObject& zoneSet); + }; + + struct AppZoneHistoryData + { + TZoneSetUUID zoneSetUuid; //TODO(stefan): is this nessecary? It doesn't exist with registry impl. + int zoneIndex; + //TODO(stefan): Also, do we need DeviceID here? Do we want to support that - app history per monitor? + }; + + struct AppZoneHistoryJSON + { + TAppPath appPath; + AppZoneHistoryData data; + + static json::JsonObject ToJson(const AppZoneHistoryJSON& appZoneHistory); + static std::optional FromJson(const json::JsonObject& zoneSet); + }; + + struct DeviceInfoData + { + ZoneSetData activeZoneSet; + bool showSpacing; + int spacing; + int zoneCount; + }; + + struct DeviceInfoJSON + { + TDeviceID deviceId; + DeviceInfoData data; + + static json::JsonObject ToJson(const DeviceInfoJSON& device); + static std::optional FromJson(const json::JsonObject& device); + }; + + using TDeviceInfosMap = std::unordered_map; + using TCustomZoneSetsMap = std::unordered_map; + using TAppliedZoneSetsMap = std::unordered_map; + using TAppZoneHistoryMap = std::unordered_map; + + static const std::wstring FANCY_ZONES_DATA_FILE = L"PersistFancyZones.json"; + + class FancyZonesData + { + public: + FancyZonesData(); + + const std::wstring& GetPersistFancyZonesJSONPath() const; + json::JsonObject GetPersistFancyZonesJSON(); + + TDeviceInfosMap& GetDeviceInfoMap() + { + return deviceInfoMap; + } + + const TCustomZoneSetsMap& GetCustomZoneSetsMap() + { + return customZoneSetsMap; + } + + const TAppZoneHistoryMap& GetAppZoneHistoryMap() + { + return appZoneHistoryMap; + } + + const TDeviceID GetActiveDeviceId() + { + return activeDeviceId; + } + + void SetActiveDeviceId(TDeviceID deviceId) + { + activeDeviceId = deviceId; + } + + inline bool DeleteTmpFile(const std::wstring& tmpFilePath) + { + return DeleteFileW(tmpFilePath.c_str()); + } + + int GetAppLastZone(HWND window, PCWSTR appPath) const; + bool SetAppLastZone(HWND window, PCWSTR appPath, DWORD zoneIndex); //TODO(stefan): Missing zone uuid (pass as arg) + + void SetActiveZoneSet(const TDeviceID& deviceId, const TZoneSetUUID& uuid); + + void SerializeDeviceInfoToTmpFile(const DeviceInfoJSON& deviceInfo, const std::wstring& tmpFilePath) const; + void ParseDeviceInfoFromTmpFile(const std::wstring& tmpFilePath); + + bool ParseCustomZoneSetFromTmpFile(const std::wstring& tmpFilePath, const TZoneSetUUID& uuid); + bool ParseDeletedCustomZoneSetsFromTmpFile(const std::wstring& tmpFilePath); + + bool ParseAppZoneHistory(const json::JsonObject& fancyZonesDataJSON); + json::JsonArray SerializeAppZoneHistory() const; + bool ParseDeviceInfos(const json::JsonObject& fancyZonesDataJSON); + json::JsonArray SerializeDeviceInfos() const; + bool ParseCustomZoneSets(const json::JsonObject& fancyZonesDataJSON); + json::JsonArray SerializeCustomZoneSets() const; + void CustomZoneSetsToJsonFile(const std::wstring& filePath) const; + + void LoadFancyZonesData(); + void SaveFancyZonesData() const; + + void MigrateDeviceInfoFromRegistry(const TDeviceID& deviceId); + + private: + void TmpMigrateAppliedZoneSetsFromRegistry(); + void MigrateAppZoneHistoryFromRegistry(); //TODO(stefan): If uuid is needed here, it needs to be resolved here some how + void MigrateCustomZoneSetsFromRegistry(); + + TAppliedZoneSetsMap appliedZoneSetsMap{}; + TAppZoneHistoryMap appZoneHistoryMap{}; + TDeviceInfosMap deviceInfoMap{}; + TCustomZoneSetsMap customZoneSetsMap{}; + + TDeviceID activeDeviceId; + std::wstring jsonFilePath; + }; + + FancyZonesData& FancyZonesDataInstance(); +} diff --git a/src/modules/fancyzones/lib/RegistryHelpers.h b/src/modules/fancyzones/lib/RegistryHelpers.h index 627254a5c1cb..e16213e4a6e2 100644 --- a/src/modules/fancyzones/lib/RegistryHelpers.h +++ b/src/modules/fancyzones/lib/RegistryHelpers.h @@ -7,132 +7,6 @@ namespace RegistryHelpers static PCWSTR REG_SETTINGS = L"Software\\SuperFancyZones"; static PCWSTR APP_ZONE_HISTORY_SUBKEY = L"AppZoneHistory"; - inline PCWSTR GetKey(_In_opt_ PCWSTR monitorId, PWSTR key, size_t keyLength) - { - if (monitorId) - { - StringCchPrintf(key, keyLength, L"%s\\%s", REG_SETTINGS, monitorId); - } - else - { - StringCchPrintf(key, keyLength, L"%s", REG_SETTINGS); - } - return key; - } - - inline HKEY OpenKey(_In_opt_ PCWSTR monitorId) - { - HKEY hkey; - wchar_t key[256]; - GetKey(monitorId, key, ARRAYSIZE(key)); - if (RegOpenKeyExW(HKEY_CURRENT_USER, key, 0, KEY_ALL_ACCESS, &hkey) == ERROR_SUCCESS) - { - return hkey; - } - return nullptr; - } - - inline HKEY CreateKey(PCWSTR monitorId) - { - HKEY hkey; - wchar_t key[256]{}; - GetKey(monitorId, key, ARRAYSIZE(key)); - if (RegCreateKeyExW(HKEY_CURRENT_USER, key, 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nullptr, &hkey, nullptr) == ERROR_SUCCESS) - { - return hkey; - } - return nullptr; - } - - inline LSTATUS GetAppLastZone(HWND window, PCWSTR appPath, _Out_ PINT iZoneIndex) - { - *iZoneIndex = -1; - - LSTATUS res{}; - if (auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL)) - { - wchar_t keyPath[256]{}; - StringCchPrintf(keyPath, ARRAYSIZE(keyPath), L"%s\\%s\\%x", REG_SETTINGS, APP_ZONE_HISTORY_SUBKEY, monitor); - - DWORD zoneIndex; - DWORD dataType = REG_DWORD; - DWORD dataSize = sizeof(DWORD); - res = SHRegGetUSValueW(keyPath, appPath, &dataType, &zoneIndex, &dataSize, FALSE, nullptr, 0); - if (res == ERROR_SUCCESS) - { - *iZoneIndex = static_cast(zoneIndex); - } - } - return res; - } - - // Pass -1 for the zoneIndex to delete the entry from the registry - inline void SaveAppLastZone(HWND window, PCWSTR appPath, DWORD zoneIndex) - { - LSTATUS res{}; - if (auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL)) - { - wchar_t keyPath[256]{}; - StringCchPrintf(keyPath, ARRAYSIZE(keyPath), L"%s\\%s\\%x", REG_SETTINGS, APP_ZONE_HISTORY_SUBKEY, monitor); - if (zoneIndex == -1) - { - SHDeleteValueW(HKEY_CURRENT_USER, keyPath, appPath); - } - else - { - SHRegSetUSValueW(keyPath, appPath, REG_DWORD, &zoneIndex, sizeof(zoneIndex), SHREGSET_FORCE_HKCU); - } - } - } - - inline void GetString(PCWSTR uniqueId, PCWSTR setting, PWSTR value, DWORD cbValue) - { - wchar_t key[256]{}; - GetKey(uniqueId, key, ARRAYSIZE(key)); - SHRegGetUSValueW(key, setting, nullptr, value, &cbValue, FALSE, nullptr, 0); - } - - inline void SetString(PCWSTR uniqueId, PCWSTR setting, PCWSTR value) - { - wchar_t key[256]{}; - GetKey(uniqueId, key, ARRAYSIZE(key)); - SHRegSetUSValueW(key, setting, REG_SZ, value, sizeof(value) * static_cast(wcslen(value)), SHREGSET_FORCE_HKCU); - } - - template - inline void GetValue(PCWSTR monitorId, PCWSTR setting, t* value, DWORD size) - { - wchar_t key[256]{}; - GetKey(monitorId, key, ARRAYSIZE(key)); - SHRegGetUSValueW(key, setting, nullptr, value, &size, FALSE, nullptr, 0); - } - - template - inline void SetValue(PCWSTR monitorId, PCWSTR setting, t value, DWORD size) - { - wchar_t key[256]{}; - GetKey(monitorId, key, ARRAYSIZE(key)); - SHRegSetUSValueW(key, setting, REG_BINARY, &value, size, SHREGSET_FORCE_HKCU); - } - - inline void DeleteZoneSet(PCWSTR monitorId, GUID guid) - { - wil::unique_cotaskmem_string zoneSetId; - if (SUCCEEDED_LOG(StringFromCLSID(guid, &zoneSetId))) - { - wchar_t key[256]{}; - GetKey(monitorId, key, ARRAYSIZE(key)); - SHDeleteValueW(HKEY_CURRENT_USER, key, zoneSetId.get()); - } - } - - inline void DeleteAllZoneSets(PCWSTR monitorId) - { - wchar_t key[256]{}; - GetKey(monitorId, key, ARRAYSIZE(key)); - SHDeleteKey(HKEY_CURRENT_USER, key); - } - inline HRESULT GetCurrentVirtualDesktop(_Out_ GUID* id) { *id = GUID_NULL; diff --git a/src/modules/fancyzones/lib/Settings.cpp b/src/modules/fancyzones/lib/Settings.cpp index 09ded242de12..dccec1363df0 100644 --- a/src/modules/fancyzones/lib/Settings.cpp +++ b/src/modules/fancyzones/lib/Settings.cpp @@ -9,11 +9,12 @@ struct FancyZonesSettings : winrt::implements MakeFancyZonesSettings(HINSTANCE hinstance, PCWSTR name) noexcept { - return winrt::make_self(hinstance, name); + auto self = winrt::make_self(hinstance, name); + if (self->Init(name, true /*fromFile*/)) + { + return self; + } + + return nullptr; } \ No newline at end of file diff --git a/src/modules/fancyzones/lib/Settings.h b/src/modules/fancyzones/lib/Settings.h index 492e07411011..f57f54155924 100644 --- a/src/modules/fancyzones/lib/Settings.h +++ b/src/modules/fancyzones/lib/Settings.h @@ -25,7 +25,7 @@ interface __declspec(uuid("{BA4E77C4-6F44-4C5D-93D3-CBDE880495C2}")) IFancyZones { IFACEMETHOD_(void, SetCallback)(interface IFancyZonesCallback* callback) = 0; IFACEMETHOD_(bool, GetConfig)(_Out_ PWSTR buffer, _Out_ int *buffer_size) = 0; - IFACEMETHOD_(void, SetConfig)(PCWSTR config) = 0; + IFACEMETHOD_(void, SetConfig)(PCWSTR serializedPowerToysSettingsJson) = 0; IFACEMETHOD_(void, CallCustomAction)(PCWSTR action) = 0; IFACEMETHOD_(Settings, GetSettings)() = 0; }; diff --git a/src/modules/fancyzones/lib/ZoneSet.cpp b/src/modules/fancyzones/lib/ZoneSet.cpp index 6b4b90ec597b..ff42b024f3fe 100644 --- a/src/modules/fancyzones/lib/ZoneSet.cpp +++ b/src/modules/fancyzones/lib/ZoneSet.cpp @@ -1,12 +1,113 @@ #include "pch.h" +#include "util.h" #include "lib/ZoneSet.h" #include "lib/RegistryHelpers.h" +#include + +namespace +{ + constexpr int C_MULTIPLIER = 10000; + + /* + struct GridLayoutInfo { + int rows; + int columns; + int rowsPercents[MAX_ZONE_COUNT]; + int columnsPercents[MAX_ZONE_COUNT]; + int cellChildMap[MAX_ZONE_COUNT][MAX_ZONE_COUNT]; + }; + */ + + auto l = JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Minimal{ .rows = 1, .columns = 1 }); + // PriorityGrid layout is unique for zoneCount <= 11. For zoneCount > 11 PriorityGrid is same as Grid + JSONHelpers::GridLayoutInfo predefinedPriorityGridLayouts[11] = { + /* 1 */ + JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{ + .rows = 1, + .columns = 1, + .rowsPercents = { 10000 }, + .columnsPercents = { 10000 }, + .cellChildMap = { { 0 } } }), + /* 2 */ + JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{ + .rows = 1, + .columns = 2, + .rowsPercents = { 10000 }, + .columnsPercents = { 6667, 3333 }, + .cellChildMap = { { 0, 1 } } }), + /* 3 */ + JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{ + .rows = 1, + .columns = 3, + .rowsPercents = { 10000 }, + .columnsPercents = { 2500, 5000, 2500 }, + .cellChildMap = { { 0, 1, 2 } } }), + /* 4 */ + JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{ + .rows = 2, + .columns = 3, + .rowsPercents = { 5000, 5000 }, + .columnsPercents = { 2500, 5000, 2500 }, + .cellChildMap = { { 0, 1, 2 }, { 0, 1, 3 } } }), + /* 5 */ + JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{ + .rows = 2, + .columns = 3, + .rowsPercents = { 5000, 5000 }, + .columnsPercents = { 2500, 5000, 2500 }, + .cellChildMap = { { 0, 1, 2 }, { 3, 1, 4 } } }), + /* 6 */ + JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{ + .rows = 3, + .columns = 3, + .rowsPercents = { 3333, 3334, 3333 }, + .columnsPercents = { 2500, 5000, 2500 }, + .cellChildMap = { { 0, 1, 2 }, { 0, 1, 3 }, { 4, 1, 5 } } }), + /* 7 */ + JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{ + .rows = 3, + .columns = 3, + .rowsPercents = { 3333, 3334, 3333 }, + .columnsPercents = { 2500, 5000, 2500 }, + .cellChildMap = { { 0, 1, 2 }, { 3, 1, 4 }, { 5, 1, 6 } } }), + /* 8 */ + JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{ + .rows = 3, + .columns = 4, + .rowsPercents = { 3333, 3334, 3333 }, + .columnsPercents = { 2500, 2500, 2500, 2500 }, + .cellChildMap = { { 0, 1, 2, 3 }, { 4, 1, 2, 5 }, { 6, 1, 2, 7 } } }), + /* 9 */ + JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{ + .rows = 3, + .columns = 4, + .rowsPercents = { 3333, 3334, 3333 }, + .columnsPercents = { 2500, 2500, 2500, 2500 }, + .cellChildMap = { { 0, 1, 2, 3 }, { 4, 1, 2, 5 }, { 6, 1, 7, 8 } } }), + /* 10 */ + JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{ + .rows = 3, + .columns = 4, + .rowsPercents = { 3333, 3334, 3333 }, + .columnsPercents = { 2500, 2500, 2500, 2500 }, + .cellChildMap = { { 0, 1, 2, 3 }, { 4, 1, 5, 6 }, { 7, 1, 8, 9 } } }), + /* 11 */ + JSONHelpers::GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{ + .rows = 3, + .columns = 4, + .rowsPercents = { 3333, 3334, 3333 }, + .columnsPercents = { 2500, 2500, 2500, 2500 }, + .cellChildMap = { { 0, 1, 2, 3 }, { 4, 1, 5, 6 }, { 7, 8, 9, 10 } } }), + }; +} + struct ZoneSet : winrt::implements { public: - ZoneSet(ZoneSetConfig const& config) : m_config(config) + ZoneSet(ZoneSetConfig const& config) : + m_config(config) { } @@ -16,18 +117,35 @@ struct ZoneSet : winrt::implements { } - IFACEMETHODIMP_(GUID) Id() noexcept { return m_config.Id; } - IFACEMETHODIMP_(WORD) LayoutId() noexcept { return m_config.LayoutId; } + IFACEMETHODIMP_(GUID) + Id() noexcept { return m_config.Id; } + IFACEMETHODIMP_(JSONHelpers::ZoneSetLayoutType) + LayoutType() noexcept { return m_config.LayoutType; } IFACEMETHODIMP AddZone(winrt::com_ptr zone) noexcept; - IFACEMETHODIMP_(winrt::com_ptr) ZoneFromPoint(POINT pt) noexcept; - IFACEMETHODIMP_(int) GetZoneIndexFromWindow(HWND window) noexcept; - IFACEMETHODIMP_(std::vector>) GetZones() noexcept { return m_zones; } - IFACEMETHODIMP_(void) Save() noexcept; - IFACEMETHODIMP_(void) MoveWindowIntoZoneByIndex(HWND window, HWND zoneWindow, int index) noexcept; - IFACEMETHODIMP_(void) MoveWindowIntoZoneByDirection(HWND window, HWND zoneWindow, DWORD vkCode) noexcept; - IFACEMETHODIMP_(void) MoveSizeEnd(HWND window, HWND zoneWindow, POINT ptClient) noexcept; + IFACEMETHODIMP_(winrt::com_ptr) + ZoneFromPoint(POINT pt) noexcept; + IFACEMETHODIMP_(int) + GetZoneIndexFromWindow(HWND window) noexcept; + IFACEMETHODIMP_(std::vector>) + GetZones() noexcept { return m_zones; } + IFACEMETHODIMP_(void) + MoveWindowIntoZoneByIndex(HWND window, HWND zoneWindow, int index) noexcept; + IFACEMETHODIMP_(void) + MoveWindowIntoZoneByDirection(HWND window, HWND zoneWindow, DWORD vkCode) noexcept; + IFACEMETHODIMP_(void) + MoveWindowIntoZoneByPoint(HWND window, HWND zoneWindow, POINT ptClient) noexcept; + IFACEMETHODIMP_(bool) + CalculateZones(MONITORINFO monitorInfo, int zoneCount, int spacing, const std::wstring& customZoneSetFilePath) noexcept; private: + bool CalculateFocusLayout(Rect workArea, int zoneCount) noexcept; + bool CalculateColumnsAndRowsLayout(Rect workArea, JSONHelpers::ZoneSetLayoutType type, int zoneCount, int spacing) noexcept; + bool CalculateGridLayout(Rect workArea, JSONHelpers::ZoneSetLayoutType type, int zoneCount, int spacing) noexcept; + bool CalculateUniquePriorityGridLayout(Rect workArea, int zoneCount, int spacing) noexcept; + bool CalculateCustomLayout(Rect workArea, const std::wstring& customZoneSetFilePath, int spacing) noexcept; + + bool CalculateGridZones(Rect workArea, JSONHelpers::GridLayoutInfo gridLayoutInfo, int spacing); + winrt::com_ptr ZoneFromWindow(HWND window) noexcept; std::vector> m_zones; @@ -44,7 +162,8 @@ IFACEMETHODIMP ZoneSet::AddZone(winrt::com_ptr zone) noexcept return S_OK; } -IFACEMETHODIMP_(winrt::com_ptr) ZoneSet::ZoneFromPoint(POINT pt) noexcept +IFACEMETHODIMP_(winrt::com_ptr) +ZoneSet::ZoneFromPoint(POINT pt) noexcept { winrt::com_ptr smallestKnownZone = nullptr; // To reduce redundant calculations, we will store the last known zones area. @@ -61,16 +180,16 @@ IFACEMETHODIMP_(winrt::com_ptr) ZoneSet::ZoneFromPoint(POINT pt) noexcept smallestKnownZone = zone; RECT* r = &smallestKnownZone->GetZoneRect(); - smallestKnownZoneArea = (r->right-r->left)*(r->bottom-r->top); + smallestKnownZoneArea = (r->right - r->left) * (r->bottom - r->top); } else { - int newZoneArea = (newZoneRect->right-newZoneRect->left)*(newZoneRect->bottom-newZoneRect->top); + int newZoneArea = (newZoneRect->right - newZoneRect->left) * (newZoneRect->bottom - newZoneRect->top); - if (newZoneArea) ZoneSet::ZoneFromPoint(POINT pt) noexcept return smallestKnownZone; } -IFACEMETHODIMP_(void) ZoneSet::Save() noexcept -{ - size_t const zoneCount = m_zones.size(); - if (zoneCount == 0) - { - RegistryHelpers::DeleteZoneSet(m_config.ResolutionKey, m_config.Id); - } - else - { - ZoneSetPersistedData data{}; - data.LayoutId = m_config.LayoutId; - data.ZoneCount = static_cast(zoneCount); - - int i = 0; - for (auto iter = m_zones.begin(); iter != m_zones.end(); iter++) - { - winrt::com_ptr zone = iter->as(); - CopyRect(&data.Zones[i++], &zone->GetZoneRect()); - } - - wil::unique_cotaskmem_string guid; - if (SUCCEEDED_LOG(StringFromCLSID(m_config.Id, &guid))) - { - if (wil::unique_hkey hkey{ RegistryHelpers::CreateKey(m_config.ResolutionKey) }) - { - RegSetValueExW(hkey.get(), guid.get(), 0, REG_BINARY, reinterpret_cast(&data), sizeof(data)); - } - } - } -} - -IFACEMETHODIMP_(int) ZoneSet::GetZoneIndexFromWindow(HWND window) noexcept +IFACEMETHODIMP_(int) +ZoneSet::GetZoneIndexFromWindow(HWND window) noexcept { int zoneIndex = 0; for (auto iter = m_zones.begin(); iter != m_zones.end(); iter++, zoneIndex++) @@ -127,26 +216,38 @@ IFACEMETHODIMP_(int) ZoneSet::GetZoneIndexFromWindow(HWND window) noexcept return -1; } -IFACEMETHODIMP_(void) ZoneSet::MoveWindowIntoZoneByIndex(HWND window, HWND windowZone, int index) noexcept +IFACEMETHODIMP_(void) +ZoneSet::MoveWindowIntoZoneByIndex(HWND window, HWND windowZone, int index) noexcept { + if (m_zones.empty()) + { + return; + } + if (index >= static_cast(m_zones.size())) { index = 0; } - if (index < m_zones.size()) + while (auto zoneDrop = ZoneFromWindow(window)) { - if (auto zone = m_zones.at(index)) - { - zone->AddWindowToZone(window, windowZone, false); - } + zoneDrop->RemoveWindowFromZone(window, !IsZoomed(window)); + } + + if (auto zone = m_zones.at(index)) + { + zone->AddWindowToZone(window, windowZone, false); } } -IFACEMETHODIMP_(void) ZoneSet::MoveWindowIntoZoneByDirection(HWND window, HWND windowZone, DWORD vkCode) noexcept +IFACEMETHODIMP_(void) +ZoneSet::MoveWindowIntoZoneByDirection(HWND window, HWND windowZone, DWORD vkCode) noexcept { - winrt::com_ptr oldZone; - winrt::com_ptr newZone; + if (m_zones.empty()) + return; + + winrt::com_ptr oldZone = nullptr; + winrt::com_ptr newZone = nullptr; auto iter = std::find(m_zones.begin(), m_zones.end(), ZoneFromWindow(window)); if (iter == m_zones.end()) @@ -183,9 +284,10 @@ IFACEMETHODIMP_(void) ZoneSet::MoveWindowIntoZoneByDirection(HWND window, HWND w } } -IFACEMETHODIMP_(void) ZoneSet::MoveSizeEnd(HWND window, HWND zoneWindow, POINT ptClient) noexcept +IFACEMETHODIMP_(void) +ZoneSet::MoveWindowIntoZoneByPoint(HWND window, HWND zoneWindow, POINT ptClient) noexcept { - if (auto zoneDrop = ZoneFromWindow(window)) + while (auto zoneDrop = ZoneFromWindow(window)) { zoneDrop->RemoveWindowFromZone(window, !IsZoomed(window)); } @@ -196,6 +298,319 @@ IFACEMETHODIMP_(void) ZoneSet::MoveSizeEnd(HWND window, HWND zoneWindow, POINT p } } +IFACEMETHODIMP_(bool) +ZoneSet::CalculateZones(MONITORINFO monitorInfo, int zoneCount, int spacing, const std::wstring& customZoneSetFilePath) noexcept +{ + Rect const workArea(monitorInfo.rcWork); + //invalid work area + if (workArea.width() == 0 || workArea.height() == 0) + { + return false; + } + + //invalid zoneCount, may cause division by zero + if (zoneCount <= 0 && m_config.LayoutType != JSONHelpers::ZoneSetLayoutType::Custom) + { + return false; + } + + bool success = true; + switch (m_config.LayoutType) + { + case JSONHelpers::ZoneSetLayoutType::Focus: + success = CalculateFocusLayout(workArea, zoneCount); + break; + case JSONHelpers::ZoneSetLayoutType::Columns: + case JSONHelpers::ZoneSetLayoutType::Rows: + success = CalculateColumnsAndRowsLayout(workArea, m_config.LayoutType, zoneCount, spacing); + break; + case JSONHelpers::ZoneSetLayoutType::Grid: + case JSONHelpers::ZoneSetLayoutType::PriorityGrid: + success = CalculateGridLayout(workArea, m_config.LayoutType, zoneCount, spacing); + break; + case JSONHelpers::ZoneSetLayoutType::Custom: + success = CalculateCustomLayout(workArea, customZoneSetFilePath, spacing); + break; + } + + return success; +} + +bool ZoneSet::CalculateFocusLayout(Rect workArea, int zoneCount) noexcept +{ + bool success = true; + + LONG left{ static_cast(workArea.width() * 0.1) }; + LONG top{ static_cast(workArea.height() * 0.1) }; + LONG right{ static_cast(workArea.width() * 0.6) }; + LONG bottom{ static_cast(workArea.height() * 0.6) }; + RECT focusZoneRect{ left, top, right, bottom }; + int focusRectXIncrement = (zoneCount <= 1) ? 0 : (int)(workArea.width() * 0.2) / (zoneCount - 1); + int focusRectYIncrement = (zoneCount <= 1) ? 0 : (int)(workArea.height() * 0.2) / (zoneCount - 1); + + if (left >= right || top >= bottom || left < 0 || right < 0 || top < 0 || bottom < 0) + { + success = false; + } + + for (int i = 0; i < zoneCount; i++) + { + AddZone(MakeZone(focusZoneRect)); + focusZoneRect.left += focusRectXIncrement; + focusZoneRect.right += focusRectXIncrement; + focusZoneRect.bottom += focusRectYIncrement; + focusZoneRect.top += focusRectYIncrement; + } + + return success; +} + +bool ZoneSet::CalculateColumnsAndRowsLayout(Rect workArea, JSONHelpers::ZoneSetLayoutType type, int zoneCount, int spacing) noexcept +{ + bool success = true; + + int gutter = spacing; + + int zonePercent = C_MULTIPLIER / zoneCount; + + LONG totalWidth; + LONG totalHeight; + + LONG cellWidth; + LONG cellHeight; + + if (type == JSONHelpers::ZoneSetLayoutType::Columns) + { + totalWidth = workArea.width() - (gutter * 2) - (spacing * (zoneCount - 1)); + totalHeight = workArea.height() - (gutter * 2); + cellWidth = totalWidth * zonePercent / C_MULTIPLIER; + cellHeight = totalHeight; + } + else + { //Rows + totalWidth = workArea.width() - (gutter * 2); + totalHeight = workArea.height() - (gutter * 2) - (spacing * (zoneCount - 1)); + cellWidth = totalWidth; + cellHeight = totalHeight * zonePercent / C_MULTIPLIER; + } + + LONG top = spacing; + LONG left = spacing; + LONG bottom = top + cellHeight; + LONG right = left + cellWidth; + + for (int zone = 0; zone < zoneCount; zone++) + { + if (left >= right || top >= bottom || left < 0 || right < 0 || top < 0 || bottom < 0) + { + success = false; + } + + RECT focusZoneRect{ left, top, right, bottom }; + AddZone(MakeZone(focusZoneRect)); + + if (type == JSONHelpers::ZoneSetLayoutType::Columns) + { + left += cellWidth + spacing; + right = left + cellWidth; + } + else + { //Rows + top += cellHeight + spacing; + bottom = top + cellHeight; + } + } + + return success; +} + +bool ZoneSet::CalculateGridLayout(Rect workArea, JSONHelpers::ZoneSetLayoutType type, int zoneCount, int spacing) noexcept +{ + const auto count = sizeof(predefinedPriorityGridLayouts) / sizeof(JSONHelpers::GridLayoutInfo); + if (type == JSONHelpers::ZoneSetLayoutType::PriorityGrid && zoneCount < count) + { + return CalculateUniquePriorityGridLayout(workArea, zoneCount, spacing); + } + + int rows = 1, columns = 1; + while (zoneCount / rows >= rows) + { + rows++; + } + rows--; + columns = zoneCount / rows; + if (zoneCount % rows == 0) + { + // even grid + } + else + { + columns++; + } + + JSONHelpers::GridLayoutInfo gridLayoutInfo(JSONHelpers::GridLayoutInfo::Minimal{ .rows = rows, .columns = columns }); + + for (int row = 0; row < rows; row++) + { + gridLayoutInfo.rowsPercents()[row] = C_MULTIPLIER / rows; + } + for (int col = 0; col < columns; col++) + { + gridLayoutInfo.columnsPercents()[col] = C_MULTIPLIER / columns; + } + + for (int i = 0; i < rows; ++i) + { + gridLayoutInfo.cellChildMap()[i] = std::vector(columns); + } + + int index = 0; + for (int col = columns - 1; col >= 0; col--) + { + for (int row = rows - 1; row >= 0; row--) + { + gridLayoutInfo.cellChildMap()[row][col] = index++; + if (index == zoneCount) + { + index--; + } + } + } + return CalculateGridZones(workArea, gridLayoutInfo, spacing); +} + +bool ZoneSet::CalculateUniquePriorityGridLayout(Rect workArea, int zoneCount, int spacing) noexcept +{ + if (zoneCount <= 0 || zoneCount >= sizeof(predefinedPriorityGridLayouts)) + { + return false; + } + + return CalculateGridZones(workArea, predefinedPriorityGridLayouts[zoneCount - 1], spacing); +} + +bool ZoneSet::CalculateCustomLayout(Rect workArea, const std::wstring& customZoneSetFilePath, int spacing) noexcept +{ + wil::unique_cotaskmem_string guuidStr; + if (SUCCEEDED_LOG(StringFromCLSID(m_config.Id, &guuidStr))) + { + const auto guuid = guuidStr.get(); + JSONHelpers::FancyZonesDataInstance().ParseCustomZoneSetFromTmpFile(customZoneSetFilePath, guuid); + const auto& customZoneSets = JSONHelpers::FancyZonesDataInstance().GetCustomZoneSetsMap(); + if (!customZoneSets.contains(guuid)) + { + return false; + } + + const auto& zoneSet = customZoneSets.at(guuid); + if (zoneSet.type == JSONHelpers::CustomLayoutType::Canvas && std::holds_alternative(zoneSet.info)) + { + const auto& zoneSetInfo = std::get(zoneSet.info); + for (const auto& zone : zoneSetInfo.zones) + { + int x = zone.x; + int y = zone.y; + int width = zone.width; + int height = zone.height; + + if (x < 0 || y < 0 || width < 0 || height < 0) + { + return false; + } + + DPIAware::Convert(m_config.Monitor, x, y); + DPIAware::Convert(m_config.Monitor, width, height); + + RECT focusZoneRect{ x, y, x + width, y + height }; + AddZone(MakeZone(focusZoneRect)); + } + + return true; + } + else if (zoneSet.type == JSONHelpers::CustomLayoutType::Grid && std::holds_alternative(zoneSet.info)) + { + const auto& info = std::get(zoneSet.info); + return CalculateGridZones(workArea, info, spacing); + } + } + + return false; +} + +bool ZoneSet::CalculateGridZones(Rect workArea, JSONHelpers::GridLayoutInfo gridLayoutInfo, int spacing) +{ + bool success = true; + + int gutter = spacing; + + LONG totalWidth = static_cast(workArea.width()) - (gutter * 2) - (spacing * (gridLayoutInfo.columns() - 1)); + LONG totalHeight = static_cast(workArea.height()) - (gutter * 2) - (spacing * (gridLayoutInfo.rows() - 1)); + struct Info + { + LONG Extent; + LONG Start; + LONG End; + }; + Info rowInfo[JSONHelpers::MAX_ZONE_COUNT]; + Info columnInfo[JSONHelpers::MAX_ZONE_COUNT]; + + LONG top = gutter; + for (int row = 0; row < gridLayoutInfo.rows(); row++) + { + rowInfo[row].Start = top; + rowInfo[row].Extent = totalHeight * gridLayoutInfo.rowsPercents()[row] / C_MULTIPLIER; + rowInfo[row].End = rowInfo[row].Start + rowInfo[row].Extent; + top += rowInfo[row].Extent + spacing; + } + + LONG left = gutter; + for (int col = 0; col < gridLayoutInfo.columns(); col++) + { + columnInfo[col].Start = left; + columnInfo[col].Extent = totalWidth * gridLayoutInfo.columnsPercents()[col] / C_MULTIPLIER; + columnInfo[col].End = columnInfo[col].Start + columnInfo[col].Extent; + left += columnInfo[col].Extent + spacing; + } + + for (int row = 0; row < gridLayoutInfo.rows(); row++) + { + for (int col = 0; col < gridLayoutInfo.columns(); col++) + { + int i = gridLayoutInfo.cellChildMap()[row][col]; + if (((row == 0) || (gridLayoutInfo.cellChildMap()[row - 1][col] != i)) && + ((col == 0) || (gridLayoutInfo.cellChildMap()[row][col - 1] != i))) + { + left = columnInfo[col].Start; + top = rowInfo[row].Start; + + int maxRow = row; + while (((maxRow + 1) < gridLayoutInfo.rows()) && (gridLayoutInfo.cellChildMap()[maxRow + 1][col] == i)) + { + maxRow++; + } + int maxCol = col; + while (((maxCol + 1) < gridLayoutInfo.columns()) && (gridLayoutInfo.cellChildMap()[row][maxCol + 1] == i)) + { + maxCol++; + } + + LONG right = columnInfo[maxCol].End; + LONG bottom = rowInfo[maxRow].End; + + if (left >= right || top >= bottom || left < 0 || right < 0 || top < 0 || bottom < 0) + { + success = false; + } + + RECT focusZoneRect{ left, top, right, bottom }; + AddZone(MakeZone(focusZoneRect)); + } + } + } + + return success; +} + winrt::com_ptr ZoneSet::ZoneFromWindow(HWND window) noexcept { for (auto iter = m_zones.begin(); iter != m_zones.end(); iter++) diff --git a/src/modules/fancyzones/lib/ZoneSet.h b/src/modules/fancyzones/lib/ZoneSet.h index 2871dd6bd995..4b5c8c8cd851 100644 --- a/src/modules/fancyzones/lib/ZoneSet.h +++ b/src/modules/fancyzones/lib/ZoneSet.h @@ -1,27 +1,22 @@ #pragma once #include "Zone.h" +#include "JsonHelpers.h" -enum class ZoneSetLayout -{ - Grid, - Row, - Focus, - Custom -}; interface __declspec(uuid("{E4839EB7-669D-49CF-84A9-71A2DFD851A3}")) IZoneSet : public IUnknown { IFACEMETHOD_(GUID, Id)() = 0; - IFACEMETHOD_(WORD, LayoutId)() = 0; + IFACEMETHOD_(JSONHelpers::ZoneSetLayoutType, LayoutType)() = 0; IFACEMETHOD(AddZone)(winrt::com_ptr zone) = 0; IFACEMETHOD_(winrt::com_ptr, ZoneFromPoint)(POINT pt) = 0; IFACEMETHOD_(int, GetZoneIndexFromWindow)(HWND window) = 0; IFACEMETHOD_(std::vector>, GetZones)() = 0; - IFACEMETHOD_(void, Save)() = 0; IFACEMETHOD_(void, MoveWindowIntoZoneByIndex)(HWND window, HWND zoneWindow, int index) = 0; IFACEMETHOD_(void, MoveWindowIntoZoneByDirection)(HWND window, HWND zoneWindow, DWORD vkCode) = 0; - IFACEMETHOD_(void, MoveSizeEnd)(HWND window, HWND zoneWindow, POINT ptClient) = 0; + IFACEMETHOD_(void, MoveWindowIntoZoneByPoint)(HWND window, HWND zoneWindow, POINT ptClient) = 0; + IFACEMETHOD_(bool, CalculateZones) + (MONITORINFO monitorInfo, int zoneCount, int spacing, const std::wstring& customZoneSetFilePath) = 0; }; #define VERSION_PERSISTEDDATA 0x0000F00D @@ -32,28 +27,39 @@ struct ZoneSetPersistedData DWORD Version{VERSION_PERSISTEDDATA}; WORD LayoutId{}; DWORD ZoneCount{}; - ZoneSetLayout Layout{}; + JSONHelpers::ZoneSetLayoutType Layout{}; + RECT Zones[MAX_ZONES]{}; +}; + +struct ZoneSetPersistedDataOLD +{ + static constexpr inline size_t MAX_ZONES = 40; + DWORD Version{ VERSION_PERSISTEDDATA }; + WORD LayoutId{}; + DWORD ZoneCount{}; + JSONHelpers::ZoneSetLayoutType Layout{}; DWORD PaddingInner{}; DWORD PaddingOuter{}; RECT Zones[MAX_ZONES]{}; }; + struct ZoneSetConfig { ZoneSetConfig( GUID id, - WORD layoutId, + JSONHelpers::ZoneSetLayoutType layoutType, HMONITOR monitor, PCWSTR resolutionKey) noexcept : Id(id), - LayoutId(layoutId), + LayoutType(layoutType), Monitor(monitor), ResolutionKey(resolutionKey) { } GUID Id{}; - WORD LayoutId{}; + JSONHelpers::ZoneSetLayoutType LayoutType{}; HMONITOR Monitor{}; PCWSTR ResolutionKey{}; }; diff --git a/src/modules/fancyzones/lib/ZoneWindow.cpp b/src/modules/fancyzones/lib/ZoneWindow.cpp index 7bf747c76485..7f78a4a9d1f6 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.cpp +++ b/src/modules/fancyzones/lib/ZoneWindow.cpp @@ -1,765 +1,774 @@ -#include "pch.h" - -#include -#include - -#include "ZoneWindow.h" -#include "trace.h" -#include "util.h" -#include "RegistryHelpers.h" - -#include - -struct ZoneWindow : public winrt::implements -{ -public: - ZoneWindow(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, PCWSTR deviceId, PCWSTR virtualDesktopId, bool flashZones); - - IFACEMETHODIMP MoveSizeEnter(HWND window, bool dragEnabled) noexcept; - IFACEMETHODIMP MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled) noexcept; - IFACEMETHODIMP MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept; - IFACEMETHODIMP MoveSizeCancel() noexcept; - IFACEMETHODIMP_(bool) IsDragEnabled() noexcept { return m_dragEnabled; } - IFACEMETHODIMP_(void) MoveWindowIntoZoneByIndex(HWND window, int index) noexcept; - IFACEMETHODIMP_(void) MoveWindowIntoZoneByDirection(HWND window, DWORD vkCode) noexcept; - IFACEMETHODIMP_(void) CycleActiveZoneSet(DWORD vkCode) noexcept; - IFACEMETHODIMP_(std::wstring) DeviceId() noexcept { return { m_deviceId.get() }; } - IFACEMETHODIMP_(std::wstring) UniqueId() noexcept { return { m_uniqueId }; } - IFACEMETHODIMP_(std::wstring) WorkAreaKey() noexcept { return { m_workArea }; } - IFACEMETHODIMP_(void) SaveWindowProcessToZoneIndex(HWND window) noexcept; - IFACEMETHODIMP_(IZoneSet*) ActiveZoneSet() noexcept { return m_activeZoneSet.get(); } - -protected: - static LRESULT CALLBACK s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept; - -private: - struct ColorSetting - { - BYTE fillAlpha{}; - COLORREF fill{}; - BYTE borderAlpha{}; - COLORREF border{}; - int thickness{}; - }; - - void ShowZoneWindow() noexcept; - void HideZoneWindow() noexcept; - void InitializeId(PCWSTR deviceId, PCWSTR virtualDesktopId) noexcept; - void LoadSettings() noexcept; - void InitializeZoneSets(MONITORINFO const& mi) noexcept; - void LoadZoneSetsFromRegistry() noexcept; - void UpdateActiveZoneSet(_In_opt_ IZoneSet* zoneSet) noexcept; - LRESULT WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept; - void DrawBackdrop(wil::unique_hdc& hdc, RECT const& clientRect) noexcept; - void DrawZone(wil::unique_hdc& hdc, ColorSetting const& colorSetting, winrt::com_ptr zone) noexcept; - void DrawIndex(wil::unique_hdc& hdc, POINT offset, size_t index, int padding, int size, bool flipX, bool flipY, COLORREF colorFill); - void DrawActiveZoneSet(wil::unique_hdc& hdc, RECT const& clientRect) noexcept; - void OnPaint(wil::unique_hdc& hdc) noexcept; - void OnKeyUp(WPARAM wparam) noexcept; - winrt::com_ptr ZoneFromPoint(POINT pt) noexcept; - void ChooseDefaultActiveZoneSet() noexcept; - bool IsOccluded(POINT pt, size_t index) noexcept; - void CycleActiveZoneSetInternal(DWORD wparam, Trace::ZoneWindow::InputMode mode) noexcept; - void FlashZones() noexcept; - UINT GetDpiForMonitor() noexcept; - - winrt::com_ptr m_host; - HMONITOR m_monitor{}; - wchar_t m_uniqueId[256]{}; // Parsed deviceId + resolution + virtualDesktopId - wchar_t m_workArea[256]{}; - wil::unique_cotaskmem_string m_deviceId{}; - wil::unique_hwnd m_window{}; - HWND m_windowMoveSize{}; - bool m_drawHints{}; - bool m_flashMode{}; - bool m_dragEnabled{}; - winrt::com_ptr m_activeZoneSet; - GUID m_activeZoneSetId{}; - std::vector> m_zoneSets; - winrt::com_ptr m_highlightZone; - WPARAM m_keyLast{}; - size_t m_keyCycle{}; - static const UINT m_showAnimationDuration = 200; // ms - static const UINT m_flashDuration = 700; // ms -}; - -ZoneWindow::ZoneWindow( - IZoneWindowHost* host, - HINSTANCE hinstance, - HMONITOR monitor, - PCWSTR deviceId, - PCWSTR virtualDesktopId, - bool flashZones) - : m_monitor(monitor) -{ - m_host.copy_from(host); - - MONITORINFO mi{}; - mi.cbSize = sizeof(mi); - if (!GetMonitorInfoW(m_monitor, &mi)) - { - return; - } - const UINT dpi = GetDpiForMonitor(); - const Rect monitorRect(mi.rcMonitor); - const Rect workAreaRect(mi.rcWork, dpi); - - StringCchPrintf(m_workArea, ARRAYSIZE(m_workArea), L"%d_%d", monitorRect.width(), monitorRect.height()); - - InitializeId(deviceId, virtualDesktopId); - LoadSettings(); - InitializeZoneSets(mi); - - WNDCLASSEXW wcex{}; - wcex.cbSize = sizeof(WNDCLASSEX); - wcex.lpfnWndProc = s_WndProc; - wcex.hInstance = hinstance; - wcex.lpszClassName = L"SuperFancyZones_ZoneWindow"; - wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW); - RegisterClassExW(&wcex); - - m_window = wil::unique_hwnd { - CreateWindowExW(WS_EX_TOOLWINDOW, L"SuperFancyZones_ZoneWindow", L"", WS_POPUP, - workAreaRect.left(), workAreaRect.top(), workAreaRect.width(), workAreaRect.height(), - nullptr, nullptr, hinstance, this) - }; - - if (m_window) - { - MakeWindowTransparent(m_window.get()); - if (flashZones) - { - // Don't flash if the foreground window is in full screen mode - RECT windowRect; - if (GetWindowRect(GetForegroundWindow(), &windowRect) && - windowRect.left == mi.rcMonitor.left && - windowRect.top == mi.rcMonitor.top && - windowRect.right == mi.rcMonitor.right && - windowRect.bottom == mi.rcMonitor.bottom) +#include "pch.h" + +#include + +#include "ZoneWindow.h" +#include "trace.h" +#include "util.h" +#include "RegistryHelpers.h" + +#include +#include + +namespace ZoneWindowUtils +{ + const std::wstring& GetActiveZoneSetTmpPath() + { + static std::wstring activeZoneSetTmpFileName; + static std::once_flag flag; + + std::call_once(flag, []() { + wchar_t fileName[L_tmpnam_s]; + + if (_wtmpnam_s(fileName, L_tmpnam_s) != 0) + abort(); + + activeZoneSetTmpFileName = std::wstring{ fileName }; + }); + + return activeZoneSetTmpFileName; + } + + const std::wstring& GetAppliedZoneSetTmpPath() + { + static std::wstring appliedZoneSetTmpFileName; + static std::once_flag flag; + + std::call_once(flag, []() { + wchar_t fileName[L_tmpnam_s]; + + if (_wtmpnam_s(fileName, L_tmpnam_s) != 0) + abort(); + + appliedZoneSetTmpFileName = std::wstring{ fileName }; + }); + + return appliedZoneSetTmpFileName; + } + + const std::wstring& GetCustomZoneSetsTmpPath() + { + static std::wstring customZoneSetsTmpFileName; + static std::once_flag flag; + + std::call_once(flag, []() { + wchar_t fileName[L_tmpnam_s]; + + if (_wtmpnam_s(fileName, L_tmpnam_s) != 0) + abort(); + + customZoneSetsTmpFileName = std::wstring{ fileName }; + }); + + return customZoneSetsTmpFileName; + } +} + +namespace ZoneWindowDrawUtils +{ + struct ColorSetting + { + BYTE fillAlpha{}; + COLORREF fill{}; + BYTE borderAlpha{}; + COLORREF border{}; + int thickness{}; + }; + + bool IsOccluded(const std::vector>& zones, POINT pt, size_t index) noexcept + { + size_t i = 1; + + for (auto iter = zones.begin(); iter != zones.end(); iter++) + { + if (winrt::com_ptr zone = iter->try_as()) + { + if (i < index) + { + if (PtInRect(&zone->GetZoneRect(), pt)) + { + return true; + } + } + } + i++; + } + return false; + } + + void DrawBackdrop(wil::unique_hdc& hdc, RECT const& clientRect) noexcept + { + FillRectARGB(hdc, &clientRect, 0, RGB(0, 0, 0), false); + } + + void DrawIndex(wil::unique_hdc& hdc, POINT offset, size_t index, int padding, int size, bool flipX, bool flipY, COLORREF colorFill) + { + RECT rect = { offset.x, offset.y, offset.x + size, offset.y + size }; + for (int y = 0; y < 3; y++) + { + for (int x = 0; x < 3; x++) + { + RECT useRect = rect; + if (flipX) + { + if (x == 0) + useRect.left += (size + padding + size + padding); + else if (x == 2) + useRect.left -= (size + padding + size + padding); + useRect.right = useRect.left + size; + } + + if (flipY) + { + if (y == 0) + useRect.top += (size + padding + size + padding); + else if (y == 2) + useRect.top -= (size + padding + size + padding); + useRect.bottom = useRect.top + size; + } + + FillRectARGB(hdc, &useRect, 200, RGB(50, 50, 50), true); + + RECT inside = useRect; + InflateRect(&inside, -2, -2); + + FillRectARGB(hdc, &inside, 100, colorFill, true); + + rect.left += (size + padding); + rect.right = rect.left + size; + + if (--index == 0) + { + return; + } + } + rect.left = offset.x; + rect.right = rect.left + size; + rect.top += (size + padding); + rect.bottom = rect.top + size; + } + } + + void DrawZone(wil::unique_hdc& hdc, ColorSetting const& colorSetting, winrt::com_ptr zone, const std::vector>& zones, bool flashMode) noexcept + { + RECT zoneRect = zone->GetZoneRect(); + if (colorSetting.borderAlpha > 0) + { + FillRectARGB(hdc, &zoneRect, colorSetting.borderAlpha, colorSetting.border, false); + InflateRect(&zoneRect, colorSetting.thickness, colorSetting.thickness); + } + FillRectARGB(hdc, &zoneRect, colorSetting.fillAlpha, colorSetting.fill, false); + + if (flashMode) + { + return; + } + COLORREF const colorFill = RGB(255, 255, 255); + + size_t const index = zone->Id(); + int const padding = 5; + int const size = 10; + POINT offset = { zoneRect.left + padding, zoneRect.top + padding }; + if (!IsOccluded(zones, offset, index)) + { + DrawIndex(hdc, offset, index, padding, size, false, false, colorFill); // top left + return; + } + + offset.x = zoneRect.right - ((padding + size) * 3); + if (!IsOccluded(zones, offset, index)) + { + DrawIndex(hdc, offset, index, padding, size, true, false, colorFill); // top right + return; + } + + offset.y = zoneRect.bottom - ((padding + size) * 3); + if (!IsOccluded(zones, offset, index)) + { + DrawIndex(hdc, offset, index, padding, size, true, true, colorFill); // bottom right + return; + } + + offset.x = zoneRect.left + padding; + DrawIndex(hdc, offset, index, padding, size, false, true, colorFill); // bottom left + } + + void DrawActiveZoneSet(wil::unique_hdc& hdc, COLORREF highlightColor, int highlightOpacity, const std::vector>& zones + , const winrt::com_ptr& highlightZone, bool flashMode, bool drawHints) noexcept + { + static constexpr std::array colors{ + RGB(75, 75, 85), + RGB(150, 150, 160), + RGB(100, 100, 110), + RGB(125, 125, 135), + RGB(225, 225, 235), + RGB(25, 25, 35), + RGB(200, 200, 210), + RGB(50, 50, 60), + RGB(175, 175, 185), + }; + + // { fillAlpha, fill, borderAlpha, border, thickness } + ColorSetting const colorHints{ 225, RGB(81, 92, 107), 255, RGB(104, 118, 138), -2 }; + ColorSetting colorViewer{ OpacitySettingToAlpha(highlightOpacity), 0, 255, RGB(40, 50, 60), -2 }; + ColorSetting colorHighlight{ OpacitySettingToAlpha(highlightOpacity), 0, 255, 0, -2 }; + ColorSetting const colorFlash{ 200, RGB(81, 92, 107), 200, RGB(104, 118, 138), -2 }; + + const size_t maxColorIndex = min(size(zones) - 1, size(colors) - 1); + size_t colorIndex = maxColorIndex; + for (auto iter = zones.begin(); iter != zones.end(); iter++) + { + winrt::com_ptr zone = iter->try_as(); + if (!zone) { - return; - } - FlashZones(); - } - } -} - -IFACEMETHODIMP ZoneWindow::MoveSizeEnter(HWND window, bool dragEnabled) noexcept -{ - if (m_windowMoveSize) - { - return E_INVALIDARG; - } - - m_dragEnabled = dragEnabled; - m_windowMoveSize = window; - m_drawHints = true; - m_highlightZone = nullptr; - ShowZoneWindow(); - return S_OK; -} - -IFACEMETHODIMP ZoneWindow::MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled) noexcept -{ - bool redraw = false; - POINT ptClient = ptScreen; - MapWindowPoints(nullptr, m_window.get(), &ptClient, 1); - - m_dragEnabled = dragEnabled; - - if (dragEnabled) - { - auto highlightZone = ZoneFromPoint(ptClient); - redraw = (highlightZone != m_highlightZone); - m_highlightZone = std::move(highlightZone); - } - else if (m_highlightZone) - { - m_highlightZone = nullptr; - redraw = true; - } - - if (redraw) - { - InvalidateRect(m_window.get(), nullptr, true); - } - return S_OK; -} - -IFACEMETHODIMP ZoneWindow::MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept -{ - if (m_windowMoveSize != window) - { - return E_INVALIDARG; - } - - if (m_activeZoneSet) - { - POINT ptClient = ptScreen; - MapWindowPoints(nullptr, m_window.get(), &ptClient, 1); - m_activeZoneSet->MoveSizeEnd(window, m_window.get(), ptClient); - - SaveWindowProcessToZoneIndex(window); - } - Trace::ZoneWindow::MoveSizeEnd(m_activeZoneSet); - - HideZoneWindow(); - m_windowMoveSize = nullptr; - return S_OK; -} - -IFACEMETHODIMP ZoneWindow::MoveSizeCancel() noexcept -{ - HideZoneWindow(); - return S_OK; -} - -IFACEMETHODIMP_(void) ZoneWindow::MoveWindowIntoZoneByIndex(HWND window, int index) noexcept -{ - if (m_activeZoneSet) - { - m_activeZoneSet->MoveWindowIntoZoneByIndex(window, m_window.get(), index); - } -} - -IFACEMETHODIMP_(void) ZoneWindow::MoveWindowIntoZoneByDirection(HWND window, DWORD vkCode) noexcept -{ - if (m_activeZoneSet) - { - m_activeZoneSet->MoveWindowIntoZoneByDirection(window, m_window.get(), vkCode); - SaveWindowProcessToZoneIndex(window); - } -} - -IFACEMETHODIMP_(void) ZoneWindow::CycleActiveZoneSet(DWORD wparam) noexcept -{ - CycleActiveZoneSetInternal(wparam, Trace::ZoneWindow::InputMode::Keyboard); - - if (m_windowMoveSize) - { - InvalidateRect(m_window.get(), nullptr, true); - } - else - { - FlashZones(); - } -} - -IFACEMETHODIMP_(void) ZoneWindow::SaveWindowProcessToZoneIndex(HWND window) noexcept -{ + continue; + } + + if (zone != highlightZone) + { + if (flashMode) + { + DrawZone(hdc, colorFlash, zone, zones, flashMode); + } + else if (drawHints) + { + DrawZone(hdc, colorHints, zone, zones, flashMode); + } + { + colorViewer.fill = colors[colorIndex]; + DrawZone(hdc, colorViewer, zone, zones, flashMode); + } + } + colorIndex = colorIndex != 0 ? colorIndex - 1 : maxColorIndex; + } + + if (highlightZone) + { + colorHighlight.fill = highlightColor; + colorHighlight.border = RGB( + max(0, GetRValue(colorHighlight.fill) - 25), + max(0, GetGValue(colorHighlight.fill) - 25), + max(0, GetBValue(colorHighlight.fill) - 25)); + DrawZone(hdc, colorHighlight, highlightZone, zones, flashMode); + } + } +} + +struct ZoneWindow : public winrt::implements +{ +public: + ZoneWindow(HINSTANCE hinstance); + bool Init(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, std::wstring uniqueId, bool flashZones); + + IFACEMETHODIMP MoveSizeEnter(HWND window, bool dragEnabled) noexcept; + IFACEMETHODIMP MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled) noexcept; + IFACEMETHODIMP MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept; + IFACEMETHODIMP MoveSizeCancel() noexcept; + IFACEMETHODIMP_(bool) IsDragEnabled() noexcept { return m_dragEnabled; } + IFACEMETHODIMP_(void) MoveWindowIntoZoneByIndex(HWND window, int index) noexcept; + IFACEMETHODIMP_(void) MoveWindowIntoZoneByDirection(HWND window, DWORD vkCode) noexcept; + IFACEMETHODIMP_(void) CycleActiveZoneSet(DWORD vkCode) noexcept; + IFACEMETHODIMP_(std::wstring) UniqueId() noexcept { return { m_uniqueId }; } + IFACEMETHODIMP_(std::wstring) WorkAreaKey() noexcept { return { m_workArea }; } + IFACEMETHODIMP_(void) SaveWindowProcessToZoneIndex(HWND window) noexcept; + IFACEMETHODIMP_(IZoneSet*) ActiveZoneSet() noexcept { return m_activeZoneSet.get(); } + +protected: + static LRESULT CALLBACK s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept; + +private: + void ShowZoneWindow() noexcept; + void HideZoneWindow() noexcept; + void LoadSettings() noexcept; + void InitializeZoneSets(MONITORINFO const& mi) noexcept; + void CalculateZoneSet() noexcept; + void UpdateActiveZoneSet(_In_opt_ IZoneSet* zoneSet) noexcept; + LRESULT WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept; + void OnPaint(wil::unique_hdc& hdc) noexcept; + void OnKeyUp(WPARAM wparam) noexcept; + winrt::com_ptr ZoneFromPoint(POINT pt) noexcept; + void ChooseDefaultActiveZoneSet() noexcept; + void CycleActiveZoneSetInternal(DWORD wparam, Trace::ZoneWindow::InputMode mode) noexcept; + void FlashZones() noexcept; + + winrt::com_ptr m_host; + HMONITOR m_monitor{}; + std::wstring m_uniqueId; // Parsed deviceId + resolution + virtualDesktopId + wchar_t m_workArea[256]{}; + wil::unique_hwnd m_window{}; + HWND m_windowMoveSize{}; + bool m_drawHints{}; + bool m_flashMode{}; + bool m_dragEnabled{}; + winrt::com_ptr m_activeZoneSet; + std::vector> m_zoneSets; + winrt::com_ptr m_highlightZone; + WPARAM m_keyLast{}; + size_t m_keyCycle{}; + static const UINT m_showAnimationDuration = 200; // ms + static const UINT m_flashDuration = 700; // ms +}; + +ZoneWindow::ZoneWindow(HINSTANCE hinstance) +{ + WNDCLASSEXW wcex{}; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.lpfnWndProc = s_WndProc; + wcex.hInstance = hinstance; + wcex.lpszClassName = L"SuperFancyZones_ZoneWindow"; + wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW); + RegisterClassExW(&wcex); +} + +bool ZoneWindow::Init(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, std::wstring uniqueId, bool flashZones) +{ + if (!host) + { + return false; + } + m_host.copy_from(host); + + MONITORINFO mi{}; + mi.cbSize = sizeof(mi); + if (!GetMonitorInfoW(monitor, &mi)) + { + return false; + } + + m_monitor = monitor; + const UINT dpi = GetDpiForMonitor(m_monitor); + const Rect monitorRect(mi.rcMonitor); + const Rect workAreaRect(mi.rcWork, dpi); + StringCchPrintf(m_workArea, ARRAYSIZE(m_workArea), L"%d_%d", monitorRect.width(), monitorRect.height()); + + m_uniqueId = uniqueId; + LoadSettings(); + InitializeZoneSets(mi); + + m_window = wil::unique_hwnd{ + CreateWindowExW(WS_EX_TOOLWINDOW, L"SuperFancyZones_ZoneWindow", L"", WS_POPUP, workAreaRect.left(), workAreaRect.top(), workAreaRect.width(), workAreaRect.height(), nullptr, nullptr, hinstance, this) + }; + + if (!m_window) + { + return false; + } + + MakeWindowTransparent(m_window.get()); + if (flashZones) + { + // Don't flash if the foreground window is in full screen mode + RECT windowRect; + if (!(GetWindowRect(GetForegroundWindow(), &windowRect) && + windowRect.left == mi.rcMonitor.left && + windowRect.top == mi.rcMonitor.top && + windowRect.right == mi.rcMonitor.right && + windowRect.bottom == mi.rcMonitor.bottom)) + { + FlashZones(); + } + } + + return true; +} + +IFACEMETHODIMP ZoneWindow::MoveSizeEnter(HWND window, bool dragEnabled) noexcept +{ + if (m_windowMoveSize) + { + return E_INVALIDARG; + } + + m_dragEnabled = dragEnabled; + m_windowMoveSize = window; + m_drawHints = true; + m_highlightZone = nullptr; + ShowZoneWindow(); + return S_OK; +} + +IFACEMETHODIMP ZoneWindow::MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled) noexcept +{ + bool redraw = false; + POINT ptClient = ptScreen; + MapWindowPoints(nullptr, m_window.get(), &ptClient, 1); + + m_dragEnabled = dragEnabled; + + if (dragEnabled) + { + auto highlightZone = ZoneFromPoint(ptClient); + redraw = (highlightZone != m_highlightZone); + m_highlightZone = std::move(highlightZone); + } + else if (m_highlightZone) + { + m_highlightZone = nullptr; + redraw = true; + } + + if (redraw) + { + InvalidateRect(m_window.get(), nullptr, true); + } + return S_OK; +} + +IFACEMETHODIMP ZoneWindow::MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept +{ + if (m_windowMoveSize != window) + { + return E_INVALIDARG; + } + + if (m_activeZoneSet) + { + POINT ptClient = ptScreen; + MapWindowPoints(nullptr, m_window.get(), &ptClient, 1); + m_activeZoneSet->MoveWindowIntoZoneByPoint(window, m_window.get(), ptClient); + + SaveWindowProcessToZoneIndex(window); + } + Trace::ZoneWindow::MoveSizeEnd(m_activeZoneSet); + + HideZoneWindow(); + m_windowMoveSize = nullptr; + return S_OK; +} + +IFACEMETHODIMP ZoneWindow::MoveSizeCancel() noexcept +{ + HideZoneWindow(); + return S_OK; +} + +IFACEMETHODIMP_(void) +ZoneWindow::MoveWindowIntoZoneByIndex(HWND window, int index) noexcept +{ + if (m_activeZoneSet) + { + m_activeZoneSet->MoveWindowIntoZoneByIndex(window, m_window.get(), index); + } +} + +IFACEMETHODIMP_(void) +ZoneWindow::MoveWindowIntoZoneByDirection(HWND window, DWORD vkCode) noexcept +{ + if (m_activeZoneSet) + { + m_activeZoneSet->MoveWindowIntoZoneByDirection(window, m_window.get(), vkCode); + SaveWindowProcessToZoneIndex(window); + } +} + +IFACEMETHODIMP_(void) +ZoneWindow::CycleActiveZoneSet(DWORD wparam) noexcept +{ + CycleActiveZoneSetInternal(wparam, Trace::ZoneWindow::InputMode::Keyboard); + + if (m_windowMoveSize) + { + InvalidateRect(m_window.get(), nullptr, true); + } + else + { + FlashZones(); + } +} + +IFACEMETHODIMP_(void) +ZoneWindow::SaveWindowProcessToZoneIndex(HWND window) noexcept +{ auto processPath = get_process_path(window); - if (!processPath.empty()) + if (!processPath.empty() && m_activeZoneSet) { DWORD zoneIndex = static_cast(m_activeZoneSet->GetZoneIndexFromWindow(window)); if (zoneIndex != -1) { - RegistryHelpers::SaveAppLastZone(window, processPath.data(), zoneIndex); + JSONHelpers::FancyZonesDataInstance().SetAppLastZone(window, processPath.data(), zoneIndex); + } + } +} + +#pragma region private +void ZoneWindow::ShowZoneWindow() noexcept +{ + if (m_window) + { + m_flashMode = false; + + UINT flags = SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE; + + HWND windowInsertAfter = m_windowMoveSize; + if (windowInsertAfter == nullptr) + { + windowInsertAfter = HWND_TOPMOST; } - } + + SetWindowPos(m_window.get(), windowInsertAfter, 0, 0, 0, 0, flags); + + AnimateWindow(m_window.get(), m_showAnimationDuration, AW_BLEND); + InvalidateRect(m_window.get(), nullptr, true); + } +} + +void ZoneWindow::HideZoneWindow() noexcept +{ + if (m_window) + { + ShowWindow(m_window.get(), SW_HIDE); + m_keyLast = 0; + m_windowMoveSize = nullptr; + m_drawHints = false; + m_highlightZone = nullptr; + } +} + +void ZoneWindow::LoadSettings() noexcept +{ + auto& deviceInfoMap = JSONHelpers::FancyZonesDataInstance().GetDeviceInfoMap(); + if (!deviceInfoMap.contains(m_uniqueId)) + { + // Creates entry in map when ZoneWindow is created + deviceInfoMap[m_uniqueId] = JSONHelpers::DeviceInfoData{ JSONHelpers::ZoneSetData{ L"null", JSONHelpers::ZoneSetLayoutType::Grid, 1 }, true, 16, 3 }; + + JSONHelpers::FancyZonesDataInstance().MigrateDeviceInfoFromRegistry(m_uniqueId); + } + + JSONHelpers::FancyZonesDataInstance().ParseDeviceInfoFromTmpFile(ZoneWindowUtils::GetActiveZoneSetTmpPath()); + JSONHelpers::FancyZonesDataInstance().ParseDeletedCustomZoneSetsFromTmpFile(ZoneWindowUtils::GetCustomZoneSetsTmpPath()); +} + +void ZoneWindow::InitializeZoneSets(MONITORINFO const& mi) noexcept +{ + CalculateZoneSet(); + + if (!m_activeZoneSet) + { + ChooseDefaultActiveZoneSet(); + } +} + +void ZoneWindow::CalculateZoneSet() noexcept +{ + const auto& deviceInfoMap = JSONHelpers::FancyZonesDataInstance().GetDeviceInfoMap(); + const auto& activeDeviceId = JSONHelpers::FancyZonesDataInstance().GetActiveDeviceId(); + const auto& activeZoneSet = deviceInfoMap.at(m_uniqueId).activeZoneSet; + + if (!activeDeviceId.empty() && activeDeviceId.compare(m_uniqueId) != 0 && !activeZoneSet.uuid.empty()) + { + return; + } + GUID zoneSetId; + if (SUCCEEDED_LOG(CLSIDFromString(activeZoneSet.uuid.c_str(), &zoneSetId))) + { + auto zoneSet = MakeZoneSet(ZoneSetConfig( + zoneSetId, + activeZoneSet.type, + m_monitor, + m_workArea)); + MONITORINFO monitorInfo{}; + monitorInfo.cbSize = sizeof(monitorInfo); + if (GetMonitorInfoW(m_monitor, &monitorInfo)) + { + bool showSpacing = deviceInfoMap.at(m_uniqueId).showSpacing; + int spacing = showSpacing ? deviceInfoMap.at(m_uniqueId).spacing : 0; + int zoneCount = activeZoneSet.zoneCount.has_value() ? activeZoneSet.zoneCount.value() : 0; + zoneSet->CalculateZones(monitorInfo, zoneCount, spacing, ZoneWindowUtils::GetAppliedZoneSetTmpPath()); + UpdateActiveZoneSet(zoneSet.get()); + } + } +} + +void ZoneWindow::UpdateActiveZoneSet(_In_opt_ IZoneSet* zoneSet) noexcept +{ + m_activeZoneSet.copy_from(zoneSet); + + if (m_activeZoneSet) + { + wil::unique_cotaskmem_string zoneSetId; + if (SUCCEEDED_LOG(StringFromCLSID(m_activeZoneSet->Id(), &zoneSetId))) + { + JSONHelpers::FancyZonesDataInstance().SetActiveZoneSet(m_uniqueId, zoneSetId.get()); + } + } +} + +LRESULT ZoneWindow::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept +{ + switch (message) + { + case WM_NCDESTROY: { + ::DefWindowProc(m_window.get(), message, wparam, lparam); + SetWindowLongPtr(m_window.get(), GWLP_USERDATA, 0); + } + break; + + case WM_ERASEBKGND: + return 1; + + case WM_PRINTCLIENT: + case WM_PAINT: { + PAINTSTRUCT ps; + wil::unique_hdc hdc{ reinterpret_cast(wparam) }; + if (!hdc) + { + hdc.reset(BeginPaint(m_window.get(), &ps)); + } + + OnPaint(hdc); + + if (wparam == 0) + { + EndPaint(m_window.get(), &ps); + } + + hdc.release(); + } + break; + + default: + { + return DefWindowProc(m_window.get(), message, wparam, lparam); + } + } + return 0; } - -#pragma region private -void ZoneWindow::ShowZoneWindow() noexcept -{ - if (m_window) - { - m_flashMode = false; - - UINT flags = SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE; - - HWND windowInsertAfter = m_windowMoveSize; - if (windowInsertAfter == nullptr) - { - windowInsertAfter = HWND_TOPMOST; - } - - SetWindowPos(m_window.get(), windowInsertAfter, 0, 0, 0, 0, flags); - - AnimateWindow(m_window.get(), m_showAnimationDuration, AW_BLEND); - InvalidateRect(m_window.get(), nullptr, true); - } -} - -void ZoneWindow::HideZoneWindow() noexcept -{ - if (m_window) - { - ShowWindow(m_window.get(), SW_HIDE); - m_keyLast = 0; - m_windowMoveSize = nullptr; - m_drawHints = false; - m_highlightZone = nullptr; - } -} - -void ZoneWindow::InitializeId(PCWSTR deviceId, PCWSTR virtualDesktopId) noexcept -{ - SHStrDup(deviceId, &m_deviceId); - - MONITORINFOEXW mi; - mi.cbSize = sizeof(mi); - if (GetMonitorInfo(m_monitor, &mi)) - { - wchar_t parsedId[256]{}; - ParseDeviceId(m_deviceId.get(), parsedId, 256); - - Rect const monitorRect(mi.rcMonitor); - StringCchPrintf(m_uniqueId, ARRAYSIZE(m_uniqueId), L"%s_%d_%d_%s", - parsedId, monitorRect.width(), monitorRect.height(), virtualDesktopId); - } -} - -void ZoneWindow::LoadSettings() noexcept -{ - wchar_t activeZoneSetId[256]; - RegistryHelpers::GetString(m_uniqueId, L"ActiveZoneSetId", activeZoneSetId, sizeof(activeZoneSetId)); - CLSIDFromString(activeZoneSetId, &m_activeZoneSetId); -} - -void ZoneWindow::InitializeZoneSets(MONITORINFO const& mi) noexcept -{ - LoadZoneSetsFromRegistry(); - - if (!m_activeZoneSet) - { - ChooseDefaultActiveZoneSet(); - } -} - -void ZoneWindow::LoadZoneSetsFromRegistry() noexcept -{ - wil::unique_hkey key{RegistryHelpers::OpenKey(m_workArea)}; - if (!key) - { - return; - } - - ZoneSetPersistedData data{}; - DWORD dataSize = sizeof(data); - wchar_t value[256]{}; - DWORD valueLength = ARRAYSIZE(value); - DWORD i = 0; - while (RegEnumValueW(key.get(), i++, value, &valueLength, nullptr, nullptr, reinterpret_cast(&data), &dataSize) == ERROR_SUCCESS) - { - if (data.Version == VERSION_PERSISTEDDATA) - { - GUID zoneSetId; - if (SUCCEEDED_LOG(CLSIDFromString(value, &zoneSetId))) - { - auto zoneSet = MakeZoneSet(ZoneSetConfig( - zoneSetId, - data.LayoutId, - m_monitor, - m_workArea)); - - if (zoneSet) - { - for (UINT j = 0; j < data.ZoneCount; j++) - { - zoneSet->AddZone(MakeZone(data.Zones[j])); - } - - if (zoneSetId == m_activeZoneSetId) - { - UpdateActiveZoneSet(zoneSet.get()); - } - - m_zoneSets.emplace_back(std::move(zoneSet)); - } - } - } - else - { - // Migrate from older settings format - } - - valueLength = ARRAYSIZE(value); - dataSize = sizeof(data); - } -} - -void ZoneWindow::UpdateActiveZoneSet(_In_opt_ IZoneSet* zoneSet) noexcept -{ - m_activeZoneSet.copy_from(zoneSet); - - if (m_activeZoneSet) - { - wil::unique_cotaskmem_string zoneSetId; - if (SUCCEEDED_LOG(StringFromCLSID(m_activeZoneSet->Id(), &zoneSetId))) - { - RegistryHelpers::SetString(m_uniqueId, L"ActiveZoneSetId", zoneSetId.get()); - } - } -} - -LRESULT ZoneWindow::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept -{ - switch (message) - { - case WM_NCDESTROY: - { - ::DefWindowProc(m_window.get(), message, wparam, lparam); - SetWindowLongPtr(m_window.get(), GWLP_USERDATA, 0); - } - break; - - case WM_ERASEBKGND: - return 1; - - case WM_PRINTCLIENT: - case WM_PAINT: - { - PAINTSTRUCT ps; - wil::unique_hdc hdc{ reinterpret_cast(wparam) }; - if (!hdc) - { - hdc.reset(BeginPaint(m_window.get(), &ps)); - } - - OnPaint(hdc); - - if (wparam == 0) - { - EndPaint(m_window.get(), &ps); - } - - hdc.release(); - } - break; - - default: - { - return DefWindowProc(m_window.get(), message, wparam, lparam); - } - } - return 0; -} - -void ZoneWindow::DrawBackdrop(wil::unique_hdc& hdc, RECT const& clientRect) noexcept -{ - FillRectARGB(hdc, &clientRect, 0, RGB(0, 0, 0), false); -} - -void ZoneWindow::DrawZone(wil::unique_hdc& hdc, ColorSetting const& colorSetting, winrt::com_ptr zone) noexcept -{ - RECT zoneRect = zone->GetZoneRect(); - if (colorSetting.borderAlpha > 0) - { - FillRectARGB(hdc, &zoneRect, colorSetting.borderAlpha, colorSetting.border, false); - InflateRect(&zoneRect, colorSetting.thickness, colorSetting.thickness); - } - FillRectARGB(hdc, &zoneRect, colorSetting.fillAlpha, colorSetting.fill, false); - - if (m_flashMode) - { - return; - } - COLORREF const colorFill = RGB(255, 255, 255); - - size_t const index = zone->Id(); - int const padding = 5; - int const size = 10; - POINT offset = { zoneRect.left + padding, zoneRect.top + padding }; - if (!IsOccluded(offset, index)) - { - DrawIndex(hdc, offset, index, padding, size, false, false, colorFill); // top left - return; - } - - offset.x = zoneRect.right - ((padding + size) * 3); - if (!IsOccluded(offset, index)) - { - DrawIndex(hdc, offset, index, padding, size, true, false, colorFill); // top right - return; - } - - offset.y = zoneRect.bottom - ((padding + size) * 3); - if (!IsOccluded(offset, index)) - { - DrawIndex(hdc, offset, index, padding, size, true, true, colorFill); // bottom right - return; - } - - offset.x = zoneRect.left + padding; - DrawIndex(hdc, offset, index, padding, size, false, true, colorFill); // bottom left -} - -void ZoneWindow::DrawIndex(wil::unique_hdc& hdc, POINT offset, size_t index, int padding, int size, bool flipX, bool flipY, COLORREF colorFill) -{ - RECT rect = { offset.x, offset.y, offset.x + size, offset.y + size }; - for (int y = 0; y < 3; y++) - { - for (int x = 0; x < 3; x++) - { - RECT useRect = rect; - if (flipX) - { - if (x == 0) useRect.left += (size + padding + size + padding); - else if (x == 2) useRect.left -= (size + padding + size + padding); - useRect.right = useRect.left + size; - } - - if (flipY) - { - if (y == 0) useRect.top += (size + padding + size + padding); - else if (y == 2) useRect.top -= (size + padding + size + padding); - useRect.bottom = useRect.top + size; - } - - FillRectARGB(hdc, &useRect, 200, RGB(50, 50, 50), true); - - RECT inside = useRect; - InflateRect(&inside, -2, -2); - - FillRectARGB(hdc, &inside, 100, colorFill, true); - - rect.left += (size + padding); - rect.right = rect.left + size; - - if (--index == 0) - { - return; - } - } - rect.left = offset.x; - rect.right = rect.left + size; - rect.top += (size + padding); - rect.bottom = rect.top + size; - } -} - -void ZoneWindow::DrawActiveZoneSet(wil::unique_hdc& hdc, RECT const& clientRect) noexcept -{ - if (m_activeZoneSet) - { - static constexpr std::array colors{ - RGB(75, 75, 85), - RGB(150, 150, 160), - RGB(100, 100, 110), - RGB(125, 125, 135), - RGB(225, 225, 235), - RGB(25, 25, 35), - RGB(200, 200, 210), - RGB(50, 50, 60), - RGB(175, 175, 185), - }; - - // { fillAlpha, fill, borderAlpha, border, thickness } - ColorSetting const colorHints { 225, RGB(81, 92, 107), 255, RGB(104, 118, 138), -2 }; - ColorSetting colorViewer { OpacitySettingToAlpha(m_host->GetZoneHighlightOpacity()), 0, 255, RGB(40, 50, 60), -2 }; - ColorSetting colorHighlight { OpacitySettingToAlpha(m_host->GetZoneHighlightOpacity()), 0, 255, 0, -2 }; - ColorSetting const colorFlash { 200, RGB(81, 92, 107), 200, RGB(104, 118, 138), -2 }; - - auto zones = m_activeZoneSet->GetZones(); - const size_t maxColorIndex = min(size(zones) - 1, size(colors) - 1); - size_t colorIndex = maxColorIndex; - for (auto iter = zones.begin(); iter != zones.end(); iter++) - { - winrt::com_ptr zone = iter->try_as(); - if (!zone) - { - continue; - } - - if (zone != m_highlightZone) - { - if (m_flashMode) - { - DrawZone(hdc, colorFlash, zone); - } - else if (m_drawHints) - { - DrawZone(hdc, colorHints, zone); - } - { - colorViewer.fill = colors[colorIndex]; - DrawZone(hdc, colorViewer, zone); - } - } - colorIndex = colorIndex != 0 ? colorIndex - 1 : maxColorIndex; - } - - if (m_highlightZone) - { - colorHighlight.fill = m_host->GetZoneHighlightColor(); - colorHighlight.border = RGB( - max(0, GetRValue(colorHighlight.fill) - 25), - max(0, GetGValue(colorHighlight.fill) - 25), - max(0, GetBValue(colorHighlight.fill) - 25) - ); - DrawZone(hdc, colorHighlight, m_highlightZone); - } - } -} - -void ZoneWindow::OnPaint(wil::unique_hdc& hdc) noexcept -{ - RECT clientRect; - GetClientRect(m_window.get(), &clientRect); - - wil::unique_hdc hdcMem; - HPAINTBUFFER bufferedPaint = BeginBufferedPaint(hdc.get(), &clientRect, BPBF_TOPDOWNDIB, nullptr, &hdcMem); - if (bufferedPaint) - { - DrawBackdrop(hdcMem, clientRect); - DrawActiveZoneSet(hdcMem, clientRect); - EndBufferedPaint(bufferedPaint, TRUE); - } -} - -void ZoneWindow::OnKeyUp(WPARAM wparam) noexcept -{ - bool fRedraw = false; - Trace::ZoneWindow::KeyUp(wparam); - - if ((wparam >= '0') && (wparam <= '9')) - { - CycleActiveZoneSetInternal(static_cast(wparam), Trace::ZoneWindow::InputMode::Keyboard); - InvalidateRect(m_window.get(), nullptr, true); - } -} - -winrt::com_ptr ZoneWindow::ZoneFromPoint(POINT pt) noexcept -{ - if (m_activeZoneSet) - { - return m_activeZoneSet->ZoneFromPoint(pt); - } - return nullptr; -} - -void ZoneWindow::ChooseDefaultActiveZoneSet() noexcept -{ - // Default zone set can be empty (no fancyzones layout), or it can be layout from virtual - // desktop from which this virtual desktop is created. - if (GUID id{ m_host->GetCurrentMonitorZoneSetId(m_monitor) }; id != GUID_NULL) { - for (const auto& zoneSet : m_zoneSets) { - if (id == zoneSet->Id()) { + +void ZoneWindow::OnPaint(wil::unique_hdc& hdc) noexcept +{ + RECT clientRect; + GetClientRect(m_window.get(), &clientRect); + + wil::unique_hdc hdcMem; + HPAINTBUFFER bufferedPaint = BeginBufferedPaint(hdc.get(), &clientRect, BPBF_TOPDOWNDIB, nullptr, &hdcMem); + if (bufferedPaint) + { + ZoneWindowDrawUtils::DrawBackdrop(hdcMem, clientRect); + if (m_activeZoneSet && m_host) + { + ZoneWindowDrawUtils::DrawActiveZoneSet(hdcMem, m_host->GetZoneHighlightColor(), m_host->GetZoneHighlightOpacity(), m_activeZoneSet->GetZones(), m_highlightZone, m_flashMode, m_drawHints); + } + + EndBufferedPaint(bufferedPaint, TRUE); + } +} + +void ZoneWindow::OnKeyUp(WPARAM wparam) noexcept +{ + bool fRedraw = false; + Trace::ZoneWindow::KeyUp(wparam); + + if ((wparam >= '0') && (wparam <= '9')) + { + CycleActiveZoneSetInternal(static_cast(wparam), Trace::ZoneWindow::InputMode::Keyboard); + InvalidateRect(m_window.get(), nullptr, true); + } +} + +winrt::com_ptr ZoneWindow::ZoneFromPoint(POINT pt) noexcept +{ + if (m_activeZoneSet) + { + return m_activeZoneSet->ZoneFromPoint(pt); + } + return nullptr; +} + +void ZoneWindow::ChooseDefaultActiveZoneSet() noexcept +{ + // Default zone set can be empty (no fancyzones layout), or it can be layout from virtual + // desktop from which this virtual desktop is created. + if (m_host) + { + auto* zoneSet = m_host->GetCurrentMonitorZoneSet(m_monitor); + if (zoneSet) + { + UpdateActiveZoneSet(zoneSet); + } + } +} + +void ZoneWindow::CycleActiveZoneSetInternal(DWORD wparam, Trace::ZoneWindow::InputMode mode) noexcept +{ + Trace::ZoneWindow::CycleActiveZoneSet(m_activeZoneSet, mode); + if (m_keyLast != wparam) + { + m_keyCycle = 0; + } + + m_keyLast = wparam; + + bool loopAround = true; + size_t const val = static_cast(wparam - L'0'); + size_t i = 0; + for (auto zoneSet : m_zoneSets) + { + if (zoneSet->GetZones().size() == val) + { + if (i < m_keyCycle) + { + i++; + } + else + { UpdateActiveZoneSet(zoneSet.get()); - return; + loopAround = false; + break; } - } - } -} - -bool ZoneWindow::IsOccluded(POINT pt, size_t index) noexcept -{ - auto zones = m_activeZoneSet->GetZones(); - size_t i = 1; - - for (auto iter = zones.begin(); iter != zones.end(); iter++) - { - if (winrt::com_ptr zone = iter->try_as()) - { - if (i < index) - { - if (PtInRect(&zone->GetZoneRect(), pt)) - { - return true; - } - } - } - i++; - } - return false; -} - -void ZoneWindow::CycleActiveZoneSetInternal(DWORD wparam, Trace::ZoneWindow::InputMode mode) noexcept -{ - Trace::ZoneWindow::CycleActiveZoneSet(m_activeZoneSet, mode); - if (m_keyLast != wparam) - { - m_keyCycle = 0; - } - - m_keyLast = wparam; - - bool loopAround = true; - size_t const val = static_cast(wparam - L'0'); - size_t i = 0; - for (auto zoneSet : m_zoneSets) - { - if (zoneSet->GetZones().size() == val) - { - if (i < m_keyCycle) - { - i++; - } - else - { - UpdateActiveZoneSet(zoneSet.get()); - loopAround = false; - break; - } - } - } - - if ((m_keyCycle > 0) && loopAround) - { - // Cycling through a non-empty group and hit the end - m_keyCycle = 0; - OnKeyUp(wparam); - } - else - { - m_keyCycle++; - } - - m_host->MoveWindowsOnActiveZoneSetChange(); - m_highlightZone = nullptr; -} - -void ZoneWindow::FlashZones() noexcept -{ - m_flashMode = true; - - ShowWindow(m_window.get(), SW_SHOWNA); - std::thread([window = m_window.get()]() - { - AnimateWindow(window, m_flashDuration, AW_HIDE | AW_BLEND); - }).detach(); -} - -typedef BOOL(WINAPI *GetDpiForMonitorInternalFunc)(HMONITOR, UINT, UINT*, UINT*); -UINT ZoneWindow::GetDpiForMonitor() noexcept -{ - UINT dpi{}; - if (wil::unique_hmodule user32{ LoadLibrary(L"user32.dll") }) - { - if (auto func = reinterpret_cast(GetProcAddress(user32.get(), "GetDpiForMonitorInternal"))) - { - func(m_monitor, 0, &dpi, &dpi); - } - } - - if (dpi == 0) - { - if (wil::unique_hdc hdc{ GetDC(nullptr) }) - { - dpi = GetDeviceCaps(hdc.get(), LOGPIXELSX); - } - } - - return (dpi == 0) ? DPIAware::DEFAULT_DPI : dpi; -} -#pragma endregion - -LRESULT CALLBACK ZoneWindow::s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept -{ - auto thisRef = reinterpret_cast(GetWindowLongPtr(window, GWLP_USERDATA)); - if ((thisRef == nullptr) && (message == WM_CREATE)) - { - auto createStruct = reinterpret_cast(lparam); - thisRef = reinterpret_cast(createStruct->lpCreateParams); - SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(thisRef)); - } - - return (thisRef != nullptr) ? thisRef->WndProc(message, wparam, lparam) : - DefWindowProc(window, message, wparam, lparam); -} - -winrt::com_ptr MakeZoneWindow(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, - PCWSTR deviceId, PCWSTR virtualDesktopId, bool flashZones) noexcept -{ - return winrt::make_self(host, hinstance, monitor, deviceId, virtualDesktopId, flashZones); -} + } + } + + if ((m_keyCycle > 0) && loopAround) + { + // Cycling through a non-empty group and hit the end + m_keyCycle = 0; + OnKeyUp(wparam); + } + else + { + m_keyCycle++; + } + + if (m_host) + { + m_host->MoveWindowsOnActiveZoneSetChange(); + } + m_highlightZone = nullptr; +} + +void ZoneWindow::FlashZones() noexcept +{ + m_flashMode = true; + + ShowWindow(m_window.get(), SW_SHOWNA); + std::thread([window = m_window.get()]() { + AnimateWindow(window, m_flashDuration, AW_HIDE | AW_BLEND); + }).detach(); +} +#pragma endregion + +LRESULT CALLBACK ZoneWindow::s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept +{ + auto thisRef = reinterpret_cast(GetWindowLongPtr(window, GWLP_USERDATA)); + if ((thisRef == nullptr) && (message == WM_CREATE)) + { + auto createStruct = reinterpret_cast(lparam); + thisRef = reinterpret_cast(createStruct->lpCreateParams); + SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(thisRef)); + } + + return (thisRef != nullptr) ? thisRef->WndProc(message, wparam, lparam) : + DefWindowProc(window, message, wparam, lparam); +} + +winrt::com_ptr MakeZoneWindow(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, std::wstring uniqueId, bool flashZones) noexcept +{ + auto self = winrt::make_self(hinstance); + if (self->Init(host, hinstance, monitor, uniqueId, flashZones)) + { + return self; + } + + return nullptr; +} diff --git a/src/modules/fancyzones/lib/ZoneWindow.h b/src/modules/fancyzones/lib/ZoneWindow.h index 0341e12f3b97..2ed484910740 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.h +++ b/src/modules/fancyzones/lib/ZoneWindow.h @@ -2,6 +2,13 @@ #include "FancyZones.h" #include "lib/ZoneSet.h" +namespace ZoneWindowUtils +{ + const std::wstring& GetActiveZoneSetTmpPath(); + const std::wstring& GetAppliedZoneSetTmpPath(); + const std::wstring& GetCustomZoneSetsTmpPath(); +} + interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IZoneWindow : public IUnknown { IFACEMETHOD(MoveSizeEnter)(HWND window, bool dragEnabled) = 0; @@ -13,11 +20,10 @@ interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IZoneWindow IFACEMETHOD_(void, MoveWindowIntoZoneByDirection)(HWND window, DWORD vkCode) = 0; IFACEMETHOD_(void, CycleActiveZoneSet)(DWORD vkCode) = 0; IFACEMETHOD_(void, SaveWindowProcessToZoneIndex)(HWND window) = 0; - IFACEMETHOD_(std::wstring, DeviceId)() = 0; IFACEMETHOD_(std::wstring, UniqueId)() = 0; IFACEMETHOD_(std::wstring, WorkAreaKey)() = 0; IFACEMETHOD_(IZoneSet*, ActiveZoneSet)() = 0; }; winrt::com_ptr MakeZoneWindow(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, - PCWSTR deviceId, PCWSTR virtualDesktopId, bool flashZones) noexcept; + std::wstring uniqueId, bool flashZones) noexcept; diff --git a/src/modules/fancyzones/lib/util.cpp b/src/modules/fancyzones/lib/util.cpp new file mode 100644 index 000000000000..aee018f06767 --- /dev/null +++ b/src/modules/fancyzones/lib/util.cpp @@ -0,0 +1,27 @@ +#include "pch.h" +#include "util.h" + +#include + +typedef BOOL(WINAPI* GetDpiForMonitorInternalFunc)(HMONITOR, UINT, UINT*, UINT*); +UINT GetDpiForMonitor(HMONITOR monitor) noexcept +{ + UINT dpi{}; + if (wil::unique_hmodule user32{ LoadLibrary(L"user32.dll") }) + { + if (auto func = reinterpret_cast(GetProcAddress(user32.get(), "GetDpiForMonitorInternal"))) + { + func(monitor, 0, &dpi, &dpi); + } + } + + if (dpi == 0) + { + if (wil::unique_hdc hdc{ GetDC(nullptr) }) + { + dpi = GetDeviceCaps(hdc.get(), LOGPIXELSX); + } + } + + return (dpi == 0) ? DPIAware::DEFAULT_DPI : dpi; +} diff --git a/src/modules/fancyzones/lib/util.h b/src/modules/fancyzones/lib/util.h index 9f59f21121d9..171a7892985b 100644 --- a/src/modules/fancyzones/lib/util.h +++ b/src/modules/fancyzones/lib/util.h @@ -117,6 +117,12 @@ inline void ParseDeviceId(PCWSTR deviceId, PWSTR parsedId, size_t size) // We're interested in the unique part between the first and last #'s // Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7} // Example output: DELA026#5&10a58c63&0&UID16777488 + const std::wstring defaultDeviceId = L"FallbackDevice"; + if (!deviceId) + { + StringCchCopy(parsedId, size, defaultDeviceId.c_str()); + return; + } wchar_t buffer[256]; StringCchCopy(buffer, 256, deviceId); @@ -130,12 +136,14 @@ inline void ParseDeviceId(PCWSTR deviceId, PWSTR parsedId, size_t size) } else { - StringCchCopy(parsedId, size, L"FallbackDevice"); + StringCchCopy(parsedId, size, defaultDeviceId.c_str()); } } inline int OpacitySettingToAlpha(int opacity) { // convert percentage to a 0-255 alpha value - return opacity * 2.55; + return static_cast(opacity * 2.55); } + +UINT GetDpiForMonitor(HMONITOR monitor) noexcept; \ No newline at end of file diff --git a/src/modules/fancyzones/tests/UnitTests/FancyZones.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/FancyZones.Spec.cpp new file mode 100644 index 000000000000..1a3165c6d4fa --- /dev/null +++ b/src/modules/fancyzones/tests/UnitTests/FancyZones.Spec.cpp @@ -0,0 +1,492 @@ +#include "pch.h" + +#include + +#include +#include +#include + +#include "util.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +namespace FancyZonesUnitTests +{ + TEST_CLASS(FancyZonesUnitTests) + { + HINSTANCE m_hInst; + winrt::com_ptr m_settings; + + TEST_METHOD_INITIALIZE(Init) + { + m_hInst = (HINSTANCE)GetModuleHandleW(nullptr); + m_settings = MakeFancyZonesSettings(m_hInst, L"FancyZonesUnitTests"); + Assert::IsTrue(m_settings != nullptr); + } + + TEST_METHOD(Create) + { + auto actual = MakeFancyZones(m_hInst, m_settings); + Assert::IsNotNull(actual.get()); + } + TEST_METHOD(CreateWithEmptyHinstance) + { + auto actual = MakeFancyZones({}, m_settings); + Assert::IsNotNull(actual.get()); + } + + TEST_METHOD(CreateWithNullHinstance) + { + auto actual = MakeFancyZones(nullptr, m_settings); + Assert::IsNotNull(actual.get()); + } + + TEST_METHOD(CreateWithNullSettings) + { + auto actual = MakeFancyZones(m_hInst, nullptr); + Assert::IsNull(actual.get()); + } + + TEST_METHOD(Run) + { + auto actual = MakeFancyZones(m_hInst, m_settings); + + std::vector threads; + std::atomic counter = 0; + const int expectedCount = 10; + + auto runFunc = [&]() { + actual->Run(); + counter++; + }; + + for (int i = 0; i < expectedCount; i++) + { + threads.push_back(std::thread(runFunc)); + } + + for (auto& thread : threads) + { + thread.join(); + } + + Assert::AreEqual(expectedCount, counter.load()); + } + + TEST_METHOD(Destroy) + { + auto actual = MakeFancyZones(m_hInst, m_settings); + + std::vector threads; + std::atomic counter = 0; + const int expectedCount = 10; + + auto destroyFunc = [&]() { + actual->Destroy(); + counter++; + }; + + for (int i = 0; i < expectedCount; i++) + { + threads.push_back(std::thread(destroyFunc)); + } + + for (auto& thread : threads) + { + thread.join(); + } + + Assert::AreEqual(expectedCount, counter.load()); + } + + TEST_METHOD(RunDestroy) + { + auto actual = MakeFancyZones(m_hInst, m_settings); + + std::vector threads; + std::atomic counter = 0; + const int expectedCount = 20; + + auto func = [&]() { + auto idHash = std::hash()(std::this_thread::get_id()); + bool run = (idHash % 2 == 0); + run ? actual->Run() : actual->Destroy(); + counter++; + }; + + for (int i = 0; i < expectedCount; i++) + { + threads.push_back(std::thread(func)); + } + + for (auto& thread : threads) + { + thread.join(); + } + + Assert::AreEqual(expectedCount, counter.load()); + } + }; + + TEST_CLASS(FancyZonesIZoneWindowHostUnitTests) + { + HINSTANCE m_hInst{}; + winrt::com_ptr m_settings = nullptr; + winrt::com_ptr m_zoneWindowHost = nullptr; + + std::wstring serializedPowerToySettings(const Settings& settings) + { + PowerToysSettings::Settings ptSettings(HINSTANCE{}, L"FancyZonesUnitTests"); + + ptSettings.add_hotkey(L"fancyzones_editor_hotkey", IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL, settings.editorHotkey); + ptSettings.add_bool_toogle(L"fancyzones_shiftDrag", IDS_SETTING_DESCRIPTION_SHIFTDRAG, settings.shiftDrag); + ptSettings.add_bool_toogle(L"fancyzones_overrideSnapHotkeys", IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS, settings.overrideSnapHotkeys); + ptSettings.add_bool_toogle(L"fancyzones_zoneSetChange_flashZones", IDS_SETTING_DESCRIPTION_ZONESETCHANGE_FLASHZONES, settings.zoneSetChange_flashZones); + ptSettings.add_bool_toogle(L"fancyzones_displayChange_moveWindows", IDS_SETTING_DESCRIPTION_DISPLAYCHANGE_MOVEWINDOWS, settings.displayChange_moveWindows); + ptSettings.add_bool_toogle(L"fancyzones_zoneSetChange_moveWindows", IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS, settings.zoneSetChange_moveWindows); + ptSettings.add_bool_toogle(L"fancyzones_virtualDesktopChange_moveWindows", IDS_SETTING_DESCRIPTION_VIRTUALDESKTOPCHANGE_MOVEWINDOWS, settings.virtualDesktopChange_moveWindows); + ptSettings.add_bool_toogle(L"fancyzones_appLastZone_moveWindows", IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS, settings.appLastZone_moveWindows); + ptSettings.add_bool_toogle(L"use_cursorpos_editor_startupscreen", IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN, settings.use_cursorpos_editor_startupscreen); + ptSettings.add_int_spinner(L"fancyzones_highlight_opacity", IDS_SETTINGS_HIGHLIGHT_OPACITY, settings.zoneHighlightOpacity, 0, 100, 1); + ptSettings.add_color_picker(L"fancyzones_zoneHighlightColor", IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR, settings.zoneHightlightColor); + ptSettings.add_multiline_string(L"fancyzones_excluded_apps", IDS_SETTING_EXCLCUDED_APPS_DESCRIPTION, settings.excludedApps); + + return ptSettings.serialize(); + } + + TEST_METHOD_INITIALIZE(Init) + { + m_hInst = (HINSTANCE)GetModuleHandleW(nullptr); + m_settings = MakeFancyZonesSettings(m_hInst, L"FancyZonesUnitTests"); + Assert::IsTrue(m_settings != nullptr); + + auto fancyZones = MakeFancyZones(m_hInst, m_settings); + Assert::IsTrue(fancyZones != nullptr); + + m_zoneWindowHost = fancyZones.as(); + Assert::IsTrue(m_zoneWindowHost != nullptr); + } + + TEST_METHOD_CLEANUP(Cleanup) + { + const auto settingsFile = PTSettingsHelper::get_module_save_folder_location(L"FancyZonesUnitTests") + L"\\settings.json"; + std::filesystem::remove(settingsFile); + } + + TEST_METHOD(GetZoneHighlightColor) + { + const auto expected = RGB(171, 175, 238); + const Settings settings{ + .shiftDrag = true, + .displayChange_moveWindows = true, + .virtualDesktopChange_moveWindows = true, + .zoneSetChange_flashZones = false, + .zoneSetChange_moveWindows = true, + .overrideSnapHotkeys = false, + .appLastZone_moveWindows = true, + .use_cursorpos_editor_startupscreen = true, + .zoneHightlightColor = L"#abafee", + .zoneHighlightOpacity = 45, + .editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3), + .excludedApps = L"app\r\napp2", + .excludedAppsArray = { L"APP", L"APP2" }, + }; + + auto config = serializedPowerToySettings(settings); + m_settings->SetConfig(config.c_str()); + + const auto actual = m_zoneWindowHost->GetZoneHighlightColor(); + Assert::AreEqual(expected, actual); + } + + TEST_METHOD(GetZoneHighlightOpacity) + { + const auto expected = 88; + const Settings settings{ + .shiftDrag = true, + .displayChange_moveWindows = true, + .virtualDesktopChange_moveWindows = true, + .zoneSetChange_flashZones = false, + .zoneSetChange_moveWindows = true, + .overrideSnapHotkeys = false, + .appLastZone_moveWindows = true, + .use_cursorpos_editor_startupscreen = true, + .zoneHightlightColor = L"#abafee", + .zoneHighlightOpacity = expected, + .editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3), + .excludedApps = L"app\r\napp2", + .excludedAppsArray = { L"APP", L"APP2" }, + }; + + auto config = serializedPowerToySettings(settings); + m_settings->SetConfig(config.c_str()); + + const auto actual = m_zoneWindowHost->GetZoneHighlightOpacity(); + Assert::AreEqual(expected, actual); + } + + TEST_METHOD(GetCurrentMonitorZoneSetEmpty) + { + const auto* actual = m_zoneWindowHost->GetCurrentMonitorZoneSet(Mocks::Monitor()); + Assert::IsNull(actual); + } + + TEST_METHOD(GetCurrentMonitorZoneSetNullMonitor) + { + const auto* actual = m_zoneWindowHost->GetCurrentMonitorZoneSet(nullptr); + Assert::IsNull(actual); + } + }; + + TEST_CLASS(FancyZonesIFancyZonesCallbackUnitTests) + { + HINSTANCE m_hInst{}; + winrt::com_ptr m_settings = nullptr; + winrt::com_ptr m_fzCallback = nullptr; + + JSONHelpers::FancyZonesData& m_fancyZonesData = JSONHelpers::FancyZonesDataInstance(); + + std::wstring serializedPowerToySettings(const Settings& settings) + { + PowerToysSettings::Settings ptSettings(HINSTANCE{}, L"FancyZonesUnitTests"); + + ptSettings.add_hotkey(L"fancyzones_editor_hotkey", IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL, settings.editorHotkey); + ptSettings.add_bool_toogle(L"fancyzones_shiftDrag", IDS_SETTING_DESCRIPTION_SHIFTDRAG, settings.shiftDrag); + ptSettings.add_bool_toogle(L"fancyzones_overrideSnapHotkeys", IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS, settings.overrideSnapHotkeys); + ptSettings.add_bool_toogle(L"fancyzones_zoneSetChange_flashZones", IDS_SETTING_DESCRIPTION_ZONESETCHANGE_FLASHZONES, settings.zoneSetChange_flashZones); + ptSettings.add_bool_toogle(L"fancyzones_displayChange_moveWindows", IDS_SETTING_DESCRIPTION_DISPLAYCHANGE_MOVEWINDOWS, settings.displayChange_moveWindows); + ptSettings.add_bool_toogle(L"fancyzones_zoneSetChange_moveWindows", IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS, settings.zoneSetChange_moveWindows); + ptSettings.add_bool_toogle(L"fancyzones_virtualDesktopChange_moveWindows", IDS_SETTING_DESCRIPTION_VIRTUALDESKTOPCHANGE_MOVEWINDOWS, settings.virtualDesktopChange_moveWindows); + ptSettings.add_bool_toogle(L"fancyzones_appLastZone_moveWindows", IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS, settings.appLastZone_moveWindows); + ptSettings.add_bool_toogle(L"use_cursorpos_editor_startupscreen", IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN, settings.use_cursorpos_editor_startupscreen); + ptSettings.add_int_spinner(L"fancyzones_highlight_opacity", IDS_SETTINGS_HIGHLIGHT_OPACITY, settings.zoneHighlightOpacity, 0, 100, 1); + ptSettings.add_color_picker(L"fancyzones_zoneHighlightColor", IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR, settings.zoneHightlightColor); + ptSettings.add_multiline_string(L"fancyzones_excluded_apps", IDS_SETTING_EXCLCUDED_APPS_DESCRIPTION, settings.excludedApps); + + return ptSettings.serialize(); + } + + void sendKeyboardInput(WORD code, bool release = false) + { + INPUT ip; + ip.type = INPUT_KEYBOARD; + ip.ki.wScan = 0; // hardware scan code for key + ip.ki.time = 0; + ip.ki.dwExtraInfo = 0; + ip.ki.wVk = code; + ip.ki.dwFlags = release ? KEYEVENTF_KEYUP : 0; + SendInput(1, &ip, sizeof(INPUT)); + } + + TEST_METHOD_INITIALIZE(Init) + { + m_hInst = (HINSTANCE)GetModuleHandleW(nullptr); + m_settings = MakeFancyZonesSettings(m_hInst, L"FancyZonesUnitTests"); + Assert::IsTrue(m_settings != nullptr); + + auto fancyZones = MakeFancyZones(m_hInst, m_settings); + Assert::IsTrue(fancyZones != nullptr); + + m_fzCallback = fancyZones.as(); + Assert::IsTrue(m_fzCallback != nullptr); + + m_fancyZonesData = JSONHelpers::FancyZonesData(); + } + + TEST_METHOD_CLEANUP(Cleanup) + { + sendKeyboardInput(VK_SHIFT, true); + sendKeyboardInput(VK_LWIN, true); + sendKeyboardInput(VK_CONTROL, true); + + const auto settingsFile = PTSettingsHelper::get_module_save_folder_location(L"FancyZonesUnitTests") + L"\\settings.json"; + std::filesystem::remove(settingsFile); + } + + TEST_METHOD(InMoveSizeTest) + { + Assert::IsFalse(m_fzCallback->InMoveSize()); + + m_fzCallback->MoveSizeStart(Mocks::Window(), Mocks::Monitor(), POINT{ 0, 0 }); + Assert::IsFalse(m_fzCallback->InMoveSize()); //point outside of window rect + + const auto window = Mocks::WindowCreate(m_hInst); + const int paddingX = 8, paddingY = 6; + RECT windowRect{}; + ::GetWindowRect(window, &windowRect); + m_fzCallback->MoveSizeStart(window, Mocks::Monitor(), POINT{ windowRect.left + paddingX, windowRect.top + paddingY }); + Assert::IsTrue(m_fzCallback->InMoveSize()); + + m_fzCallback->MoveSizeEnd(Mocks::Window(), POINT{ 0, 0 }); + Assert::IsFalse(m_fzCallback->InMoveSize()); + } + + TEST_METHOD(MoveSizeEndAppLastZoneTest) + { + const auto window = Mocks::WindowCreate(m_hInst); + const auto processPath = get_process_path(window); + + Assert::AreEqual(-1, m_fancyZonesData.GetAppLastZone(window, processPath.c_str())); + + m_fzCallback->MoveSizeEnd(window, POINT{ 0, 0 }); + Assert::AreEqual(-1, m_fancyZonesData.GetAppLastZone(window, processPath.c_str())); + + m_fancyZonesData.SetAppLastZone(window, processPath.c_str(), 1); + Assert::AreEqual(1, m_fancyZonesData.GetAppLastZone(window, processPath.c_str())); + + m_fzCallback->MoveSizeEnd(window, POINT{ 0, 0 }); + Assert::AreEqual(-1, m_fancyZonesData.GetAppLastZone(window, processPath.c_str())); + } + + TEST_METHOD(OnKeyDownNothingPressed) + { + for (DWORD code = '0'; code <= '9'; code++) + { + tagKBDLLHOOKSTRUCT input{}; + input.vkCode = code; + Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); + } + + { + tagKBDLLHOOKSTRUCT input{}; + input.vkCode = VK_LEFT; + Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); + } + + { + tagKBDLLHOOKSTRUCT input{}; + input.vkCode = VK_RIGHT; + Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); + } + } + + TEST_METHOD(OnKeyDownShiftPressed) + { + sendKeyboardInput(VK_SHIFT); + + for (DWORD code = '0'; code <= '9'; code++) + { + tagKBDLLHOOKSTRUCT input{}; + input.vkCode = code; + Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); + } + + { + tagKBDLLHOOKSTRUCT input{}; + input.vkCode = VK_LEFT; + Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); + } + + { + tagKBDLLHOOKSTRUCT input{}; + input.vkCode = VK_RIGHT; + Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); + } + } + + TEST_METHOD(OnKeyDownWinPressed) + { + sendKeyboardInput(VK_LWIN); + + for (DWORD code = '0'; code <= '9'; code++) + { + tagKBDLLHOOKSTRUCT input{}; + input.vkCode = code; + Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); + } + + { + tagKBDLLHOOKSTRUCT input{}; + input.vkCode = VK_LEFT; + Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); + } + + { + tagKBDLLHOOKSTRUCT input{}; + input.vkCode = VK_RIGHT; + Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); + } + } + + TEST_METHOD(OnKeyDownWinShiftPressed) + { + sendKeyboardInput(VK_LWIN); + sendKeyboardInput(VK_SHIFT); + + for (DWORD code = '0'; code <= '9'; code++) + { + tagKBDLLHOOKSTRUCT input{}; + input.vkCode = code; + Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); + } + + { + tagKBDLLHOOKSTRUCT input{}; + input.vkCode = VK_LEFT; + Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); + } + + { + tagKBDLLHOOKSTRUCT input{}; + input.vkCode = VK_RIGHT; + Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); + } + } + + TEST_METHOD(OnKeyDownWinCtrlPressed) + { + sendKeyboardInput(VK_LWIN); + sendKeyboardInput(VK_CONTROL); + + const Settings settings{ + .overrideSnapHotkeys = false, + }; + + auto config = serializedPowerToySettings(settings); + m_settings->SetConfig(config.c_str()); + + for (DWORD code = '0'; code <= '9'; code++) + { + tagKBDLLHOOKSTRUCT input{}; + input.vkCode = code; + Assert::IsTrue(m_fzCallback->OnKeyDown(&input)); + } + + { + tagKBDLLHOOKSTRUCT input{}; + input.vkCode = VK_LEFT; + Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); + } + + { + tagKBDLLHOOKSTRUCT input{}; + input.vkCode = VK_RIGHT; + Assert::IsFalse(m_fzCallback->OnKeyDown(&input)); + } + } + + TEST_METHOD(OnKeyDownWinPressedOverride) + { + sendKeyboardInput(VK_LWIN); + + const Settings settings{ + .overrideSnapHotkeys = true, + }; + + auto config = serializedPowerToySettings(settings); + m_settings->SetConfig(config.c_str()); + + { + tagKBDLLHOOKSTRUCT input{}; + input.vkCode = VK_LEFT; + Assert::IsTrue(m_fzCallback->OnKeyDown(&input)); + } + + { + tagKBDLLHOOKSTRUCT input{}; + input.vkCode = VK_RIGHT; + Assert::IsTrue(m_fzCallback->OnKeyDown(&input)); + } + } + }; +} \ No newline at end of file diff --git a/src/modules/fancyzones/tests/UnitTests/FancyZonesSettings.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/FancyZonesSettings.Spec.cpp new file mode 100644 index 000000000000..543ea77d4645 --- /dev/null +++ b/src/modules/fancyzones/tests/UnitTests/FancyZonesSettings.Spec.cpp @@ -0,0 +1,734 @@ +#include "pch.h" +#include + +#include +#include +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +namespace FancyZonesUnitTests +{ + void compareHotkeyObjects(const PowerToysSettings::HotkeyObject& expected, const PowerToysSettings::HotkeyObject& actual) + { + Assert::AreEqual(expected.alt_pressed(), actual.alt_pressed()); + Assert::AreEqual(expected.ctrl_pressed(), actual.ctrl_pressed()); + Assert::AreEqual(expected.shift_pressed(), actual.shift_pressed()); + Assert::AreEqual(expected.win_pressed(), actual.win_pressed()); + + //NOTE: key_from_code may create different values + //Assert::AreEqual(expected.get_key(), actual.get_key()); + Assert::AreEqual(expected.get_code(), actual.get_code()); + Assert::AreEqual(expected.get_modifiers(), actual.get_modifiers()); + Assert::AreEqual(expected.get_modifiers_repeat(), actual.get_modifiers_repeat()); + } + + void compareSettings(const Settings& expected, const Settings& actual) + { + Assert::AreEqual(expected.shiftDrag, actual.shiftDrag); + Assert::AreEqual(expected.displayChange_moveWindows, actual.displayChange_moveWindows); + Assert::AreEqual(expected.virtualDesktopChange_moveWindows, actual.virtualDesktopChange_moveWindows); + Assert::AreEqual(expected.zoneSetChange_flashZones, actual.zoneSetChange_flashZones); + Assert::AreEqual(expected.zoneSetChange_moveWindows, actual.zoneSetChange_moveWindows); + Assert::AreEqual(expected.overrideSnapHotkeys, actual.overrideSnapHotkeys); + Assert::AreEqual(expected.appLastZone_moveWindows, actual.appLastZone_moveWindows); + Assert::AreEqual(expected.use_cursorpos_editor_startupscreen, actual.use_cursorpos_editor_startupscreen); + Assert::AreEqual(expected.zoneHightlightColor.c_str(), actual.zoneHightlightColor.c_str()); + Assert::AreEqual(expected.zoneHighlightOpacity, actual.zoneHighlightOpacity); + Assert::AreEqual(expected.excludedApps.c_str(), actual.excludedApps.c_str()); + Assert::AreEqual(expected.excludedAppsArray.size(), actual.excludedAppsArray.size()); + for (int i = 0; i < expected.excludedAppsArray.size(); i++) + { + Assert::AreEqual(expected.excludedAppsArray[i].c_str(), actual.excludedAppsArray[i].c_str()); + } + + compareHotkeyObjects(expected.editorHotkey, actual.editorHotkey); + } + + TEST_CLASS(FancyZonesSettingsCreationUnitTest) + { + HINSTANCE m_hInst; + PCWSTR m_moduleName = L"FancyZonesTest"; + std::wstring m_tmpName; + + const PowerToysSettings::HotkeyObject m_defaultHotkeyObject = PowerToysSettings::HotkeyObject::from_settings(true, false, false, false, VK_OEM_3); + const Settings m_defaultSettings; + + TEST_METHOD_INITIALIZE(Init) + { + m_hInst = (HINSTANCE)GetModuleHandleW(nullptr); + m_tmpName = PTSettingsHelper::get_module_save_folder_location(m_moduleName) + L"\\settings.json"; + } + + TEST_METHOD_CLEANUP(Cleanup) + { + std::filesystem::remove(m_tmpName); + } + + TEST_METHOD(CreateWithHinstanceDefault) + { + auto actual = MakeFancyZonesSettings({}, m_moduleName); + Assert::IsTrue(actual != nullptr); + + auto actualSettings = actual->GetSettings(); + compareSettings(m_defaultSettings, actualSettings); + } + + TEST_METHOD(CreateWithHinstanceNullptr) + { + auto actual = MakeFancyZonesSettings(nullptr, m_moduleName); + Assert::IsTrue(actual != nullptr); + + auto actualSettings = actual->GetSettings(); + compareSettings(m_defaultSettings, actualSettings); + } + + TEST_METHOD(CreateWithNameEmpty) + { + auto actual = MakeFancyZonesSettings(m_hInst, L""); + Assert::IsTrue(actual != nullptr); + + auto actualSettings = actual->GetSettings(); + compareSettings(m_defaultSettings, actualSettings); + } + + TEST_METHOD(CreateWithNameNullptr) + { + auto actual = MakeFancyZonesSettings(m_hInst, nullptr); + Assert::IsTrue(actual == nullptr); + } + + TEST_METHOD(Create) + { + //prepare data + const Settings expected { + .shiftDrag = false, + .displayChange_moveWindows = true, + .virtualDesktopChange_moveWindows = true, + .zoneSetChange_flashZones = true, + .zoneSetChange_moveWindows = true, + .overrideSnapHotkeys = false, + .appLastZone_moveWindows = false, + .use_cursorpos_editor_startupscreen = true, + .zoneHightlightColor = L"#00FFD7", + .zoneHighlightOpacity = 45, + .editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_OEM_3), + .excludedApps = L"app", + .excludedAppsArray = { L"APP" }, + }; + + PowerToysSettings::PowerToyValues values(m_moduleName); + values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag); + values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows); + values.add_property(L"fancyzones_virtualDesktopChange_moveWindows", expected.virtualDesktopChange_moveWindows); + values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones); + values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows); + values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys); + values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows); + values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen); + values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHightlightColor); + values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity); + values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json()); + values.add_property(L"fancyzones_excluded_apps", expected.excludedApps); + + values.save_to_settings_file(); + + auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName); + Assert::IsTrue(actual != nullptr); + + auto actualSettings = actual->GetSettings(); + compareSettings(expected, actualSettings); + } + + TEST_METHOD(CreateWithMultipleApps) + { + //prepare data + const Settings expected { + .shiftDrag = false, + .displayChange_moveWindows = true, + .virtualDesktopChange_moveWindows = true, + .zoneSetChange_flashZones = true, + .zoneSetChange_moveWindows = true, + .overrideSnapHotkeys = false, + .appLastZone_moveWindows = false, + .use_cursorpos_editor_startupscreen = true, + .zoneHightlightColor = L"#00FFD7", + .zoneHighlightOpacity = 45, + .editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_OEM_3), + .excludedApps = L"app\r\napp1\r\napp2\r\nanother app", + .excludedAppsArray = { L"APP", L"APP1", L"APP2", L"ANOTHER APP" }, + }; + + PowerToysSettings::PowerToyValues values(m_moduleName); + values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag); + values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows); + values.add_property(L"fancyzones_virtualDesktopChange_moveWindows", expected.virtualDesktopChange_moveWindows); + values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones); + values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows); + values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys); + values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows); + values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen); + values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHightlightColor); + values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity); + values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json()); + values.add_property(L"fancyzones_excluded_apps", expected.excludedApps); + + values.save_to_settings_file(); + + auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName); + Assert::IsTrue(actual != nullptr); + + auto actualSettings = actual->GetSettings(); + compareSettings(expected, actualSettings); + } + + TEST_METHOD(CreateWithBoolValuesMissed) + { + const Settings expected { + .shiftDrag = m_defaultSettings.shiftDrag, + .displayChange_moveWindows = m_defaultSettings.displayChange_moveWindows, + .virtualDesktopChange_moveWindows = m_defaultSettings.virtualDesktopChange_moveWindows, + .zoneSetChange_flashZones = m_defaultSettings.zoneSetChange_flashZones, + .zoneSetChange_moveWindows = m_defaultSettings.zoneSetChange_moveWindows, + .overrideSnapHotkeys = m_defaultSettings.overrideSnapHotkeys, + .appLastZone_moveWindows = m_defaultSettings.appLastZone_moveWindows, + .use_cursorpos_editor_startupscreen = m_defaultSettings.use_cursorpos_editor_startupscreen, + .zoneHightlightColor = L"#00FFD7", + .zoneHighlightOpacity = 45, + .editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_OEM_3), + .excludedApps = L"app", + .excludedAppsArray = { L"APP" }, + }; + + PowerToysSettings::PowerToyValues values(m_moduleName); + values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHightlightColor); + values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity); + values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json()); + values.add_property(L"fancyzones_excluded_apps", expected.excludedApps); + + values.save_to_settings_file(); + + auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName); + Assert::IsTrue(actual != nullptr); + + auto actualSettings = actual->GetSettings(); + compareSettings(expected, actualSettings); + } + + TEST_METHOD(CreateColorMissed) + { + //prepare data + const Settings expected { + .shiftDrag = false, + .displayChange_moveWindows = true, + .virtualDesktopChange_moveWindows = true, + .zoneSetChange_flashZones = true, + .zoneSetChange_moveWindows = true, + .overrideSnapHotkeys = false, + .appLastZone_moveWindows = false, + .use_cursorpos_editor_startupscreen = true, + .zoneHightlightColor = m_defaultSettings.zoneHightlightColor, + .zoneHighlightOpacity = 45, + .editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_OEM_3), + .excludedApps = L"app", + .excludedAppsArray = { L"APP" }, + }; + + PowerToysSettings::PowerToyValues values(m_moduleName); + values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag); + values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows); + values.add_property(L"fancyzones_virtualDesktopChange_moveWindows", expected.virtualDesktopChange_moveWindows); + values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones); + values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows); + values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys); + values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows); + values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen); + values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity); + values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json()); + values.add_property(L"fancyzones_excluded_apps", expected.excludedApps); + + values.save_to_settings_file(); + + auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName); + Assert::IsTrue(actual != nullptr); + + auto actualSettings = actual->GetSettings(); + compareSettings(expected, actualSettings); + } + + TEST_METHOD(CreateOpacityMissed) + { + //prepare data + const Settings expected { + .shiftDrag = false, + .displayChange_moveWindows = true, + .virtualDesktopChange_moveWindows = true, + .zoneSetChange_flashZones = true, + .zoneSetChange_moveWindows = true, + .overrideSnapHotkeys = false, + .appLastZone_moveWindows = false, + .use_cursorpos_editor_startupscreen = true, + .zoneHightlightColor = L"#00FFD7", + .zoneHighlightOpacity = m_defaultSettings.zoneHighlightOpacity, + .editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_OEM_3), + .excludedApps = L"app", + .excludedAppsArray = { L"APP" }, + }; + + PowerToysSettings::PowerToyValues values(m_moduleName); + values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag); + values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows); + values.add_property(L"fancyzones_virtualDesktopChange_moveWindows", expected.virtualDesktopChange_moveWindows); + values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones); + values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows); + values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys); + values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows); + values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen); + values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHightlightColor); + values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json()); + values.add_property(L"fancyzones_excluded_apps", expected.excludedApps); + + values.save_to_settings_file(); + + auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName); + Assert::IsTrue(actual != nullptr); + + auto actualSettings = actual->GetSettings(); + compareSettings(expected, actualSettings); + } + + TEST_METHOD(CreateHotkeyMissed) + { + //prepare data + const Settings expected = Settings{ + .shiftDrag = false, + .displayChange_moveWindows = true, + .virtualDesktopChange_moveWindows = true, + .zoneSetChange_flashZones = true, + .zoneSetChange_moveWindows = true, + .overrideSnapHotkeys = false, + .appLastZone_moveWindows = false, + .use_cursorpos_editor_startupscreen = true, + .zoneHightlightColor = L"#00FFD7", + .zoneHighlightOpacity = 45, + .editorHotkey = m_defaultSettings.editorHotkey, + .excludedApps = L"app", + .excludedAppsArray = { L"APP" }, + }; + + PowerToysSettings::PowerToyValues values(m_moduleName); + values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag); + values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows); + values.add_property(L"fancyzones_virtualDesktopChange_moveWindows", expected.virtualDesktopChange_moveWindows); + values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones); + values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows); + values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys); + values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows); + values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen); + values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHightlightColor); + values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity); + values.add_property(L"fancyzones_excluded_apps", expected.excludedApps); + + values.save_to_settings_file(); + + auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName); + Assert::IsTrue(actual != nullptr); + + auto actualSettings = actual->GetSettings(); + compareSettings(expected, actualSettings); + } + + TEST_METHOD(CreateAppsMissed) + { + //prepare data + const Settings expected = Settings{ + .shiftDrag = false, + .displayChange_moveWindows = true, + .virtualDesktopChange_moveWindows = true, + .zoneSetChange_flashZones = true, + .zoneSetChange_moveWindows = true, + .overrideSnapHotkeys = false, + .appLastZone_moveWindows = false, + .use_cursorpos_editor_startupscreen = true, + .zoneHightlightColor = L"#00FFD7", + .zoneHighlightOpacity = 45, + .editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_OEM_3), + .excludedApps = m_defaultSettings.excludedApps, + .excludedAppsArray = m_defaultSettings.excludedAppsArray, + }; + + PowerToysSettings::PowerToyValues values(m_moduleName); + values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag); + values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows); + values.add_property(L"fancyzones_virtualDesktopChange_moveWindows", expected.virtualDesktopChange_moveWindows); + values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones); + values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows); + values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys); + values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows); + values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen); + values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHightlightColor); + values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity); + values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json()); + + values.save_to_settings_file(); + + auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName); + Assert::IsTrue(actual != nullptr); + + auto actualSettings = actual->GetSettings(); + compareSettings(expected, actualSettings); + } + + TEST_METHOD(CreateWithEmptyJson) + { + json::to_file(m_tmpName, json::JsonObject()); + auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName); + Assert::IsTrue(actual != nullptr); + + auto actualSettings = actual->GetSettings(); + compareSettings(m_defaultSettings, actualSettings); + } + }; + + TEST_CLASS(FancyZonesSettingsCallbackUnitTests) + { + winrt::com_ptr m_settings = nullptr; + PCWSTR m_moduleName = L"FancyZonesTest"; + + struct FZCallback : public winrt::implements + { + public: + FZCallback(bool* callFlag) : + m_callFlag(callFlag) + { + *m_callFlag = false; + } + + IFACEMETHODIMP_(bool) InMoveSize() noexcept { return false; } + IFACEMETHODIMP_(void) MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen) noexcept {} + IFACEMETHODIMP_(void) MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen) noexcept {} + IFACEMETHODIMP_(void) MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept {} + IFACEMETHODIMP_(void) VirtualDesktopChanged() noexcept {} + IFACEMETHODIMP_(void) VirtualDesktopInitialize() noexcept {} + IFACEMETHODIMP_(void) WindowCreated(HWND window) noexcept {} + IFACEMETHODIMP_(bool) OnKeyDown(PKBDLLHOOKSTRUCT info) noexcept { return false; } + + IFACEMETHODIMP_(void) ToggleEditor() noexcept + { + Assert::IsNotNull(m_callFlag); + *m_callFlag = true; + } + + IFACEMETHODIMP_(void) SettingsChanged() noexcept + { + Assert::IsNotNull(m_callFlag); + *m_callFlag = true; + } + + private: + bool* m_callFlag = nullptr; + }; + + TEST_METHOD_INITIALIZE(Init) + { + HINSTANCE hInst = (HINSTANCE)GetModuleHandleW(nullptr); + const Settings expected{ + .shiftDrag = false, + .displayChange_moveWindows = true, + .virtualDesktopChange_moveWindows = true, + .zoneSetChange_flashZones = true, + .zoneSetChange_moveWindows = true, + .overrideSnapHotkeys = false, + .appLastZone_moveWindows = false, + .use_cursorpos_editor_startupscreen = true, + .zoneHightlightColor = L"#00FFD7", + .zoneHighlightOpacity = 45, + .editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_OEM_3), + .excludedApps = L"app", + .excludedAppsArray = { L"APP" }, + }; + + PowerToysSettings::PowerToyValues values(m_moduleName); + values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag); + values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows); + values.add_property(L"fancyzones_virtualDesktopChange_moveWindows", expected.virtualDesktopChange_moveWindows); + values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones); + values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows); + values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys); + values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows); + values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen); + values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHightlightColor); + values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity); + values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json()); + values.add_property(L"fancyzones_excluded_apps", expected.excludedApps); + + values.save_to_settings_file(); + + m_settings = MakeFancyZonesSettings(hInst, m_moduleName); + Assert::IsTrue(m_settings != nullptr); + } + + TEST_METHOD_CLEANUP(Cleanup) + { + const auto settingsFile = PTSettingsHelper::get_module_save_folder_location(m_moduleName) + L"\\settings.json"; + std::filesystem::remove(settingsFile); + } + + TEST_METHOD(CallbackSetConfig) + { + bool flag = false; + FZCallback callback(&flag); + + json::JsonObject json{}; + json.SetNamedValue(L"name", json::JsonValue::CreateStringValue(L"name")); + + m_settings->SetCallback(&callback); + m_settings->SetConfig(json.Stringify().c_str()); + + Assert::IsTrue(flag); + } + + TEST_METHOD(CallbackCallCustomAction) + { + bool flag = false; + FZCallback callback(&flag); + + json::JsonObject action{}; + action.SetNamedValue(L"action_name", json::JsonValue::CreateStringValue(L"ToggledFZEditor")); + + m_settings->SetCallback(&callback); + m_settings->CallCustomAction(action.Stringify().c_str()); + + Assert::IsTrue(flag); + } + + TEST_METHOD(CallbackCallCustomActionNotToggle) + { + bool flag = false; + FZCallback callback(&flag); + + json::JsonObject action{}; + action.SetNamedValue(L"action_name", json::JsonValue::CreateStringValue(L"NOT_ToggledFZEditor")); + + m_settings->SetCallback(&callback); + m_settings->CallCustomAction(action.Stringify().c_str()); + + Assert::IsFalse(flag); + } + + TEST_METHOD(CallbackGetConfig) + { + bool flag = false; + FZCallback callback(&flag); + + m_settings->SetCallback(&callback); + + int bufSize = 0; + m_settings->GetConfig(L"", &bufSize); + + Assert::IsFalse(flag); + } + + TEST_METHOD(CallbackGetSettings) + { + bool flag = false; + FZCallback callback(&flag); + + m_settings->SetCallback(&callback); + m_settings->GetSettings(); + + Assert::IsFalse(flag); + } + }; + + TEST_CLASS(FancyZonesSettingsUnitTests) + { + winrt::com_ptr m_settings = nullptr; + PowerToysSettings::Settings* m_ptSettings = nullptr; + PCWSTR m_moduleName = L"FancyZonesTest"; + + std::wstring serializedPowerToySettings(const Settings& settings) + { + PowerToysSettings::Settings ptSettings(HINSTANCE{}, m_moduleName); + ptSettings.set_description(IDS_SETTING_DESCRIPTION); + ptSettings.set_icon_key(L"pt-fancy-zones"); + ptSettings.set_overview_link(L"https://github.com/microsoft/PowerToys/blob/master/src/modules/fancyzones/README.md"); + ptSettings.set_video_link(L"https://youtu.be/rTtGzZYAXgY"); + + ptSettings.add_custom_action( + L"ToggledFZEditor", // action name. + IDS_SETTING_LAUNCH_EDITOR_LABEL, + IDS_SETTING_LAUNCH_EDITOR_BUTTON, + IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION); + ptSettings.add_hotkey(L"fancyzones_editor_hotkey", IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL, settings.editorHotkey); + ptSettings.add_bool_toogle(L"fancyzones_shiftDrag", IDS_SETTING_DESCRIPTION_SHIFTDRAG, settings.shiftDrag); + ptSettings.add_bool_toogle(L"fancyzones_overrideSnapHotkeys", IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS, settings.overrideSnapHotkeys); + ptSettings.add_bool_toogle(L"fancyzones_zoneSetChange_flashZones", IDS_SETTING_DESCRIPTION_ZONESETCHANGE_FLASHZONES, settings.zoneSetChange_flashZones); + ptSettings.add_bool_toogle(L"fancyzones_displayChange_moveWindows", IDS_SETTING_DESCRIPTION_DISPLAYCHANGE_MOVEWINDOWS, settings.displayChange_moveWindows); + ptSettings.add_bool_toogle(L"fancyzones_zoneSetChange_moveWindows", IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS, settings.zoneSetChange_moveWindows); + ptSettings.add_bool_toogle(L"fancyzones_virtualDesktopChange_moveWindows", IDS_SETTING_DESCRIPTION_VIRTUALDESKTOPCHANGE_MOVEWINDOWS, settings.virtualDesktopChange_moveWindows); + ptSettings.add_bool_toogle(L"fancyzones_appLastZone_moveWindows", IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS, settings.appLastZone_moveWindows); + ptSettings.add_bool_toogle(L"use_cursorpos_editor_startupscreen", IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN, settings.use_cursorpos_editor_startupscreen); + ptSettings.add_int_spinner(L"fancyzones_highlight_opacity", IDS_SETTINGS_HIGHLIGHT_OPACITY, settings.zoneHighlightOpacity, 0, 100, 1); + ptSettings.add_color_picker(L"fancyzones_zoneHighlightColor", IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR, settings.zoneHightlightColor); + ptSettings.add_multiline_string(L"fancyzones_excluded_apps", IDS_SETTING_EXCLCUDED_APPS_DESCRIPTION, settings.excludedApps); + + return ptSettings.serialize(); + } + + TEST_METHOD_INITIALIZE(Init) + { + HINSTANCE hInst = (HINSTANCE)GetModuleHandleW(nullptr); + + //init m_settings + const Settings expected{ + .shiftDrag = false, + .displayChange_moveWindows = true, + .virtualDesktopChange_moveWindows = true, + .zoneSetChange_flashZones = true, + .zoneSetChange_moveWindows = true, + .overrideSnapHotkeys = false, + .appLastZone_moveWindows = false, + .use_cursorpos_editor_startupscreen = true, + .zoneHightlightColor = L"#00FFD7", + .zoneHighlightOpacity = 45, + .editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_OEM_3), + .excludedApps = L"app", + .excludedAppsArray = { L"APP" }, + }; + + PowerToysSettings::PowerToyValues values(m_moduleName); + values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag); + values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows); + values.add_property(L"fancyzones_virtualDesktopChange_moveWindows", expected.virtualDesktopChange_moveWindows); + values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones); + values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows); + values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys); + values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows); + values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen); + values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHightlightColor); + values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity); + values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json()); + values.add_property(L"fancyzones_excluded_apps", expected.excludedApps); + + values.save_to_settings_file(); + + m_settings = MakeFancyZonesSettings(hInst, m_moduleName); + Assert::IsTrue(m_settings != nullptr); + + //init m_ptSettings + m_ptSettings = new PowerToysSettings::Settings(hInst, m_moduleName); + m_ptSettings->set_description(IDS_SETTING_DESCRIPTION); + m_ptSettings->set_icon_key(L"pt-fancy-zones"); + m_ptSettings->set_overview_link(L"https://github.com/microsoft/PowerToys/blob/master/src/modules/fancyzones/README.md"); + m_ptSettings->set_video_link(L"https://youtu.be/rTtGzZYAXgY"); + + m_ptSettings->add_custom_action( + L"ToggledFZEditor", // action name. + IDS_SETTING_LAUNCH_EDITOR_LABEL, + IDS_SETTING_LAUNCH_EDITOR_BUTTON, + IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION); + m_ptSettings->add_hotkey(L"fancyzones_editor_hotkey", IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL, expected.editorHotkey); + m_ptSettings->add_bool_toogle(L"fancyzones_shiftDrag", IDS_SETTING_DESCRIPTION_SHIFTDRAG, expected.shiftDrag); + m_ptSettings->add_bool_toogle(L"fancyzones_overrideSnapHotkeys", IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS, expected.overrideSnapHotkeys); + m_ptSettings->add_bool_toogle(L"fancyzones_zoneSetChange_flashZones", IDS_SETTING_DESCRIPTION_ZONESETCHANGE_FLASHZONES, expected.zoneSetChange_flashZones); + m_ptSettings->add_bool_toogle(L"fancyzones_displayChange_moveWindows", IDS_SETTING_DESCRIPTION_DISPLAYCHANGE_MOVEWINDOWS, expected.displayChange_moveWindows); + m_ptSettings->add_bool_toogle(L"fancyzones_zoneSetChange_moveWindows", IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS, expected.zoneSetChange_moveWindows); + m_ptSettings->add_bool_toogle(L"fancyzones_virtualDesktopChange_moveWindows", IDS_SETTING_DESCRIPTION_VIRTUALDESKTOPCHANGE_MOVEWINDOWS, expected.virtualDesktopChange_moveWindows); + m_ptSettings->add_bool_toogle(L"fancyzones_appLastZone_moveWindows", IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS, expected.appLastZone_moveWindows); + m_ptSettings->add_bool_toogle(L"use_cursorpos_editor_startupscreen", IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN, expected.use_cursorpos_editor_startupscreen); + m_ptSettings->add_int_spinner(L"fancyzones_highlight_opacity", IDS_SETTINGS_HIGHLIGHT_OPACITY, expected.zoneHighlightOpacity, 0, 100, 1); + m_ptSettings->add_color_picker(L"fancyzones_zoneHighlightColor", IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR, expected.zoneHightlightColor); + m_ptSettings->add_multiline_string(L"fancyzones_excluded_apps", IDS_SETTING_EXCLCUDED_APPS_DESCRIPTION, expected.excludedApps); + } + + TEST_METHOD_CLEANUP(Cleanup) + { + const auto settingsFile = PTSettingsHelper::get_module_save_folder_location(m_moduleName) + L"\\settings.json"; + std::filesystem::remove(settingsFile); + } + + TEST_METHOD(GetConfig) + { + const int expectedSize = m_ptSettings->serialize().size() + 1; + + int actualBufferSize = expectedSize; + PWSTR actualBuffer = new wchar_t[actualBufferSize]; + + Assert::IsTrue(m_settings->GetConfig(actualBuffer, &actualBufferSize)); + Assert::AreEqual(expectedSize, actualBufferSize); + + Assert::AreEqual(m_ptSettings->serialize().c_str(), actualBuffer); + } + + TEST_METHOD(GetConfigSmallBuffer) + { + const auto serialized = m_ptSettings->serialize(); + const int expectedSize = serialized.size() + 1; + + int actualBufferSize = m_ptSettings->serialize().size() - 1; + PWSTR actualBuffer = new wchar_t[actualBufferSize]; + + Assert::IsFalse(m_settings->GetConfig(actualBuffer, &actualBufferSize)); + Assert::AreEqual(expectedSize, actualBufferSize); + Assert::AreNotEqual(serialized.c_str(), actualBuffer); + } + + TEST_METHOD(GetConfigNullBuffer) + { + const auto serialized = m_ptSettings->serialize(); + const int expectedSize = serialized.size() + 1; + + int actualBufferSize = 0; + PWSTR actualBuffer = nullptr; + + Assert::IsFalse(m_settings->GetConfig(actualBuffer, &actualBufferSize)); + Assert::AreEqual(expectedSize, actualBufferSize); + } + + TEST_METHOD(SetConfig) + { + //cleanup file before call set config + const auto settingsFile = PTSettingsHelper::get_module_save_folder_location(m_moduleName) + L"\\settings.json"; + std::filesystem::remove(settingsFile); + + const Settings expected { + .shiftDrag = true, + .displayChange_moveWindows = true, + .virtualDesktopChange_moveWindows = true, + .zoneSetChange_flashZones = false, + .zoneSetChange_moveWindows = true, + .overrideSnapHotkeys = false, + .appLastZone_moveWindows = true, + .use_cursorpos_editor_startupscreen = true, + .zoneHightlightColor = L"#00AABB", + .zoneHighlightOpacity = 45, + .editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3), + .excludedApps = L"app\r\napp2", + .excludedAppsArray = { L"APP", L"APP2" }, + }; + + auto config = serializedPowerToySettings(expected); + m_settings->SetConfig(config.c_str()); + + auto actual = m_settings->GetSettings(); + compareSettings(expected, actual); + + Assert::IsTrue(std::filesystem::exists(settingsFile)); + } + + TEST_METHOD(SetConfigNullptr) + { + //cleanup file before call set config + const auto settingsFile = PTSettingsHelper::get_module_save_folder_location(m_moduleName) + L"\\settings.json"; + std::filesystem::remove(settingsFile); + + const auto expected = m_settings->GetSettings(); + m_settings->SetConfig(nullptr); + + auto actual = m_settings->GetSettings(); + compareSettings(expected, actual); + Assert::IsTrue(std::filesystem::exists(settingsFile)); + } + }; +} \ No newline at end of file diff --git a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp new file mode 100644 index 000000000000..5d06d8a079ab --- /dev/null +++ b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp @@ -0,0 +1,1487 @@ +#include "pch.h" +#include + +#include + +#include + +using namespace JSONHelpers; +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +namespace FancyZonesUnitTests +{ + void compareJsonObjects(const json::JsonObject& expected, const json::JsonObject& actual, bool recursive = true) + { + auto iter = expected.First(); + while (iter.HasCurrent()) + { + const auto key = iter.Current().Key(); + Assert::IsTrue(actual.HasKey(key), key.c_str()); + + const std::wstring expectedStringified = iter.Current().Value().Stringify().c_str(); + const std::wstring actualStringified = actual.GetNamedValue(key).Stringify().c_str(); + + if (recursive) + { + json::JsonObject expectedJson; + if (json::JsonObject::TryParse(expectedStringified, expectedJson)) + { + json::JsonObject actualJson; + if (json::JsonObject::TryParse(actualStringified, actualJson)) + { + compareJsonObjects(expectedJson, actualJson, true); + } + else + { + Assert::IsTrue(false, key.c_str()); + } + } + else + { + Assert::AreEqual(expectedStringified, actualStringified, key.c_str()); + } + } + else + { + Assert::AreEqual(expectedStringified, actualStringified, key.c_str()); + } + + iter.MoveNext(); + } + } + + TEST_CLASS(ZoneSetLayoutTypeUnitTest) + { + TEST_METHOD(ZoneSetLayoutTypeToString){ + std::map expectedMap = { + std::make_pair(-1, L"TypeToString_ERROR"), + std::make_pair(0, L"focus"), + std::make_pair(1, L"columns"), + std::make_pair(2, L"rows"), + std::make_pair(3, L"grid"), + std::make_pair(4, L"priority-grid"), + std::make_pair(5, L"custom"), + std::make_pair(6, L"TypeToString_ERROR"), + }; + + for (const auto& expected : expectedMap) + { + auto actual = JSONHelpers::TypeToString(static_cast(expected.first)); + Assert::AreEqual(expected.second, actual); + } + } + + TEST_METHOD(ZoneSetLayoutTypeFromString) + { + std::map expectedMap = { + std::make_pair(ZoneSetLayoutType::Focus, L"focus"), + std::make_pair(ZoneSetLayoutType::Columns, L"columns"), + std::make_pair(ZoneSetLayoutType::Rows, L"rows"), + std::make_pair(ZoneSetLayoutType::Grid, L"grid"), + std::make_pair(ZoneSetLayoutType::PriorityGrid, L"priority-grid"), + std::make_pair(ZoneSetLayoutType::Custom, L"custom"), + }; + + for (const auto& expected : expectedMap) + { + auto actual = JSONHelpers::TypeFromString(expected.second); + Assert::AreEqual(static_cast(expected.first), static_cast(actual)); + } + } + + TEST_METHOD(ZoneSetLayoutTypeFromLayoutId) + { + std::map expectedMap = { + std::make_pair(ZoneSetLayoutType::Focus, 0xFFFF), + std::make_pair(ZoneSetLayoutType::Columns, 0xFFFD), + std::make_pair(ZoneSetLayoutType::Rows, 0xFFFE), + std::make_pair(ZoneSetLayoutType::Grid, 0xFFFC), + std::make_pair(ZoneSetLayoutType::PriorityGrid, 0xFFFB), + std::make_pair(ZoneSetLayoutType::Custom, 0xFFFA), + std::make_pair(ZoneSetLayoutType::Custom, 0), + std::make_pair(ZoneSetLayoutType::Custom, -1), + }; + + for (const auto& expected : expectedMap) + { + auto actual = JSONHelpers::TypeFromLayoutId(expected.second); + Assert::AreEqual(static_cast(expected.first), static_cast(actual)); + } + } + }; + + TEST_CLASS(CanvasLayoutInfoUnitTests) + { + json::JsonObject m_json = json::JsonObject::Parse(L"{\"ref-width\": 123, \"ref-height\": 321, \"zones\": [{\"X\": 11, \"Y\": 22, \"width\": 33, \"height\": 44}, {\"X\": 55, \"Y\": 66, \"width\": 77, \"height\": 88}]}"); + + TEST_METHOD(ToJson) + { + CanvasLayoutInfo info; + info.referenceWidth = 123; + info.referenceHeight = 321; + info.zones = { CanvasLayoutInfo::Rect{ 11, 22, 33, 44 }, CanvasLayoutInfo::Rect{ 55, 66, 77, 88 } }; + + auto actual = CanvasLayoutInfo::ToJson(info); + compareJsonObjects(m_json, actual); + } + + TEST_METHOD(FromJson) + { + CanvasLayoutInfo expected; + expected.referenceWidth = 123; + expected.referenceHeight = 321; + expected.zones = { CanvasLayoutInfo::Rect{ 11, 22, 33, 44 }, CanvasLayoutInfo::Rect{ 55, 66, 77, 88 } }; + + auto actual = CanvasLayoutInfo::FromJson(m_json); + Assert::IsTrue(actual.has_value()); + + Assert::AreEqual(expected.referenceHeight, actual->referenceHeight); + Assert::AreEqual(expected.referenceWidth, actual->referenceWidth); + Assert::AreEqual(expected.zones.size(), actual->zones.size()); + for (int i = 0; i < expected.zones.size(); i++) + { + Assert::AreEqual(expected.zones[i].x, actual->zones[i].x); + Assert::AreEqual(expected.zones[i].y, actual->zones[i].y); + Assert::AreEqual(expected.zones[i].width, actual->zones[i].width); + Assert::AreEqual(expected.zones[i].height, actual->zones[i].height); + } + } + + TEST_METHOD(FromJsonMissingKeys) + { + CanvasLayoutInfo info{ 123, 321, { CanvasLayoutInfo::Rect{ 11, 22, 33, 44 }, CanvasLayoutInfo::Rect{ 55, 66, 77, 88 } } }; + const auto json = CanvasLayoutInfo::ToJson(info); + + auto iter = json.First(); + while (iter.HasCurrent()) + { + json::JsonObject modifiedJson = json::JsonObject::Parse(json.Stringify()); + modifiedJson.Remove(iter.Current().Key()); + + auto actual = CanvasLayoutInfo::FromJson(modifiedJson); + Assert::IsFalse(actual.has_value()); + + iter.MoveNext(); + } + } + }; + + TEST_CLASS(GridLayoutInfoUnitTests) + { + private: + GridLayoutInfo m_info = GridLayoutInfo(GridLayoutInfo::Minimal{ .rows = 3, .columns = 4 }); + json::JsonObject m_gridJson = json::JsonObject(); + json::JsonArray m_rowsArray, m_columnsArray, m_cells; + + void compareSizes(int expectedRows, int expectedColumns, const GridLayoutInfo& actual) + { + Assert::AreEqual(expectedRows, actual.rows()); + Assert::AreEqual(expectedColumns, actual.columns()); + Assert::AreEqual((size_t)expectedRows, actual.rowsPercents().size()); + Assert::AreEqual((size_t)expectedColumns, actual.columnsPercents().size()); + Assert::AreEqual((size_t)expectedRows, actual.cellChildMap().size()); + + for (int i = 0; i < expectedRows; i++) + { + Assert::AreEqual((size_t)expectedColumns, actual.cellChildMap()[i].size()); + } + } + + void compareVectors(const std::vector& expected, const std::vector& actual) + { + Assert::AreEqual(expected.size(), actual.size()); + for (int i = 0; i < expected.size(); i++) + { + Assert::AreEqual(expected[i], actual[i]); + } + } + + void compareGridInfos(const GridLayoutInfo& expected, const GridLayoutInfo& actual) + { + compareSizes(expected.rows(), expected.columns(), actual); + + compareVectors(expected.rowsPercents(), actual.rowsPercents()); + compareVectors(expected.columnsPercents(), actual.columnsPercents()); + for (int i = 0; i < expected.cellChildMap().size(); i++) + { + compareVectors(expected.cellChildMap()[i], actual.cellChildMap()[i]); + } + } + + TEST_METHOD_INITIALIZE(Init) + { + m_info = GridLayoutInfo(GridLayoutInfo::Minimal{ .rows = 3, .columns = 4 }); + for (int i = 0; i < m_info.rows(); i++) + { + int row = rand() % 100; + m_rowsArray.Append(json::JsonValue::CreateNumberValue(row)); + m_info.rowsPercents()[i] = row; + } + + for (int i = 0; i < m_info.columns(); i++) + { + int column = rand() % 100; + m_columnsArray.Append(json::JsonValue::CreateNumberValue(column)); + m_info.columnsPercents()[i] = column; + } + + for (int i = 0; i < m_info.rows(); i++) + { + json::JsonArray cellsArray; + for (int j = 0; j < m_info.columns(); j++) + { + int cell = rand() % 100; + m_info.cellChildMap()[i][j] = cell; + cellsArray.Append(json::JsonValue::CreateNumberValue(cell)); + } + m_cells.Append(cellsArray); + } + + m_gridJson = json::JsonObject::Parse(L"{\"rows\": 3, \"columns\": 4}"); + m_gridJson.SetNamedValue(L"rows-percentage", m_rowsArray); + m_gridJson.SetNamedValue(L"columns-percentage", m_columnsArray); + m_gridJson.SetNamedValue(L"cell-child-map", m_cells); + } + + TEST_METHOD_CLEANUP(Cleanup) + { + m_rowsArray.Clear(); + m_cells.Clear(); + m_columnsArray.Clear(); + m_gridJson.Clear(); + m_info = GridLayoutInfo(GridLayoutInfo::Minimal{ .rows = 3, .columns = 4 }); + } + + public: + TEST_METHOD(CreationZero) + { + const int expectedRows = 0, expectedColumns = 0; + GridLayoutInfo info(GridLayoutInfo::Minimal{ .rows = expectedRows, .columns = expectedColumns }); + compareSizes(expectedRows, expectedColumns, info); + } + + TEST_METHOD(Creation) + { + const int expectedRows = 3, expectedColumns = 4; + const std::vector expectedRowsPercents = { 0, 0, 0 }; + const std::vector expectedColumnsPercents = { 0, 0, 0, 0 }; + + GridLayoutInfo info(GridLayoutInfo::Minimal{ .rows = expectedRows, .columns = expectedColumns }); + compareSizes(expectedRows, expectedColumns, info); + + compareVectors(expectedRowsPercents, info.rowsPercents()); + compareVectors(expectedColumnsPercents, info.columnsPercents()); + for (int i = 0; i < info.cellChildMap().size(); i++) + { + compareVectors({ 0, 0, 0, 0 }, info.cellChildMap()[i]); + } + } + + TEST_METHOD(CreationFull) + { + const int expectedRows = 3, expectedColumns = 4; + const std::vector expectedRowsPercents = { 1, 2, 3 }; + const std::vector expectedColumnsPercents = { 4, 3, 2, 1 }; + const std::vector> expectedCells = { expectedColumnsPercents, expectedColumnsPercents, expectedColumnsPercents }; + + GridLayoutInfo info(GridLayoutInfo::Full{ + .rows = expectedRows, + .columns = expectedColumns , + .rowsPercents = expectedRowsPercents, + .columnsPercents = expectedColumnsPercents, + .cellChildMap = expectedCells }); + compareSizes(expectedRows, expectedColumns, info); + + compareVectors(expectedRowsPercents, info.rowsPercents()); + compareVectors(expectedColumnsPercents, info.columnsPercents()); + for (int i = 0; i < info.cellChildMap().size(); i++) + { + compareVectors(expectedCells[i], info.cellChildMap()[i]); + } + } + + TEST_METHOD(CreationFullVectorsSmaller) + { + const int expectedRows = 3, expectedColumns = 4; + const std::vector expectedRowsPercents = { 1, 2, 0 }; + const std::vector expectedColumnsPercents = { 4, 3, 0, 0 }; + const std::vector> expectedCells = { { 0, 0, 0, 0 }, { 1, 0, 0, 0 }, { 1, 2, 0, 0 } }; + + GridLayoutInfo info(GridLayoutInfo::Full{ + .rows = expectedRows, + .columns = expectedColumns, + .rowsPercents = { 1, 2 }, + .columnsPercents = { 4, 3 }, + .cellChildMap = { {}, { 1 }, { 1, 2 } } }); + compareSizes(expectedRows, expectedColumns, info); + + compareVectors(expectedRowsPercents, info.rowsPercents()); + compareVectors(expectedColumnsPercents, info.columnsPercents()); + for (int i = 0; i < info.cellChildMap().size(); i++) + { + compareVectors(expectedCells[i], info.cellChildMap()[i]); + } + } + + TEST_METHOD(CreationFullVectorsBigger) + { + const int expectedRows = 3, expectedColumns = 4; + const std::vector expectedRowsPercents = { 1, 2, 3 }; + const std::vector expectedColumnsPercents = { 4, 3, 2, 1 }; + const std::vector> expectedCells = { expectedColumnsPercents, expectedColumnsPercents, expectedColumnsPercents }; + + GridLayoutInfo info(GridLayoutInfo::Full{ + .rows = expectedRows, + .columns = expectedColumns, + .rowsPercents = { 1, 2, 3, 4, 5 }, + .columnsPercents = { 4, 3, 2, 1, 0, -1 }, + .cellChildMap = { { 4, 3, 2, 1, 0, -1 }, { 4, 3, 2, 1, 0, -1 }, { 4, 3, 2, 1, 0, -1 } } }); + compareSizes(expectedRows, expectedColumns, info); + + compareVectors(expectedRowsPercents, info.rowsPercents()); + compareVectors(expectedColumnsPercents, info.columnsPercents()); + for (int i = 0; i < info.cellChildMap().size(); i++) + { + compareVectors(expectedCells[i], info.cellChildMap()[i]); + } + } + + TEST_METHOD(ToJson) + { + json::JsonObject expected = json::JsonObject(m_gridJson); + GridLayoutInfo info = m_info; + + auto actual = GridLayoutInfo::ToJson(info); + compareJsonObjects(expected, actual); + } + + TEST_METHOD(FromJson) + { + json::JsonObject json = json::JsonObject(m_gridJson); + GridLayoutInfo expected = m_info; + + auto actual = GridLayoutInfo::FromJson(json); + Assert::IsTrue(actual.has_value()); + compareGridInfos(expected, *actual); + } + + TEST_METHOD(FromJsonEmptyArray) + { + json::JsonObject json = json::JsonObject::Parse(L"{\"rows\": 0, \"columns\": 0}"); + GridLayoutInfo expected(GridLayoutInfo::Minimal{ 0, 0 }); + + json.SetNamedValue(L"rows-percentage", json::JsonArray()); + json.SetNamedValue(L"columns-percentage", json::JsonArray()); + json.SetNamedValue(L"cell-child-map", json::JsonArray()); + + auto actual = GridLayoutInfo::FromJson(json); + Assert::IsTrue(actual.has_value()); + compareGridInfos(expected, *actual); + } + + TEST_METHOD(FromJsonSmallerArray) + { + GridLayoutInfo expected = m_info; + expected.rowsPercents().pop_back(); + expected.columnsPercents().pop_back(); + expected.cellChildMap().pop_back(); + expected.cellChildMap()[0].pop_back(); + json::JsonObject json = GridLayoutInfo::ToJson(expected); + + auto actual = GridLayoutInfo::FromJson(json); + Assert::IsFalse(actual.has_value()); + } + + TEST_METHOD(FromJsonBiggerArray) + { + GridLayoutInfo expected = m_info; + + //extra + for (int i = 0; i < 5; i++) + { + expected.rowsPercents().push_back(rand() % 100); + expected.columnsPercents().push_back(rand() % 100); + expected.cellChildMap().push_back({}); + + for (int j = 0; j < 5; j++) + { + expected.cellChildMap()[i].push_back(rand() % 100); + } + } + + auto json = GridLayoutInfo::ToJson(expected); + + auto actual = GridLayoutInfo::FromJson(json); + Assert::IsFalse(actual.has_value()); + } + + TEST_METHOD(FromJsonMissingKeys) + { + GridLayoutInfo info = m_info; + const auto json = json::JsonObject(m_gridJson); + + auto iter = json.First(); + while (iter.HasCurrent()) + { + json::JsonObject modifiedJson = json::JsonObject::Parse(json.Stringify()); + modifiedJson.Remove(iter.Current().Key()); + + auto actual = GridLayoutInfo::FromJson(modifiedJson); + Assert::IsFalse(actual.has_value()); + + iter.MoveNext(); + } + } + }; + + TEST_CLASS(CustomZoneSetUnitTests) + { + TEST_METHOD(ToJsonGrid) + { + CustomZoneSetJSON zoneSet{ L"uuid", CustomZoneSetData{ L"name", CustomLayoutType::Grid, GridLayoutInfo(GridLayoutInfo::Minimal{}) } }; + + json::JsonObject expected = json::JsonObject::Parse(L"{\"uuid\": \"uuid\", \"name\": \"name\", \"type\": \"grid\"}"); + expected.SetNamedValue(L"info", GridLayoutInfo::ToJson(std::get(zoneSet.data.info))); + + auto actual = CustomZoneSetJSON::ToJson(zoneSet); + compareJsonObjects(expected, actual); + } + + TEST_METHOD(ToJsonCanvas) + { + CustomZoneSetJSON zoneSet{ L"uuid", CustomZoneSetData{ L"name", CustomLayoutType::Canvas, CanvasLayoutInfo{} } }; + + json::JsonObject expected = json::JsonObject::Parse(L"{\"uuid\": \"uuid\", \"name\": \"name\", \"type\": \"canvas\"}"); + expected.SetNamedValue(L"info", CanvasLayoutInfo::ToJson(std::get(zoneSet.data.info))); + + auto actual = CustomZoneSetJSON::ToJson(zoneSet); + compareJsonObjects(expected, actual); + } + + TEST_METHOD(FromJsonGrid) + { + const auto grid = GridLayoutInfo(GridLayoutInfo::Full{ 1, 3, { 10000 }, { 2500, 5000, 2500 }, { { 0, 1, 2 } } }); + CustomZoneSetJSON expected{ L"uuid", CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } }; + + json::JsonObject json = json::JsonObject::Parse(L"{\"uuid\": \"uuid\", \"name\": \"name\", \"type\": \"grid\"}"); + json.SetNamedValue(L"info", GridLayoutInfo::ToJson(std::get(expected.data.info))); + + auto actual = CustomZoneSetJSON::FromJson(json); + Assert::IsTrue(actual.has_value()); + + Assert::AreEqual(expected.uuid.c_str(), actual->uuid.c_str()); + Assert::AreEqual(expected.data.name.c_str(), actual->data.name.c_str()); + Assert::AreEqual((int)expected.data.type, (int)actual->data.type); + + auto expectedGrid = std::get(expected.data.info); + auto actualGrid = std::get(actual->data.info); + Assert::AreEqual(expectedGrid.rows(), actualGrid.rows()); + Assert::AreEqual(expectedGrid.columns(), actualGrid.columns()); + } + + TEST_METHOD(FromJsonCanvas) + { + CustomZoneSetJSON expected{ L"uuid", CustomZoneSetData{ L"name", CustomLayoutType::Canvas, CanvasLayoutInfo{ 2, 1 } } }; + + json::JsonObject json = json::JsonObject::Parse(L"{\"uuid\": \"uuid\", \"name\": \"name\", \"type\": \"canvas\"}"); + json.SetNamedValue(L"info", CanvasLayoutInfo::ToJson(std::get(expected.data.info))); + + auto actual = CustomZoneSetJSON::FromJson(json); + Assert::IsTrue(actual.has_value()); + + Assert::AreEqual(expected.uuid.c_str(), actual->uuid.c_str()); + Assert::AreEqual(expected.data.name.c_str(), actual->data.name.c_str()); + Assert::AreEqual((int)expected.data.type, (int)actual->data.type); + + auto expectedGrid = std::get(expected.data.info); + auto actualGrid = std::get(actual->data.info); + Assert::AreEqual(expectedGrid.referenceWidth, actualGrid.referenceWidth); + Assert::AreEqual(expectedGrid.referenceHeight, actualGrid.referenceHeight); + } + + TEST_METHOD(FromJsonMissingKeys) + { + CustomZoneSetJSON zoneSet{ L"uuid", CustomZoneSetData{ L"name", CustomLayoutType::Canvas, CanvasLayoutInfo{ 2, 1 } } }; + const auto json = CustomZoneSetJSON::ToJson(zoneSet); + + auto iter = json.First(); + while (iter.HasCurrent()) + { + json::JsonObject modifiedJson = json::JsonObject::Parse(json.Stringify()); + modifiedJson.Remove(iter.Current().Key()); + + auto actual = CustomZoneSetJSON::FromJson(modifiedJson); + Assert::IsFalse(actual.has_value()); + + iter.MoveNext(); + } + } + }; + + TEST_CLASS(ZoneSetDataUnitTest){ + TEST_METHOD(ToJsonCustom) + { + json::JsonObject expected = json::JsonObject::Parse(L"{\"uuid\": \"uuid\", \"type\": \"custom\"}"); + ZoneSetData data{ L"uuid", ZoneSetLayoutType::Custom }; + const auto actual = ZoneSetData::ToJson(data); + compareJsonObjects(expected, actual); + } + + TEST_METHOD(ToJsonGeneral) + { + json::JsonObject expected = json::JsonObject::Parse(L"{\"uuid\": \"uuid\", \"type\": \"rows\", \"zone-count\": 47372}"); + ZoneSetData data{ L"uuid", ZoneSetLayoutType::Rows, 47372 }; + const auto actual = ZoneSetData::ToJson(data); + compareJsonObjects(expected, actual); + } + + TEST_METHOD(FromJsonCustom) + { + ZoneSetData expected{ L"uuid", ZoneSetLayoutType::Custom }; + + json::JsonObject json = json::JsonObject::Parse(L"{\"uuid\": \"uuid\", \"type\": \"custom\"}"); + auto actual = ZoneSetData::FromJson(json); + Assert::IsTrue(actual.has_value()); + + Assert::AreEqual(expected.uuid.c_str(), actual->uuid.c_str()); + Assert::AreEqual((int)expected.type, (int)actual->type); + Assert::IsFalse(actual->zoneCount.has_value()); + } + + TEST_METHOD(FromJsonCustomZoneAdded) + { + ZoneSetData expected{ L"uuid", ZoneSetLayoutType::Custom }; + + json::JsonObject json = json::JsonObject::Parse(L"{\"uuid\": \"uuid\", \"type\": \"custom\", \"zone-count\": 47372}"); + auto actual = ZoneSetData::FromJson(json); + Assert::IsTrue(actual.has_value()); + + Assert::AreEqual(expected.uuid.c_str(), actual->uuid.c_str()); + Assert::AreEqual((int)expected.type, (int)actual->type); + Assert::IsFalse(actual->zoneCount.has_value()); + } + + TEST_METHOD(FromJsonGeneral) + { + ZoneSetData expected{ L"uuid", ZoneSetLayoutType::Columns, 47372 }; + + json::JsonObject json = json::JsonObject::Parse(L"{\"uuid\": \"uuid\", \"type\": \"columns\", \"zone-count\": 47372}"); + auto actual = ZoneSetData::FromJson(json); + Assert::IsTrue(actual.has_value()); + + Assert::AreEqual(expected.uuid.c_str(), actual->uuid.c_str()); + Assert::AreEqual((int)expected.type, (int)actual->type); + Assert::IsTrue(actual->zoneCount.has_value()); + Assert::AreEqual(*expected.zoneCount, *actual->zoneCount); + } + + TEST_METHOD(FromJsonTypeInvalid) + { + ZoneSetData expected{ L"uuid", ZoneSetLayoutType::Custom }; + + json::JsonObject json = json::JsonObject::Parse(L"{\"uuid\": \"uuid\", \"type\": \"invalid_type\"}"); + auto actual = ZoneSetData::FromJson(json); + Assert::IsTrue(actual.has_value()); + + Assert::AreEqual(expected.uuid.c_str(), actual->uuid.c_str()); + Assert::AreEqual((int)expected.type, (int)actual->type); + Assert::IsFalse(actual->zoneCount.has_value()); + } + + TEST_METHOD(FromJsonMissingKeys) + { + ZoneSetData data{ L"uuid", ZoneSetLayoutType::Columns, 47372 }; + const auto json = ZoneSetData::ToJson(data); + + auto iter = json.First(); + while (iter.HasCurrent()) + { + json::JsonObject modifiedJson = json::JsonObject::Parse(json.Stringify()); + modifiedJson.Remove(iter.Current().Key()); + + auto actual = ZoneSetData::FromJson(modifiedJson); + Assert::IsFalse(actual.has_value()); + + iter.MoveNext(); + } + } + }; + + TEST_CLASS(AppZoneHistoryUnitTests) + { + TEST_METHOD(ToJson) + { + AppZoneHistoryJSON appZoneHistory{ L"appPath", AppZoneHistoryData{ L"zone-set-uuid", 54321 } }; + json::JsonObject expected = json::JsonObject::Parse(L"{\"app-path\": \"appPath\", \"zoneset-uuid\": \"zone-set-uuid\", \"zone-index\": 54321}"); + + auto actual = AppZoneHistoryJSON::ToJson(appZoneHistory); + compareJsonObjects(expected, actual); + } + + TEST_METHOD(FromJson) + { + AppZoneHistoryJSON expected{ L"appPath", AppZoneHistoryData{ L"zone-set-uuid", 54321 } }; + json::JsonObject json = json::JsonObject::Parse(L"{\"app-path\": \"appPath\", \"zoneset-uuid\": \"zone-set-uuid\", \"zone-index\": 54321}"); + + auto actual = AppZoneHistoryJSON::FromJson(json); + Assert::IsTrue(actual.has_value()); + + Assert::AreEqual(expected.appPath.c_str(), actual->appPath.c_str()); + Assert::AreEqual(expected.data.zoneIndex, actual->data.zoneIndex); + Assert::AreEqual(expected.data.zoneSetUuid.c_str(), actual->data.zoneSetUuid.c_str()); + } + + TEST_METHOD(FromJsonMissingKeys) + { + AppZoneHistoryJSON appZoneHistory{ L"appPath", AppZoneHistoryData{ L"zone-set-uuid", 54321 } }; + const auto json = AppZoneHistoryJSON::ToJson(appZoneHistory); + + auto iter = json.First(); + while (iter.HasCurrent()) + { + json::JsonObject modifiedJson = json::JsonObject::Parse(json.Stringify()); + modifiedJson.Remove(iter.Current().Key()); + + auto actual = AppZoneHistoryJSON::FromJson(modifiedJson); + Assert::IsFalse(actual.has_value()); + + iter.MoveNext(); + } + } + }; + + TEST_CLASS(DeviceInfoUnitTests) + { + private: + DeviceInfoJSON m_defaultDeviceInfo = DeviceInfoJSON{ L"default_device_id", DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 } }; + json::JsonObject m_defaultJson = json::JsonObject::Parse(L"{\"device-id\": \"default_device_id\", \"active-zoneset\": {\"type\": \"custom\", \"uuid\": \"uuid\"}, \"editor-show-spacing\": true, \"editor-spacing\": 16, \"editor-zone-count\": 3}"); + + public: + TEST_METHOD(ToJson) + { + DeviceInfoJSON deviceInfo = m_defaultDeviceInfo; + json::JsonObject expected = m_defaultJson; + + auto actual = DeviceInfoJSON::ToJson(deviceInfo); + compareJsonObjects(expected, actual); + } + + TEST_METHOD(FromJson) + { + DeviceInfoJSON expected = m_defaultDeviceInfo; + expected.data.spacing = true; + + json::JsonObject json = DeviceInfoJSON::ToJson(expected); + auto actual = DeviceInfoJSON::FromJson(json); + Assert::IsTrue(actual.has_value()); + + Assert::AreEqual(expected.deviceId.c_str(), actual->deviceId.c_str(), L"device id"); + Assert::AreEqual(expected.data.zoneCount, actual->data.zoneCount, L"zone count"); + Assert::AreEqual((int)expected.data.activeZoneSet.type, (int)actual->data.activeZoneSet.type, L"zone set type"); + Assert::AreEqual(expected.data.activeZoneSet.uuid.c_str(), actual->data.activeZoneSet.uuid.c_str(), L"zone set uuid"); + } + + TEST_METHOD(FromJsonSpacingTrue) + { + DeviceInfoJSON expected = m_defaultDeviceInfo; + expected.data.spacing = true; + + json::JsonObject json = DeviceInfoJSON::ToJson(expected); + auto actual = DeviceInfoJSON::FromJson(json); + Assert::IsTrue(actual.has_value()); + + Assert::AreEqual(expected.data.spacing, actual->data.spacing); + } + + TEST_METHOD(FromJsonSpacingFalse) + { + DeviceInfoJSON expected = m_defaultDeviceInfo; + expected.data.activeZoneSet.type = ZoneSetLayoutType::Custom; + + json::JsonObject json = DeviceInfoJSON::ToJson(expected); + auto actual = DeviceInfoJSON::FromJson(json); + Assert::IsTrue(actual.has_value()); + + Assert::AreEqual(expected.data.spacing, actual->data.spacing); + } + + TEST_METHOD(FromJsonZoneCustom) + { + DeviceInfoJSON expected = m_defaultDeviceInfo; + expected.data.activeZoneSet.type = ZoneSetLayoutType::Custom; + + json::JsonObject json = DeviceInfoJSON::ToJson(expected); + auto actual = DeviceInfoJSON::FromJson(json); + Assert::IsTrue(actual.has_value()); + + Assert::AreEqual((int)expected.data.activeZoneSet.type, (int)actual->data.activeZoneSet.type, L"zone set type"); + Assert::IsFalse(actual->data.activeZoneSet.zoneCount.has_value(), L"zone set count"); + } + + TEST_METHOD(FromJsonZoneGeneral) + { + DeviceInfoJSON expected = m_defaultDeviceInfo; + expected.data.activeZoneSet.type = ZoneSetLayoutType::PriorityGrid; + expected.data.activeZoneSet.zoneCount = 10; + + json::JsonObject json = DeviceInfoJSON::ToJson(expected); + auto actual = DeviceInfoJSON::FromJson(json); + Assert::IsTrue(actual.has_value()); + + Assert::AreEqual((int)expected.data.activeZoneSet.type, (int)actual->data.activeZoneSet.type, L"zone set type"); + Assert::IsTrue(actual->data.activeZoneSet.zoneCount.has_value(), L"zone set count"); + Assert::AreEqual(*expected.data.activeZoneSet.zoneCount, *actual->data.activeZoneSet.zoneCount); + } + + TEST_METHOD(FromJsonMissingKeys) + { + DeviceInfoJSON deviceInfo{ L"default_device_id", DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 } }; + const auto json = DeviceInfoJSON::ToJson(deviceInfo); + + auto iter = json.First(); + while (iter.HasCurrent()) + { + json::JsonObject modifiedJson = json::JsonObject::Parse(json.Stringify()); + modifiedJson.Remove(iter.Current().Key()); + + auto actual = DeviceInfoJSON::FromJson(modifiedJson); + Assert::IsFalse(actual.has_value()); + + iter.MoveNext(); + } + } + }; + + TEST_CLASS(FancyZonesDataUnitTests) + { + private: + const std::wstring m_defaultCustomDeviceStr = L"{\"device-id\": \"default_device_id\", \"active-zoneset\": {\"type\": \"custom\", \"uuid\": \"uuid\"}, \"editor-show-spacing\": true, \"editor-spacing\": 16, \"editor-zone-count\": 3}"; + const json::JsonValue m_defaultCustomDeviceValue = json::JsonValue::Parse(m_defaultCustomDeviceStr); + const json::JsonObject m_defaultCustomDeviceObj = json::JsonObject::Parse(m_defaultCustomDeviceStr); + + void compareJsonArrays(const json::JsonArray& expected, const json::JsonArray& actual) + { + Assert::AreEqual(expected.Size(), actual.Size()); + for (uint32_t i = 0; i < expected.Size(); i++) + { + compareJsonObjects(expected.GetObjectAt(i), actual.GetObjectAt(i)); + } + } + + public: + TEST_METHOD(FancyZonesDataPath) + { + FancyZonesData data; + Assert::IsFalse(data.GetPersistFancyZonesJSONPath().empty()); + } + + TEST_METHOD(FancyZonesDataJsonEmpty) + { + FancyZonesData data; + const auto jsonPath = data.GetPersistFancyZonesJSONPath(); + auto savedJson = json::from_file(jsonPath); + + if (std::filesystem::exists(jsonPath)) + { + std::filesystem::remove(jsonPath); + } + + json::JsonObject expected; + auto actual = data.GetPersistFancyZonesJSON(); + + Assert::AreEqual(expected.Stringify().c_str(), actual.Stringify().c_str()); + + if (savedJson) + { + json::to_file(jsonPath, *savedJson); + } + } + + TEST_METHOD(FancyZonesDataJson) + { + FancyZonesData data; + const auto jsonPath = data.GetPersistFancyZonesJSONPath(); + auto savedJson = json::from_file(jsonPath); + + if (std::filesystem::exists(jsonPath)) + { + std::filesystem::remove(jsonPath); + } + + json::JsonObject expected = json::JsonObject::Parse(L"{\"fancy-zones\":{\"custom-zonesets \":[{\"uuid\":\"uuid1\",\"name\":\"Custom1\",\"type\":\"custom\" }] } }"); + json::to_file(jsonPath, expected); + + auto actual = data.GetPersistFancyZonesJSON(); + Assert::AreEqual(expected.Stringify().c_str(), actual.Stringify().c_str()); + + if (savedJson) + { + json::to_file(jsonPath, *savedJson); + } + else + { + std::filesystem::remove(jsonPath); + } + } + + TEST_METHOD(FancyZonesDataDeviceInfoMap) + { + FancyZonesData data; + const auto actual = data.GetDeviceInfoMap(); + Assert::IsTrue(actual.empty()); + } + + TEST_METHOD(FancyZonesDataDeviceInfoMapParseEmpty) + { + FancyZonesData data; + + json::JsonObject json; + data.ParseDeviceInfos(json); + + const auto actual = data.GetDeviceInfoMap(); + Assert::IsTrue(actual.empty()); + } + + TEST_METHOD(FancyZonesDataDeviceInfoMapParseValidEmpty) + { + FancyZonesData data; + + json::JsonObject expected; + json::JsonArray zoneSets; + expected.SetNamedValue(L"devices", zoneSets); + + data.ParseDeviceInfos(expected); + + const auto actual = data.GetDeviceInfoMap(); + Assert::IsTrue(actual.empty()); + } + + TEST_METHOD(FancyZonesDataDeviceInfoMapParseInvalid) + { + json::JsonArray devices; + devices.Append(json::JsonObject::Parse(m_defaultCustomDeviceStr)); + devices.Append(json::JsonObject::Parse(L"{\"device-id\": \"device_id\"}")); + + json::JsonObject expected; + expected.SetNamedValue(L"devices", devices); + + FancyZonesData data; + auto actual = data.ParseDeviceInfos(expected); + + Assert::IsFalse(actual); + } + + TEST_METHOD(FancyZonesDataDeviceInfoMapParseSingle) + { + json::JsonArray devices; + devices.Append(m_defaultCustomDeviceValue); + json::JsonObject expected; + expected.SetNamedValue(L"devices", devices); + + FancyZonesData data; + data.ParseDeviceInfos(expected); + + const auto actualMap = data.GetDeviceInfoMap(); + Assert::AreEqual((size_t)1, actualMap.size()); + } + + TEST_METHOD(FancyZonesDataDeviceInfoMapParseMany) + { + json::JsonArray devices; + for (int i = 0; i < 10; i++) + { + json::JsonObject obj = json::JsonObject::Parse(m_defaultCustomDeviceStr); + obj.SetNamedValue(L"device-id", json::JsonValue::CreateStringValue(std::to_wstring(i))); + + Logger::WriteMessage(obj.Stringify().c_str()); + Logger::WriteMessage("\n"); + + devices.Append(obj); + } + + json::JsonObject expected; + expected.SetNamedValue(L"devices", devices); + Logger::WriteMessage(expected.Stringify().c_str()); + Logger::WriteMessage("\n"); + + FancyZonesData data; + data.ParseDeviceInfos(expected); + + const auto actualMap = data.GetDeviceInfoMap(); + Assert::AreEqual((size_t)10, actualMap.size()); + } + + TEST_METHOD(FancyZonesDataSerialize) + { + json::JsonArray expectedDevices; + expectedDevices.Append(m_defaultCustomDeviceObj); + json::JsonObject expected; + expected.SetNamedValue(L"devices", expectedDevices); + + FancyZonesData data; + data.ParseDeviceInfos(expected); + + auto actual = data.SerializeDeviceInfos(); + compareJsonArrays(expectedDevices, actual); + } + + TEST_METHOD(DeviceInfoSaveTemp) + { + FancyZonesData data; + DeviceInfoJSON deviceInfo{ L"default_device_id", DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 } }; + + const std::wstring path = data.GetPersistFancyZonesJSONPath() + L".test_tmp"; + data.SerializeDeviceInfoToTmpFile(deviceInfo, path); + + bool actualFileExists = std::filesystem::exists(path); + Assert::IsTrue(actualFileExists); + + auto expectedData = DeviceInfoJSON::ToJson(deviceInfo); + auto actualSavedData = json::from_file(path); + std::filesystem::remove(path); //clean up before compare asserts + + Assert::IsTrue(actualSavedData.has_value()); + compareJsonObjects(expectedData, *actualSavedData); + } + + TEST_METHOD(DeviceInfoReadTemp) + { + FancyZonesData data; + const std::wstring zoneUuid = L"default_device_id"; + DeviceInfoJSON expected{ zoneUuid, DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 } }; + const std::wstring path = data.GetPersistFancyZonesJSONPath() + L".test_tmp"; + data.SerializeDeviceInfoToTmpFile(expected, path); + + data.ParseDeviceInfoFromTmpFile(path); + + bool actualFileExists = std::filesystem::exists(path); + if (actualFileExists) + { + std::filesystem::remove(path); //clean up before compare asserts + } + Assert::IsFalse(actualFileExists); + + auto devices = data.GetDeviceInfoMap(); + Assert::AreEqual((size_t)1, devices.size()); + + auto actual = devices.find(zoneUuid)->second; + Assert::AreEqual(expected.data.showSpacing, actual.showSpacing); + Assert::AreEqual(expected.data.spacing, actual.spacing); + Assert::AreEqual(expected.data.zoneCount, actual.zoneCount); + Assert::AreEqual((int)expected.data.activeZoneSet.type, (int)actual.activeZoneSet.type); + Assert::AreEqual(expected.data.activeZoneSet.uuid.c_str(), actual.activeZoneSet.uuid.c_str()); + Assert::IsFalse(actual.activeZoneSet.zoneCount.has_value()); + } + + TEST_METHOD(DeviceInfoReadTempUnexsisted) + { + FancyZonesData data; + const std::wstring path = data.GetPersistFancyZonesJSONPath() + L".test_tmp"; + data.ParseDeviceInfoFromTmpFile(path); + + auto devices = data.GetDeviceInfoMap(); + Assert::AreEqual((size_t)0, devices.size()); + } + + TEST_METHOD(AppZoneHistoryParseSingle) + { + const std::wstring appPath = L"appPath"; + json::JsonObject json; + AppZoneHistoryJSON expected{ appPath, AppZoneHistoryData{ L"zone-set-uuid", 54321 } }; + json::JsonArray zoneHistoryArray; + zoneHistoryArray.Append(AppZoneHistoryJSON::ToJson(expected)); + json.SetNamedValue(L"app-zone-history", json::JsonValue::Parse(zoneHistoryArray.Stringify())); + + FancyZonesData data; + data.ParseAppZoneHistory(json); + + auto actualMap = data.GetAppZoneHistoryMap(); + Assert::AreEqual((size_t)zoneHistoryArray.Size(), actualMap.size()); + + auto actual = actualMap.find(appPath)->second; + Assert::AreEqual(expected.data.zoneSetUuid.c_str(), actual.zoneSetUuid.c_str()); + Assert::AreEqual(expected.data.zoneIndex, actual.zoneIndex); + } + + TEST_METHOD(AppZoneHistoryParseMany) + { + json::JsonObject json; + json::JsonArray zoneHistoryArray; + zoneHistoryArray.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-1", AppZoneHistoryData{ L"zone-set-uuid", 54321 } })); + zoneHistoryArray.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-2", AppZoneHistoryData{ L"zone-set-uuid", 54321 } })); + zoneHistoryArray.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-3", AppZoneHistoryData{ L"zone-set-uuid", 54321 } })); + zoneHistoryArray.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-4", AppZoneHistoryData{ L"zone-set-uuid", 54321 } })); + json.SetNamedValue(L"app-zone-history", json::JsonValue::Parse(zoneHistoryArray.Stringify())); + + FancyZonesData data; + data.ParseAppZoneHistory(json); + + auto actualMap = data.GetAppZoneHistoryMap(); + Assert::AreEqual((size_t)zoneHistoryArray.Size(), actualMap.size()); + + auto iter = zoneHistoryArray.First(); + while (iter.HasCurrent()) + { + auto expected = AppZoneHistoryJSON::FromJson(json::JsonObject::Parse(iter.Current().Stringify())); + auto actual = actualMap.find(expected->appPath)->second; + Assert::AreEqual(expected->data.zoneSetUuid.c_str(), actual.zoneSetUuid.c_str()); + Assert::AreEqual(expected->data.zoneIndex, actual.zoneIndex); + + iter.MoveNext(); + } + } + + TEST_METHOD(AppZoneHistoryParseEmpty) + { + FancyZonesData data; + data.ParseAppZoneHistory(json::JsonObject()); + + auto actual = data.GetAppZoneHistoryMap(); + Assert::IsTrue(actual.empty()); + } + + TEST_METHOD(AppZoneHistoryParseInvalid) + { + const std::wstring appPath = L"appPath"; + json::JsonObject json; + AppZoneHistoryJSON expected{ appPath, AppZoneHistoryData{ L"zone-set-uuid", 54321 } }; + json.SetNamedValue(L"app-zone-history", json::JsonValue::Parse(AppZoneHistoryJSON::ToJson(expected).Stringify())); + + FancyZonesData data; + bool actual = data.ParseAppZoneHistory(json); + + Assert::IsFalse(actual); + } + + TEST_METHOD(AppZoneHistorySerializeSingle) + { + const std::wstring appPath = L"appPath"; + json::JsonArray expected; + expected.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ appPath, AppZoneHistoryData{ L"zone-set-uuid", 54321 } })); + json::JsonObject json; + json.SetNamedValue(L"app-zone-history", json::JsonValue::Parse(expected.Stringify())); + + FancyZonesData data; + data.ParseAppZoneHistory(json); + + auto actual = data.SerializeAppZoneHistory(); + compareJsonArrays(expected, actual); + } + + TEST_METHOD(AppZoneHistorySerializeMany) + { + json::JsonObject json; + json::JsonArray expected; + expected.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-1", AppZoneHistoryData{ L"zone-set-uuid", 54321 } })); + expected.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-2", AppZoneHistoryData{ L"zone-set-uuid", 54321 } })); + expected.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-3", AppZoneHistoryData{ L"zone-set-uuid", 54321 } })); + expected.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-4", AppZoneHistoryData{ L"zone-set-uuid", 54321 } })); + json.SetNamedValue(L"app-zone-history", json::JsonValue::Parse(expected.Stringify())); + + FancyZonesData data; + data.ParseAppZoneHistory(json); + + auto actual = data.SerializeAppZoneHistory(); + compareJsonArrays(expected, actual); + } + + TEST_METHOD(AppZoneHistorySerializeEmpty) + { + json::JsonArray expected; + json::JsonObject json; + json.SetNamedValue(L"app-zone-history", json::JsonValue::Parse(expected.Stringify())); + + FancyZonesData data; + data.ParseAppZoneHistory(json); + + auto actual = data.SerializeAppZoneHistory(); + compareJsonArrays(expected, actual); + } + + TEST_METHOD(CustomZoneSetsParseSingle) + { + const std::wstring zoneUuid = L"uuid"; + GridLayoutInfo grid(GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{ + .rows = 1, + .columns = 3, + .rowsPercents = { 10000 }, + .columnsPercents = { 2500, 5000, 2500 }, + .cellChildMap = { { 0, 1, 2 } } })); + + json::JsonObject json; + CustomZoneSetJSON expected{ zoneUuid, CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } }; + json::JsonArray array; + array.Append(CustomZoneSetJSON::ToJson(expected)); + json.SetNamedValue(L"custom-zone-sets", json::JsonValue::Parse(array.Stringify())); + + FancyZonesData data; + data.ParseCustomZoneSets(json); + + auto actualMap = data.GetCustomZoneSetsMap(); + Assert::AreEqual((size_t)array.Size(), actualMap.size()); + + auto actual = actualMap.find(zoneUuid)->second; + Assert::AreEqual(expected.data.name.c_str(), actual.name.c_str()); + Assert::AreEqual((int)expected.data.type, (int)actual.type); + + auto expectedGrid = std::get(expected.data.info); + auto actualGrid = std::get(actual.info); + Assert::AreEqual(expectedGrid.rows(), actualGrid.rows()); + Assert::AreEqual(expectedGrid.columns(), actualGrid.columns()); + } + + TEST_METHOD(CustomZoneSetsParseMany) + { + json::JsonObject json; + json::JsonArray array; + const GridLayoutInfo grid(GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{ + .rows = 1, + .columns = 3, + .rowsPercents = { 10000 }, + .columnsPercents = { 2500, 5000, 2500 }, + .cellChildMap = { { 0, 1, 2 } } })); + array.Append(CustomZoneSetJSON::ToJson(CustomZoneSetJSON{ L"zone-uuid-1", CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } })); + array.Append(CustomZoneSetJSON::ToJson(CustomZoneSetJSON{ L"zone-uuid-2", CustomZoneSetData{ L"name", CustomLayoutType::Canvas, CanvasLayoutInfo{ 1, 2 } } })); + array.Append(CustomZoneSetJSON::ToJson(CustomZoneSetJSON{ L"zone-uuid-3", CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } })); + array.Append(CustomZoneSetJSON::ToJson(CustomZoneSetJSON{ L"zone-uuid-4", CustomZoneSetData{ L"name", CustomLayoutType::Canvas, CanvasLayoutInfo{ 1, 2 } } })); + json.SetNamedValue(L"custom-zone-sets", json::JsonValue::Parse(array.Stringify())); + + FancyZonesData data; + data.ParseCustomZoneSets(json); + + auto actualMap = data.GetCustomZoneSetsMap(); + Assert::AreEqual((size_t)array.Size(), actualMap.size()); + + auto iter = array.First(); + while (iter.HasCurrent()) + { + auto expected = CustomZoneSetJSON::FromJson(json::JsonObject::Parse(iter.Current().Stringify())); + auto actual = actualMap.find(expected->uuid)->second; + Assert::AreEqual(expected->data.name.c_str(), actual.name.c_str(), L"name"); + Assert::AreEqual((int)expected->data.type, (int)actual.type, L"type"); + + if (expected->data.type == CustomLayoutType::Grid) + { + auto expectedInfo = std::get(expected->data.info); + auto actualInfo = std::get(actual.info); + Assert::AreEqual(expectedInfo.rows(), actualInfo.rows(), L"grid rows"); + Assert::AreEqual(expectedInfo.columns(), actualInfo.columns(), L"grid columns"); + } + else + { + auto expectedInfo = std::get(expected->data.info); + auto actualInfo = std::get(actual.info); + Assert::AreEqual(expectedInfo.referenceWidth, actualInfo.referenceWidth, L"canvas width"); + Assert::AreEqual(expectedInfo.referenceHeight, actualInfo.referenceHeight, L"canvas height"); + } + + iter.MoveNext(); + } + } + + TEST_METHOD(CustomZoneSetsParseEmpty) + { + FancyZonesData data; + data.ParseCustomZoneSets(json::JsonObject()); + + auto actual = data.GetCustomZoneSetsMap(); + Assert::IsTrue(actual.empty()); + } + + TEST_METHOD(CustomZoneSetsParseInvalid) + { + json::JsonObject json; + CustomZoneSetJSON expected{ L"uuid", CustomZoneSetData{ L"name", CustomLayoutType::Grid, GridLayoutInfo(GridLayoutInfo::Minimal{ 1, 2 }) } }; + json.SetNamedValue(L"custom-zone-sets", json::JsonValue::Parse(CustomZoneSetJSON::ToJson(expected).Stringify())); + + FancyZonesData data; + auto actual = data.ParseCustomZoneSets(json); + + Assert::IsFalse(actual); + } + + TEST_METHOD(CustomZoneSetsSerializeSingle) + { + json::JsonArray expected; + const GridLayoutInfo grid(GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{ + .rows = 1, + .columns = 3, + .rowsPercents = { 10000 }, + .columnsPercents = { 2500, 5000, 2500 }, + .cellChildMap = { { 0, 1, 2 } } })); + expected.Append(CustomZoneSetJSON::ToJson(CustomZoneSetJSON{ L"uuid", CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } })); + json::JsonObject json; + json.SetNamedValue(L"custom-zone-sets", json::JsonValue::Parse(expected.Stringify())); + + FancyZonesData data; + data.ParseCustomZoneSets(json); + + auto actual = data.SerializeCustomZoneSets(); + compareJsonArrays(expected, actual); + } + + TEST_METHOD(CustomZoneSetsSerializeMany) + { + json::JsonObject json; + json::JsonArray expected; + const GridLayoutInfo grid(GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{ + .rows = 1, + .columns = 3, + .rowsPercents = { 10000 }, + .columnsPercents = { 2500, 5000, 2500 }, + .cellChildMap = { { 0, 1, 2 } } })); + + expected.Append(CustomZoneSetJSON::ToJson(CustomZoneSetJSON{ L"zone-uuid-1", CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } })); + expected.Append(CustomZoneSetJSON::ToJson(CustomZoneSetJSON{ L"zone-uuid-2", CustomZoneSetData{ L"name", CustomLayoutType::Canvas, CanvasLayoutInfo{ 1, 2 } } })); + expected.Append(CustomZoneSetJSON::ToJson(CustomZoneSetJSON{ L"zone-uuid-3", CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } })); + expected.Append(CustomZoneSetJSON::ToJson(CustomZoneSetJSON{ L"zone-uuid-4", CustomZoneSetData{ L"name", CustomLayoutType::Canvas, CanvasLayoutInfo{ 1, 2 } } })); + json.SetNamedValue(L"custom-zone-sets", json::JsonValue::Parse(expected.Stringify())); + + FancyZonesData data; + data.ParseCustomZoneSets(json); + + auto actual = data.SerializeCustomZoneSets(); + compareJsonArrays(expected, actual); + } + + TEST_METHOD(CustomZoneSetsSerializeEmpty) + { + json::JsonArray expected; + json::JsonObject json; + json.SetNamedValue(L"custom-zone-sets", json::JsonValue::Parse(expected.Stringify())); + + FancyZonesData data; + data.ParseCustomZoneSets(json); + + auto actual = data.SerializeCustomZoneSets(); + compareJsonArrays(expected, actual); + } + + TEST_METHOD(CustomZoneSetsReadTemp) + { + const std::wstring uuid = L"uuid"; + const GridLayoutInfo grid(GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{ + .rows = 1, + .columns = 3, + .rowsPercents = { 10000 }, + .columnsPercents = { 2500, 5000, 2500 }, + .cellChildMap = { { 0, 1, 2 } } })); + CustomZoneSetJSON expected{ uuid, CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } }; + + FancyZonesData data; + const std::wstring path = data.GetPersistFancyZonesJSONPath() + L".test_tmp"; + json::to_file(path, CustomZoneSetJSON::ToJson(expected)); + + data.ParseCustomZoneSetFromTmpFile(path, uuid); + + bool actualFileExists = std::filesystem::exists(path); + if (actualFileExists) + { + std::filesystem::remove(path); //clean up before compare asserts + } + Assert::IsFalse(actualFileExists); + + auto devices = data.GetCustomZoneSetsMap(); + Assert::AreEqual((size_t)1, devices.size()); + + auto actual = devices.find(uuid)->second; + Assert::AreEqual((int)expected.data.type, (int)actual.type); + Assert::AreEqual(expected.data.name.c_str(), actual.name.c_str()); + auto expectedGrid = std::get(expected.data.info); + auto actualGrid = std::get(actual.info); + Assert::AreEqual(expectedGrid.rows(), actualGrid.rows()); + Assert::AreEqual(expectedGrid.columns(), actualGrid.columns()); + } + + TEST_METHOD(CustomZoneSetsReadTempUnexsisted) + { + const std::wstring uuid = L"uuid"; + FancyZonesData data; + + const std::wstring path = data.GetPersistFancyZonesJSONPath() + L".test_tmp"; + + data.ParseCustomZoneSetFromTmpFile(path, uuid); + auto devices = data.GetDeviceInfoMap(); + Assert::AreEqual((size_t)0, devices.size()); + } + + TEST_METHOD(CustomZoneSetsReadTempMissedUuid) + { + const std::wstring uuid = L"uuid"; + CustomZoneSetJSON expected{ uuid, CustomZoneSetData{ L"name", CustomLayoutType::Grid, GridLayoutInfo(GridLayoutInfo::Minimal{ 1, 2 }) } }; + FancyZonesData data; + + const std::wstring path = data.GetPersistFancyZonesJSONPath() + L".test_tmp"; + + json::to_file(path, CustomZoneSetJSON::ToJson(expected)); + data.ParseCustomZoneSetFromTmpFile(path, L"another_uuid"); + + bool actualFileExists = std::filesystem::exists(path); + if (actualFileExists) + { + std::filesystem::remove(path); //clean up before compare asserts + } + Assert::IsFalse(actualFileExists); + + auto devices = data.GetDeviceInfoMap(); + Assert::AreEqual((size_t)0, devices.size()); + } + + TEST_METHOD(SetActiveZoneSet) + { + FancyZonesData data; + const std::wstring uuid = L"uuid"; + const std::wstring uniqueId = L"default_device_id"; + + json::JsonArray devices; + devices.Append(m_defaultCustomDeviceValue); + json::JsonObject json; + json.SetNamedValue(L"devices", devices); + data.ParseDeviceInfos(json); + + data.SetActiveZoneSet(uniqueId, uuid); + + auto actual = data.GetDeviceInfoMap().find(uniqueId)->second; + Assert::AreEqual(uuid, actual.activeZoneSet.uuid); + } + + TEST_METHOD(SetActiveZoneSetUuidEmpty) + { + FancyZonesData data; + const std::wstring uuid = L""; + const std::wstring expected = L"uuid"; + const std::wstring uniqueId = L"default_device_id"; + + json::JsonArray devices; + devices.Append(m_defaultCustomDeviceValue); + json::JsonObject json; + json.SetNamedValue(L"devices", devices); + data.ParseDeviceInfos(json); + + data.SetActiveZoneSet(uniqueId, uuid); + + auto actual = data.GetDeviceInfoMap().find(uniqueId)->second; + Assert::AreEqual(expected, actual.activeZoneSet.uuid); + } + + TEST_METHOD(SetActiveZoneSetUniqueIdInvalid) + { + FancyZonesData data; + const std::wstring uuid = L"new_uuid"; + const std::wstring expected = L"uuid"; + const std::wstring uniqueId = L"id_not_contained_by_device_info_map"; + + json::JsonArray devices; + devices.Append(m_defaultCustomDeviceValue); + json::JsonObject json; + json.SetNamedValue(L"devices", devices); + bool parseRes = data.ParseDeviceInfos(json); + Assert::IsTrue(parseRes); + + data.SetActiveZoneSet(uniqueId, uuid); + + const auto& deviceInfoMap = data.GetDeviceInfoMap(); + auto actual = deviceInfoMap.find(L"default_device_id")->second; + Assert::AreEqual(expected, actual.activeZoneSet.uuid); + + Assert::IsTrue(deviceInfoMap.end() == deviceInfoMap.find(uniqueId), L"new device info should not be added"); + } + + TEST_METHOD(LoadFancyZonesDataFromJson) + { + FancyZonesData data; + const auto jsonPath = data.GetPersistFancyZonesJSONPath(); + auto savedJson = json::from_file(jsonPath); + + if (std::filesystem::exists(jsonPath)) + { + std::filesystem::remove(jsonPath); + } + + const GridLayoutInfo grid(GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{ + .rows = 1, + .columns = 3, + .rowsPercents = { 10000 }, + .columnsPercents = { 2500, 5000, 2500 }, + .cellChildMap = { { 0, 1, 2 } } })); + CustomZoneSetJSON zoneSets{ L"zone-set-uuid", CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } }; + AppZoneHistoryJSON appZoneHistory{ L"app-path", AppZoneHistoryData{ L"zone-set-uuid", 54321 } }; + DeviceInfoJSON deviceInfo{ L"uuid", DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 } }; + json::JsonArray zoneSetsArray, appZonesArray, deviceInfoArray; + zoneSetsArray.Append(CustomZoneSetJSON::ToJson(zoneSets)); + appZonesArray.Append(AppZoneHistoryJSON::ToJson(appZoneHistory)); + deviceInfoArray.Append(DeviceInfoJSON::ToJson(deviceInfo)); + json::JsonObject fancyZones; + fancyZones.SetNamedValue(L"custom-zone-sets", zoneSetsArray); + fancyZones.SetNamedValue(L"app-zone-history", appZonesArray); + fancyZones.SetNamedValue(L"devices", deviceInfoArray); + + json::to_file(jsonPath, fancyZones); + + data.LoadFancyZonesData(); + if (savedJson) + { + json::to_file(jsonPath, *savedJson); + } + else + { + std::filesystem::remove(jsonPath); + } + + Assert::IsFalse(data.GetCustomZoneSetsMap().empty()); + Assert::IsFalse(data.GetCustomZoneSetsMap().empty()); + Assert::IsFalse(data.GetCustomZoneSetsMap().empty()); + } + + TEST_METHOD(LoadFancyZonesDataFromRegistry) + { + FancyZonesData data; + const auto jsonPath = data.GetPersistFancyZonesJSONPath(); + auto savedJson = json::from_file(jsonPath); + + if (std::filesystem::exists(jsonPath)) + { + std::filesystem::remove(jsonPath); + } + + data.LoadFancyZonesData(); + bool actual = std::filesystem::exists(jsonPath); + if (savedJson) + { + json::to_file(jsonPath, *savedJson); + } + else + { + std::filesystem::remove(jsonPath); + } + + Assert::IsTrue(actual); + } + + TEST_METHOD(SaveFancyZonesData) + { + FancyZonesData data; + const auto jsonPath = data.GetPersistFancyZonesJSONPath(); + auto savedJson = json::from_file(jsonPath); + + if (std::filesystem::exists(jsonPath)) + { + std::filesystem::remove(jsonPath); + } + + data.SaveFancyZonesData(); + bool actual = std::filesystem::exists(jsonPath); + + if (savedJson) + { + json::to_file(jsonPath, *savedJson); + } + else + { + std::filesystem::remove(jsonPath); + } + + Assert::IsTrue(actual); + } + }; +} \ No newline at end of file diff --git a/src/modules/fancyzones/tests/UnitTests/RegistryHelpers.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/RegistryHelpers.Spec.cpp deleted file mode 100644 index ecfbe1d3f03f..000000000000 --- a/src/modules/fancyzones/tests/UnitTests/RegistryHelpers.Spec.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "pch.h" -#include "lib\RegistryHelpers.h" - -using namespace Microsoft::VisualStudio::CppUnitTestFramework; - -namespace FancyZonesUnitTests -{ - TEST_CLASS(RegistryHelpersUnitTests){ - public: - TEST_METHOD(GetDefaultKey){ - // Test the path to the key is the same string. - wchar_t key[256]; - Assert::AreEqual(0, wcscmp(RegistryHelpers::GetKey(nullptr, key, ARRAYSIZE(key)), L"Software\\SuperFancyZones")); -} - -TEST_METHOD(GetKeyWithMonitor) -{ - // Test the path to the key is the same string. - wchar_t key[256]; - Assert::AreEqual(0, wcscmp(RegistryHelpers::GetKey(L"Monitor1", key, ARRAYSIZE(key)), L"Software\\SuperFancyZones\\Monitor1")); -} - -TEST_METHOD(OpenKey) -{ - // The default key should exist. - wil::unique_hkey key{ RegistryHelpers::OpenKey({}) }; - Assert::IsNotNull(key.get()); - - // The Monitor1 key shouldn't exist. - wil::unique_hkey key2{ RegistryHelpers::OpenKey(L"Monitor1") }; - Assert::IsNull(key2.get()); -} -} -; -} diff --git a/src/modules/fancyzones/tests/UnitTests/UnitTests.vcxproj b/src/modules/fancyzones/tests/UnitTests/UnitTests.vcxproj index 15ec6d2ee05e..c42116b8f02b 100644 --- a/src/modules/fancyzones/tests/UnitTests/UnitTests.vcxproj +++ b/src/modules/fancyzones/tests/UnitTests/UnitTests.vcxproj @@ -96,12 +96,15 @@ + + + Create Create - + diff --git a/src/modules/fancyzones/tests/UnitTests/UnitTests.vcxproj.filters b/src/modules/fancyzones/tests/UnitTests/UnitTests.vcxproj.filters index fe17c4c652f4..71de598915ae 100644 --- a/src/modules/fancyzones/tests/UnitTests/UnitTests.vcxproj.filters +++ b/src/modules/fancyzones/tests/UnitTests/UnitTests.vcxproj.filters @@ -1,4 +1,4 @@ - + @@ -24,15 +24,24 @@ Source Files - - Source Files - Source Files Source Files + + Source Files + + + Source Files + + + Source Files + + + Source Files + diff --git a/src/modules/fancyzones/tests/UnitTests/Util.cpp b/src/modules/fancyzones/tests/UnitTests/Util.cpp new file mode 100644 index 000000000000..b0657288d65c --- /dev/null +++ b/src/modules/fancyzones/tests/UnitTests/Util.cpp @@ -0,0 +1,149 @@ +#include "pch.h" +#include "Util.h" + +static int s_classId = 0; + +namespace Mocks +{ + class HwndCreator + { + public: + HwndCreator(const std::wstring& title = L""); + + ~HwndCreator(); + + HWND operator()(HINSTANCE hInst); + + void setHwnd(HWND val); + void setCondition(bool cond); + + inline HINSTANCE getHInstance() const { return m_hInst; } + inline const std::wstring& getTitle() const { return m_windowTitle; } + inline const std::wstring& getWindowClassName() const { return m_windowClassName; } + + private: + std::wstring m_windowTitle; + std::wstring m_windowClassName; + + std::mutex m_mutex; + std::condition_variable m_conditionVar; + bool m_conditionFlag; + HANDLE m_thread; + + HINSTANCE m_hInst; + HWND m_hWnd; + }; + + HWND WindowCreate(HINSTANCE hInst) + { + return HwndCreator()(hInst); + } +} + +LRESULT CALLBACK DLLWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + if (message == WM_DESTROY) + { + PostQuitMessage(0); + return 0; + } + + return DefWindowProc(hwnd, message, wParam, lParam); +} + +BOOL RegisterDLLWindowClass(LPCWSTR szClassName, Mocks::HwndCreator* creator) +{ + if (!creator) + return false; + + WNDCLASSEX wc; + + wc.hInstance = creator->getHInstance(); + wc.lpszClassName = szClassName; + wc.lpfnWndProc = DLLWindowProc; + wc.cbSize = sizeof(WNDCLASSEX); + + wc.style = CS_DBLCLKS; + wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.lpszMenuName = NULL; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hbrBackground = (HBRUSH)COLOR_BACKGROUND; + + auto regRes = RegisterClassEx(&wc); + return regRes; +} + +DWORD WINAPI ThreadProc(LPVOID lpParam) +{ + MSG messages; + Mocks::HwndCreator* creator = reinterpret_cast(lpParam); + if (!creator) + return -1; + + if (RegisterDLLWindowClass((LPCWSTR)creator->getWindowClassName().c_str(), creator) != 0) + { + auto hWnd = CreateWindowEx(0, (LPCWSTR)creator->getWindowClassName().c_str(), (LPCWSTR)creator->getTitle().c_str(), WS_EX_APPWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 10, 10, nullptr, nullptr, creator->getHInstance(), NULL); + creator->setHwnd(hWnd); + creator->setCondition(true); + + while (GetMessage(&messages, NULL, 0, 0)) + { + TranslateMessage(&messages); + DispatchMessage(&messages); + } + + creator->setHwnd(hWnd); + } + else + { + creator->setCondition(true); + } + + return 1; +} + +namespace Mocks +{ + HwndCreator::HwndCreator(const std::wstring& title) : + m_windowTitle(title), m_windowClassName(std::to_wstring(++s_classId)), m_conditionFlag(false), m_thread(nullptr), m_hInst(HINSTANCE{}), m_hWnd(nullptr) + { + } + + HwndCreator::~HwndCreator() + { + std::unique_lock lock(m_mutex); + m_conditionVar.wait(lock, [this] { return m_conditionFlag; }); + + if (m_thread) + { + CloseHandle(m_thread); + } + } + + HWND HwndCreator::operator()(HINSTANCE hInst) + { + m_hInst = hInst; + m_conditionFlag = false; + std::unique_lock lock(m_mutex); + + m_thread = CreateThread(0, NULL, ThreadProc, (LPVOID)this, NULL, NULL); + m_conditionVar.wait(lock, [this] { return m_conditionFlag; }); + + return m_hWnd; + } + + void HwndCreator::setHwnd(HWND val) + { + m_hWnd = val; + } + + void HwndCreator::setCondition(bool cond) + { + m_conditionFlag = cond; + m_conditionVar.notify_one(); + } + +} \ No newline at end of file diff --git a/src/modules/fancyzones/tests/UnitTests/Util.h b/src/modules/fancyzones/tests/UnitTests/Util.h index 6d5ca961b8a4..b66e51381e35 100644 --- a/src/modules/fancyzones/tests/UnitTests/Util.h +++ b/src/modules/fancyzones/tests/UnitTests/Util.h @@ -1,5 +1,7 @@ #pragma once +#include "lib/JsonHelpers.h" + namespace CustomAssert { static void AreEqual(const RECT& r1, const RECT& r2) @@ -13,9 +15,9 @@ namespace CustomAssert Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(g1 == g2); } - static void AreEqual(WORD w1, WORD w2) + static void AreEqual(JSONHelpers::ZoneSetLayoutType t1, JSONHelpers::ZoneSetLayoutType t2) { - Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(w1 == w2); + Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(t1 == t2); } } @@ -39,4 +41,5 @@ namespace Mocks return reinterpret_cast(++s_nextInstance); } -} \ No newline at end of file + HWND WindowCreate(HINSTANCE hInst); +} diff --git a/src/modules/fancyzones/tests/UnitTests/Zone.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/Zone.Spec.cpp index 561e9dd67cd8..ae5be7c313da 100644 --- a/src/modules/fancyzones/tests/UnitTests/Zone.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/Zone.Spec.cpp @@ -1,5 +1,6 @@ #include "pch.h" #include "lib\Zone.h" +#include "lib\Settings.h" #include "Util.h" @@ -7,48 +8,419 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace FancyZonesUnitTests { - TEST_CLASS(ZoneUnitTests){ - public: - TEST_METHOD(TestCreateZone){ - RECT zoneRect{ 10, 10, 200, 200 }; - winrt::com_ptr zone = MakeZone(zoneRect); - Assert::IsNotNull(&zone); - CustomAssert::AreEqual(zoneRect, zone->GetZoneRect()); - - constexpr size_t id = 10; - zone->SetId(id); - Assert::AreEqual(zone->Id(), id); -} + TEST_CLASS(ZoneUnitTests) + { + private: + RECT m_zoneRect{ 10, 10, 200, 200 }; + HINSTANCE m_hInst{}; -TEST_METHOD(ContainsWindow) -{ - RECT zoneRect{ 10, 10, 200, 200 }; - winrt::com_ptr zone = MakeZone(zoneRect); - HWND newWindow = Mocks::Window(); - Assert::IsFalse(zone->ContainsWindow(newWindow)); -} + HWND addWindow(const winrt::com_ptr& zone, bool stamp) + { + HWND window = Mocks::WindowCreate(m_hInst); + HWND zoneWindow = Mocks::WindowCreate(m_hInst); + zone->AddWindowToZone(window, zoneWindow, stamp); -TEST_METHOD(TestAddRemoveWindow) -{ - RECT zoneRect{ 10, 10, 200, 200 }; - winrt::com_ptr zone = MakeZone(zoneRect); - HWND newWindow = Mocks::Window(); + return window; + } - Assert::IsFalse(zone->ContainsWindow(newWindow)); - zone->AddWindowToZone(newWindow, Mocks::Window(), true); - Assert::IsTrue(zone->ContainsWindow(newWindow)); + void addMany(const winrt::com_ptr& zone) + { + for (int i = 0; i < 10; i++) + { + addWindow(zone, i % 2 == 0); + } + } - zone->RemoveWindowFromZone(newWindow, false); - Assert::IsFalse(zone->ContainsWindow(newWindow)); -} + TEST_METHOD_INITIALIZE(Init) + { + m_hInst = (HINSTANCE)GetModuleHandleW(nullptr); + } -TEST_METHOD(TestRemoveInvalidWindow) -{ - RECT zoneRect{ 10, 10, 200, 200 }; - winrt::com_ptr zone = MakeZone(zoneRect); - HWND newWindow = Mocks::Window(); - zone->RemoveWindowFromZone(newWindow, false); -} -} -; + public: + TEST_METHOD(TestCreateZone) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + Assert::IsNotNull(&zone); + CustomAssert::AreEqual(m_zoneRect, zone->GetZoneRect()); + } + + TEST_METHOD(TestCreateZoneZeroRect) + { + RECT zoneRect{ 0, 0, 0, 0 }; + winrt::com_ptr zone = MakeZone(zoneRect); + Assert::IsNotNull(&zone); + CustomAssert::AreEqual(zoneRect, zone->GetZoneRect()); + } + + TEST_METHOD(GetSetId) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + + constexpr size_t id = 10; + zone->SetId(id); + Assert::AreEqual(zone->Id(), id); + } + + TEST_METHOD(IsEmpty) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + Assert::IsTrue(zone->IsEmpty()); + } + + TEST_METHOD(IsNonEmptyStampTrue) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + addWindow(zone, true); + + Assert::IsFalse(zone->IsEmpty()); + } + + TEST_METHOD(IsNonEmptyStampFalse) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + addWindow(zone, false); + + Assert::IsFalse(zone->IsEmpty()); + } + + TEST_METHOD(IsNonEmptyManyWindows) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + + HWND zoneWindow = Mocks::WindowCreate(m_hInst); + for (int i = 0; i < 10; i++) + { + HWND window = Mocks::WindowCreate(m_hInst); + zone->AddWindowToZone(window, zoneWindow, i % 2 == 0); + } + + Assert::IsFalse(zone->IsEmpty()); + } + + TEST_METHOD(IsNonEmptyManyZoneWindows) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + + HWND window = Mocks::WindowCreate(m_hInst); + for (int i = 0; i < 10; i++) + { + HWND zoneWindow = Mocks::WindowCreate(m_hInst); + zone->AddWindowToZone(window, zoneWindow, i % 2 == 0); + } + + Assert::IsFalse(zone->IsEmpty()); + } + + TEST_METHOD(IsNonEmptyMany) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + addMany(zone); + + Assert::IsFalse(zone->IsEmpty()); + } + + TEST_METHOD(ContainsWindowEmpty) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + HWND newWindow = Mocks::WindowCreate(m_hInst); + Assert::IsFalse(zone->ContainsWindow(newWindow)); + } + + TEST_METHOD(ContainsWindowNot) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + addMany(zone); + + HWND newWindow = Mocks::WindowCreate(m_hInst); + Assert::IsFalse(zone->ContainsWindow(newWindow)); + } + + TEST_METHOD(ContainsWindowStampTrue) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + HWND window = addWindow(zone, true); + + Assert::IsTrue(zone->ContainsWindow(window)); + } + + TEST_METHOD(ContainsWindowStampFalse) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + HWND window = addWindow(zone, false); + + Assert::IsTrue(zone->ContainsWindow(window)); + } + + TEST_METHOD(ContainsWindowManyWindows) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + + HWND zoneWindow = Mocks::WindowCreate(m_hInst); + std::vector windowVec{}; + for (int i = 0; i < 10; i++) + { + HWND window = Mocks::WindowCreate(m_hInst); + windowVec.push_back(window); + zone->AddWindowToZone(window, zoneWindow, i % 2 == 0); + } + + for (auto wnd : windowVec) + { + Assert::IsTrue(zone->ContainsWindow(wnd)); + } + } + + TEST_METHOD(ContainsWindowManyZoneWindows) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + + HWND window = Mocks::WindowCreate(m_hInst); + std::vector windowVec{}; + for (int i = 0; i < 10; i++) + { + HWND zoneWindow = Mocks::WindowCreate(m_hInst); + windowVec.push_back(window); + zone->AddWindowToZone(window, zoneWindow, i % 2 == 0); + } + + for (auto wnd : windowVec) + { + Assert::IsTrue(zone->ContainsWindow(wnd)); + } + } + + TEST_METHOD(ContainsWindowMany) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + + std::vector windowVec{}; + for (int i = 0; i < 10; i++) + { + HWND window = addWindow(zone, i % 2 == 0); + windowVec.push_back(window); + } + + for (auto wnd : windowVec) + { + Assert::IsTrue(zone->ContainsWindow(wnd)); + } + } + + TEST_METHOD(AddWindowNullptr) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + + HWND window = nullptr; + HWND zoneWindow = Mocks::WindowCreate(m_hInst); + zone->AddWindowToZone(window, zoneWindow, true); + + Assert::IsFalse(zone->IsEmpty()); + Assert::IsTrue(zone->ContainsWindow(window)); + } + + TEST_METHOD(AddWindowZoneNullptr) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + + HWND window = Mocks::WindowCreate(m_hInst); + HWND zoneWindow = nullptr; + zone->AddWindowToZone(window, zoneWindow, true); + + Assert::IsFalse(zone->IsEmpty()); + Assert::IsTrue(zone->ContainsWindow(window)); + } + + TEST_METHOD(AddManySame) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + + HWND zoneWindow = Mocks::WindowCreate(m_hInst); + HWND window = Mocks::WindowCreate(m_hInst); + for (int i = 0; i < 10; i++) + { + zone->AddWindowToZone(window, zoneWindow, i % 2 == 0); + } + + Assert::IsFalse(zone->IsEmpty()); + Assert::IsTrue(zone->ContainsWindow(window)); + } + + TEST_METHOD(AddManySameNullptr) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + + HWND zoneWindow = nullptr; + HWND window = nullptr; + for (int i = 0; i < 10; i++) + { + zone->AddWindowToZone(window, zoneWindow, i % 2 == 0); + } + + Assert::IsTrue(zone->ContainsWindow(window)); + } + + TEST_METHOD(RemoveWindowRestoreSizeTrue) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + HWND newWindow = Mocks::WindowCreate(m_hInst); + + zone->AddWindowToZone(newWindow, Mocks::WindowCreate(m_hInst), true); + Assert::IsFalse(zone->IsEmpty()); + Assert::IsTrue(zone->ContainsWindow(newWindow)); + + zone->RemoveWindowFromZone(newWindow, true); + Assert::IsTrue(zone->IsEmpty()); + Assert::IsFalse(zone->ContainsWindow(newWindow)); + } + + TEST_METHOD(RemoveWindowRestoreSizeFalse) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + HWND newWindow = Mocks::WindowCreate(m_hInst); + + zone->AddWindowToZone(newWindow, Mocks::WindowCreate(m_hInst), true); + Assert::IsFalse(zone->IsEmpty()); + Assert::IsTrue(zone->ContainsWindow(newWindow)); + + zone->RemoveWindowFromZone(newWindow, false); + Assert::IsTrue(zone->IsEmpty()); + Assert::IsFalse(zone->ContainsWindow(newWindow)); + } + + TEST_METHOD(RemoveInvalidWindowRestoreSizeTrue) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + HWND newWindow = Mocks::WindowCreate(m_hInst); + zone->RemoveWindowFromZone(newWindow, true); + + Assert::IsTrue(zone->IsEmpty()); + Assert::IsFalse(zone->ContainsWindow(newWindow)); + } + + TEST_METHOD(RemoveInvalidWindowRestoreSizeFalse) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + HWND newWindow = Mocks::WindowCreate(m_hInst); + zone->RemoveWindowFromZone(newWindow, false); + + Assert::IsTrue(zone->IsEmpty()); + Assert::IsFalse(zone->ContainsWindow(newWindow)); + } + + TEST_METHOD(RemoveNullptrWindowRestoreSizeTrue) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + HWND newWindow = nullptr; + + zone->AddWindowToZone(newWindow, Mocks::WindowCreate(m_hInst), true); + Assert::IsFalse(zone->IsEmpty()); + Assert::IsTrue(zone->ContainsWindow(newWindow)); + + zone->RemoveWindowFromZone(newWindow, true); + Assert::IsTrue(zone->IsEmpty()); + Assert::IsFalse(zone->ContainsWindow(newWindow)); + } + + TEST_METHOD(RemoveNullptrWindowRestoreSizeFalse) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + HWND newWindow = nullptr; + + zone->AddWindowToZone(newWindow, Mocks::WindowCreate(m_hInst), true); + Assert::IsFalse(zone->IsEmpty()); + Assert::IsTrue(zone->ContainsWindow(newWindow)); + + zone->RemoveWindowFromZone(newWindow, false); + Assert::IsTrue(zone->IsEmpty()); + Assert::IsFalse(zone->ContainsWindow(newWindow)); + } + + TEST_METHOD(RemoveMany) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + + std::vector windowVec{}; + for (int i = 0; i < 10; i++) + { + HWND window = addWindow(zone, i % 2 == 0); + windowVec.push_back(window); + } + + for (auto wnd : windowVec) + { + zone->RemoveWindowFromZone(wnd, true); + } + + Assert::IsTrue(zone->IsEmpty()); + } + + TEST_METHOD(RemoveManySame) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + + HWND zoneWindow = Mocks::WindowCreate(m_hInst); + HWND window = Mocks::WindowCreate(m_hInst); + for (int i = 0; i < 10; i++) + { + zone->AddWindowToZone(window, zoneWindow, i % 2 == 0); + } + + zone->RemoveWindowFromZone(window, true); + + Assert::IsTrue(zone->IsEmpty()); + Assert::IsFalse(zone->ContainsWindow(window)); + } + + TEST_METHOD(RemoveDouble) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + + HWND zoneWindow = Mocks::WindowCreate(m_hInst); + HWND window = Mocks::WindowCreate(m_hInst); + for (int i = 0; i < 10; i++) + { + zone->AddWindowToZone(window, zoneWindow, i % 2 == 0); + } + + zone->RemoveWindowFromZone(window, true); + zone->RemoveWindowFromZone(window, true); + + Assert::IsTrue(zone->IsEmpty()); + } + + TEST_METHOD(StampTrue) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + size_t expected = 123456; + zone->SetId(expected); + + HWND window = addWindow(zone, true); + + HANDLE actual = GetProp(window, ZONE_STAMP); + Assert::IsNotNull(actual); + + size_t actualVal = HandleToLong(actual); + Assert::AreEqual(expected, actualVal); + } + + TEST_METHOD(StampTrueNoId) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + + HWND window = addWindow(zone, true); + + HANDLE actual = GetProp(window, ZONE_STAMP); + Assert::IsNull(actual); + } + + TEST_METHOD(StampFalse) + { + winrt::com_ptr zone = MakeZone(m_zoneRect); + + HWND window = addWindow(zone, false); + + HANDLE actual = GetProp(window, ZONE_STAMP); + Assert::IsNull(actual); + } + }; } diff --git a/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp index 1affea841393..288ff2eb3798 100644 --- a/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp @@ -1,192 +1,1170 @@ #include "pch.h" +#include "lib\JsonHelpers.h" #include "lib\ZoneSet.h" +#include + #include "Util.h" +#include using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using TZoneSetLayoutType = JSONHelpers::ZoneSetLayoutType; namespace FancyZonesUnitTests { - TEST_CLASS(ZoneSetUnitTests){ - public: - TEST_METHOD(TestCreateZoneSet){ - GUID zoneSetId{}; - CoCreateGuid(&zoneSetId); - constexpr WORD layoutId = 0xFFFF; - - ZoneSetConfig config(zoneSetId, layoutId, Mocks::Monitor(), L"WorkAreaIn"); - winrt::com_ptr set = MakeZoneSet(config); - Assert::IsNotNull(&set); - CustomAssert::AreEqual(set->Id(), zoneSetId); - CustomAssert::AreEqual(set->LayoutId(), layoutId); -} + TEST_CLASS(ZoneSetUnitTests) + { + GUID m_id; + const TZoneSetLayoutType m_layoutType = TZoneSetLayoutType::Custom; + const PCWSTR m_resolutionKey = L"WorkAreaIn"; -TEST_METHOD(TestAddZone) -{ - ZoneSetConfig config({}, 0xFFFF, Mocks::Monitor(), L"WorkAreaIn"); - winrt::com_ptr set = MakeZoneSet(config); + winrt::com_ptr m_set; - // Add a zone - { - winrt::com_ptr zone = MakeZone({ 0, 0, 100, 100 }); - set->AddZone(zone); - auto zones = set->GetZones(); - Assert::IsTrue(zones.size() == 1); - Assert::IsTrue(zones[0] == zone); - Assert::IsTrue(zone->Id() == 1); - } - - // Add a second zone at the back. - { - winrt::com_ptr zone = MakeZone({ 0, 0, 100, 100 }); - set->AddZone(zone); - auto zones = set->GetZones(); - Assert::IsTrue(zones.size() == 2); - Assert::IsTrue(zones[1] == zone); - Assert::IsTrue(zone->Id() == 2); - } -} + TEST_METHOD_INITIALIZE(Init) + { + auto hres = CoCreateGuid(&m_id); + Assert::AreEqual(S_OK, hres); -TEST_METHOD(TestMoveWindowIntoZoneByIndex) -{ - ZoneSetConfig config({}, 0xFFFF, Mocks::Monitor(), L"WorkAreaIn"); - winrt::com_ptr set = MakeZoneSet(config); - - // Add a couple of zones. - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }); - winrt::com_ptr zone2 = MakeZone({ 0, 0, 100, 100 }); - winrt::com_ptr zone3 = MakeZone({ 0, 0, 100, 100 }); - set->AddZone(zone1); - set->AddZone(zone2); - set->AddZone(zone3); - - HWND window = Mocks::Window(); - set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 1); - Assert::IsFalse(zone1->ContainsWindow(window)); - Assert::IsTrue(zone2->ContainsWindow(window)); - Assert::IsFalse(zone3->ContainsWindow(window)); -} + ZoneSetConfig m_config = ZoneSetConfig(m_id, m_layoutType, Mocks::Monitor(), m_resolutionKey); + m_set = MakeZoneSet(m_config); + } -TEST_METHOD(TestMoveWindowIntoZoneByIndexWithNoZones) -{ - ZoneSetConfig config({}, 0xFFFF, Mocks::Monitor(), L"WorkAreaIn"); - winrt::com_ptr set = MakeZoneSet(config); + void compareZones(const winrt::com_ptr& expected, const winrt::com_ptr& actual) + { + Assert::AreEqual(expected->Id(), actual->Id()); + Assert::AreEqual(expected->GetZoneRect().left, actual->GetZoneRect().left); + Assert::AreEqual(expected->GetZoneRect().right, actual->GetZoneRect().right); + Assert::AreEqual(expected->GetZoneRect().top, actual->GetZoneRect().top); + Assert::AreEqual(expected->GetZoneRect().bottom, actual->GetZoneRect().bottom); + } - // Add a couple of zones. - HWND window = Mocks::Window(); - set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); -} + public: + TEST_METHOD(TestCreateZoneSet) + { + Assert::IsNotNull(&m_set); + CustomAssert::AreEqual(m_set->Id(), m_id); + CustomAssert::AreEqual(m_set->LayoutType(), m_layoutType); + } -TEST_METHOD(TestMoveWindowIntoZoneByIndexWithInvalidIndex) -{ - ZoneSetConfig config({}, 0xFFFF, Mocks::Monitor(), L"WorkAreaIn"); - winrt::com_ptr set = MakeZoneSet(config); - - // Add a couple of zones. - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }); - winrt::com_ptr zone2 = MakeZone({ 0, 0, 100, 100 }); - winrt::com_ptr zone3 = MakeZone({ 0, 0, 100, 100 }); - set->AddZone(zone1); - set->AddZone(zone2); - set->AddZone(zone3); - - HWND window = Mocks::Window(); - set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 100); - Assert::IsTrue(zone1->ContainsWindow(window)); - Assert::IsFalse(zone2->ContainsWindow(window)); - Assert::IsFalse(zone3->ContainsWindow(window)); -} -} -; + TEST_METHOD(TestCreateZoneSetGuidEmpty) + { + GUID zoneSetId{}; + ZoneSetConfig config(zoneSetId, m_layoutType, Mocks::Monitor(), m_resolutionKey); + winrt::com_ptr set = MakeZoneSet(config); -// MoveWindowIntoZoneByDirection is complicated enough to warrant it's own test class -TEST_CLASS(MoveWindowIntoZoneByDirectionUnitTests) -{ - winrt::com_ptr set; - winrt::com_ptr zone1; - winrt::com_ptr zone2; - winrt::com_ptr zone3; + Assert::IsNotNull(&set); + CustomAssert::AreEqual(set->Id(), zoneSetId); + CustomAssert::AreEqual(set->LayoutType(), m_layoutType); + } - TEST_METHOD_INITIALIZE(Initialize) - { - ZoneSetConfig config({}, 0xFFFF, Mocks::Monitor(), L"WorkAreaIn"); - set = MakeZoneSet(config); - - // Add a couple of zones. - zone1 = MakeZone({ 0, 0, 100, 100 }); - zone2 = MakeZone({ 0, 0, 100, 100 }); - zone3 = MakeZone({ 0, 0, 100, 100 }); - set->AddZone(zone1); - set->AddZone(zone2); - set->AddZone(zone3); - } - - TEST_METHOD(MoveWindowIntoZoneByDirectionRightNoZones) - { - HWND window = Mocks::Window(); - set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_RIGHT); - Assert::IsTrue(zone1->ContainsWindow(window)); - Assert::IsFalse(zone2->ContainsWindow(window)); - Assert::IsFalse(zone3->ContainsWindow(window)); - } - - TEST_METHOD(MoveWindowIntoZoneByDirectionLeftNoZones) - { - HWND window = Mocks::Window(); - set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_LEFT); - Assert::IsFalse(zone1->ContainsWindow(window)); - Assert::IsFalse(zone2->ContainsWindow(window)); - Assert::IsTrue(zone3->ContainsWindow(window)); - } - - TEST_METHOD(MoveWindowIntoZoneByDirectionRight) - { - HWND window = Mocks::Window(); - zone1->AddWindowToZone(window, Mocks::Window(), false /*stampZone*/); - set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_RIGHT); - Assert::IsFalse(zone1->ContainsWindow(window)); - Assert::IsTrue(zone2->ContainsWindow(window)); - Assert::IsFalse(zone3->ContainsWindow(window)); - - set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_RIGHT); - Assert::IsFalse(zone1->ContainsWindow(window)); - Assert::IsFalse(zone2->ContainsWindow(window)); - Assert::IsTrue(zone3->ContainsWindow(window)); - } - - TEST_METHOD(MoveWindowIntoZoneByDirectionLeft) - { - HWND window = Mocks::Window(); - zone3->AddWindowToZone(window, Mocks::Window(), false /*stampZone*/); - set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_LEFT); - Assert::IsFalse(zone1->ContainsWindow(window)); - Assert::IsTrue(zone2->ContainsWindow(window)); - Assert::IsFalse(zone3->ContainsWindow(window)); - - set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_LEFT); - Assert::IsTrue(zone1->ContainsWindow(window)); - Assert::IsFalse(zone2->ContainsWindow(window)); - Assert::IsFalse(zone3->ContainsWindow(window)); - } - - TEST_METHOD(MoveWindowIntoZoneByDirectionWrapAroundRight) + TEST_METHOD(TestCreateZoneSetMonitorEmpty) + { + ZoneSetConfig config(m_id, m_layoutType, nullptr, m_resolutionKey); + winrt::com_ptr set = MakeZoneSet(config); + Assert::IsNotNull(&set); + CustomAssert::AreEqual(set->Id(), m_id); + CustomAssert::AreEqual(set->LayoutType(), m_layoutType); + } + + TEST_METHOD(TestCreateZoneSetKeyEmpty) + { + ZoneSetConfig config(m_id, m_layoutType, Mocks::Monitor(), nullptr); + winrt::com_ptr set = MakeZoneSet(config); + Assert::IsNotNull(&set); + CustomAssert::AreEqual(set->Id(), m_id); + CustomAssert::AreEqual(set->LayoutType(), m_layoutType); + } + + TEST_METHOD(EmptyZones) + { + auto zones = m_set->GetZones(); + Assert::AreEqual((size_t)0, zones.size()); + } + + TEST_METHOD(AddOne) + { + winrt::com_ptr zone = MakeZone({ 0, 0, 100, 100 }); + m_set->AddZone(zone); + auto zones = m_set->GetZones(); + Assert::AreEqual((size_t)1, zones.size()); + compareZones(zone, zones[0]); + Assert::AreEqual((size_t)1, zones[0]->Id()); + } + + TEST_METHOD(AddManySame) + { + winrt::com_ptr zone = MakeZone({ 0, 0, 100, 100 }); + for (size_t i = 0; i < 1024; i++) + { + m_set->AddZone(zone); + auto zones = m_set->GetZones(); + Assert::AreEqual(i + 1, zones.size()); + compareZones(zone, zones[i]); + Assert::AreEqual(i + 1, zones[i]->Id()); + } + } + + TEST_METHOD(AddManyEqual) + { + for (size_t i = 0; i < 1024; i++) + { + winrt::com_ptr zone = MakeZone({ 0, 0, 100, 100 }); + m_set->AddZone(zone); + auto zones = m_set->GetZones(); + Assert::AreEqual(i + 1, zones.size()); + compareZones(zone, zones[i]); + Assert::AreEqual(i + 1, zones[i]->Id()); + } + } + + TEST_METHOD(AddManyDifferent) + { + for (size_t i = 0; i < 1024; i++) + { + winrt::com_ptr zone = MakeZone({ rand() % 10, rand() % 10, rand() % 100, rand() % 100 }); + m_set->AddZone(zone); + auto zones = m_set->GetZones(); + Assert::AreEqual(i + 1, zones.size()); + compareZones(zone, zones[i]); + Assert::AreEqual(i + 1, zones[i]->Id()); + } + } + + TEST_METHOD(ZoneFromPointEmpty) + { + auto actual = m_set->ZoneFromPoint(POINT{ 0, 0 }); + Assert::IsTrue(nullptr == actual); + } + + TEST_METHOD(ZoneFromPointInner) + { + const int left = 0, top = 0, right = 100, bottom = 100; + winrt::com_ptr expected = MakeZone({ left, top, right, bottom }); + m_set->AddZone(expected); + + for (int i = left + 1; i < right; i++) + { + for (int j = top + 1; j < bottom; j++) + { + auto actual = m_set->ZoneFromPoint(POINT{ i, j }); + Assert::IsTrue(actual != nullptr); + compareZones(expected, actual); + } + } + } + + TEST_METHOD(ZoneFromPointBorder) + { + const int left = 0, top = 0, right = 100, bottom = 100; + winrt::com_ptr expected = MakeZone({ left, top, right, bottom }); + m_set->AddZone(expected); + + for (int i = left; i < right; i++) + { + auto actual = m_set->ZoneFromPoint(POINT{ i, top }); + Assert::IsTrue(actual != nullptr); + compareZones(expected, actual); + } + + for (int i = top; i < bottom; i++) + { + auto actual = m_set->ZoneFromPoint(POINT{ left, i }); + Assert::IsTrue(actual != nullptr); + compareZones(expected, actual); + } + + //bottom and right borders considered to be outside + for (int i = left; i < right; i++) + { + auto actual = m_set->ZoneFromPoint(POINT{ i, bottom }); + Assert::IsTrue(nullptr == actual); + } + + for (int i = top; i < bottom; i++) + { + auto actual = m_set->ZoneFromPoint(POINT{ right, i }); + Assert::IsTrue(nullptr == actual); + } + } + + TEST_METHOD(ZoneFromPointOuter) + { + const int left = 0, top = 0, right = 100, bottom = 100; + winrt::com_ptr zone = MakeZone({ left, top, right, bottom }); + m_set->AddZone(zone); + + auto actual = m_set->ZoneFromPoint(POINT{ 101, 101 }); + Assert::IsTrue(actual == nullptr); + } + + TEST_METHOD(ZoneFromPointOverlapping) + { + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }); + m_set->AddZone(zone1); + winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }); + m_set->AddZone(zone2); + winrt::com_ptr zone3 = MakeZone({ 10, 10, 150, 150 }); + m_set->AddZone(zone3); + winrt::com_ptr zone4 = MakeZone({ 10, 10, 50, 50 }); + m_set->AddZone(zone4); + + auto actual = m_set->ZoneFromPoint(POINT{ 50, 50 }); + Assert::IsTrue(actual != nullptr); + compareZones(zone2, actual); + } + + TEST_METHOD(ZoneFromPointWithNotNormalizedRect) + { + winrt::com_ptr zone = MakeZone({ 100, 100, 0, 0 }); + m_set->AddZone(zone); + + auto actual = m_set->ZoneFromPoint(POINT{ 50, 50 }); + Assert::IsTrue(actual == nullptr); + } + + TEST_METHOD(ZoneFromPointWithZeroRect) + { + winrt::com_ptr zone = MakeZone({ 0, 0, 0, 0 }); + m_set->AddZone(zone); + + auto actual = m_set->ZoneFromPoint(POINT{ 0, 0 }); + Assert::IsTrue(actual == nullptr); + } + + TEST_METHOD(ZoneIndexFromWindow) + { + HWND window = Mocks::Window(); + HWND zoneWindow = Mocks::Window(); + + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }); + winrt::com_ptr zone2 = MakeZone({ 20, 20, 200, 200 }); + winrt::com_ptr zone3 = MakeZone({ 0, 0, 100, 100 }); + winrt::com_ptr zone4 = MakeZone({ 10, 10, 100, 100 }); + winrt::com_ptr zone5 = MakeZone({ 20, 20, 100, 100 }); + + zone3->AddWindowToZone(window, zoneWindow, true); + + m_set->AddZone(zone1); + m_set->AddZone(zone2); + m_set->AddZone(zone3); + m_set->AddZone(zone4); + m_set->AddZone(zone5); + + const int expected = 2; + auto actual = m_set->GetZoneIndexFromWindow(window); + Assert::AreEqual(expected, actual); + } + + TEST_METHOD(ZoneIndexFromWindowWithEqualWindows) + { + HWND window = Mocks::Window(); + HWND zoneWindow = Mocks::Window(); + + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }); + winrt::com_ptr zone2 = MakeZone({ 20, 20, 200, 200 }); + winrt::com_ptr zone3 = MakeZone({ 0, 0, 100, 100 }); + winrt::com_ptr zone4 = MakeZone({ 10, 10, 100, 100 }); + winrt::com_ptr zone5 = MakeZone({ 20, 20, 100, 100 }); + + zone3->AddWindowToZone(window, zoneWindow, true); + zone4->AddWindowToZone(window, zoneWindow, true); + + m_set->AddZone(zone1); + m_set->AddZone(zone2); + m_set->AddZone(zone3); + m_set->AddZone(zone4); + m_set->AddZone(zone5); + + const int expected = 2; + auto actual = m_set->GetZoneIndexFromWindow(window); + Assert::AreEqual(expected, actual); + } + + TEST_METHOD(ZoneIndexFromWindowUnknown) + { + winrt::com_ptr zone = MakeZone({ 0, 0, 100, 100 }); + HWND window = Mocks::Window(); + HWND zoneWindow = Mocks::Window(); + zone->AddWindowToZone(window, zoneWindow, true); + m_set->AddZone(zone); + + const int expected = -1; + auto actual = m_set->GetZoneIndexFromWindow(Mocks::Window()); + Assert::AreEqual(expected, actual); + } + + TEST_METHOD(ZoneIndexFromWindowNull) + { + winrt::com_ptr zone = MakeZone({ 0, 0, 100, 100 }); + HWND window = Mocks::Window(); + HWND zoneWindow = Mocks::Window(); + zone->AddWindowToZone(window, zoneWindow, true); + m_set->AddZone(zone); + + const int expected = -1; + auto actual = m_set->GetZoneIndexFromWindow(nullptr); + Assert::AreEqual(expected, actual); + } + + TEST_METHOD(MoveWindowIntoZoneByIndex) + { + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }); + winrt::com_ptr zone2 = MakeZone({ 0, 0, 100, 100 }); + winrt::com_ptr zone3 = MakeZone({ 0, 0, 100, 100 }); + m_set->AddZone(zone1); + m_set->AddZone(zone2); + m_set->AddZone(zone3); + + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 1); + Assert::IsFalse(zone1->ContainsWindow(window)); + Assert::IsTrue(zone2->ContainsWindow(window)); + Assert::IsFalse(zone3->ContainsWindow(window)); + } + + TEST_METHOD(MoveWindowIntoZoneByIndexWithNoZones) + { + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); + } + + TEST_METHOD(MoveWindowIntoZoneByIndexWithInvalidIndex) + { + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }); + winrt::com_ptr zone2 = MakeZone({ 0, 0, 100, 100 }); + winrt::com_ptr zone3 = MakeZone({ 0, 0, 100, 100 }); + m_set->AddZone(zone1); + m_set->AddZone(zone2); + m_set->AddZone(zone3); + + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 100); + Assert::IsTrue(zone1->ContainsWindow(window)); + Assert::IsFalse(zone2->ContainsWindow(window)); + Assert::IsFalse(zone3->ContainsWindow(window)); + } + + TEST_METHOD(MoveWindowIntoZoneByIndexSeveralTimesSameWindow) + { + // Add a couple of zones. + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }); + winrt::com_ptr zone2 = MakeZone({ 1, 1, 101, 101 }); + winrt::com_ptr zone3 = MakeZone({ 2, 2, 102, 102 }); + m_set->AddZone(zone1); + m_set->AddZone(zone2); + m_set->AddZone(zone3); + + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); + Assert::IsTrue(zone1->ContainsWindow(window)); + Assert::IsFalse(zone2->ContainsWindow(window)); + Assert::IsFalse(zone3->ContainsWindow(window)); + + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 1); + Assert::IsFalse(zone1->ContainsWindow(window)); + Assert::IsTrue(zone2->ContainsWindow(window)); + Assert::IsFalse(zone3->ContainsWindow(window)); + + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 2); + Assert::IsFalse(zone1->ContainsWindow(window)); + Assert::IsFalse(zone2->ContainsWindow(window)); + Assert::IsTrue(zone3->ContainsWindow(window)); + } + + TEST_METHOD(MoveWindowIntoZoneByIndexSeveralTimesSameIndex) + { + // Add a couple of zones. + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }); + winrt::com_ptr zone2 = MakeZone({ 1, 1, 101, 101 }); + winrt::com_ptr zone3 = MakeZone({ 2, 2, 102, 102 }); + m_set->AddZone(zone1); + m_set->AddZone(zone2); + m_set->AddZone(zone3); + + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); + Assert::IsTrue(zone1->ContainsWindow(window)); + Assert::IsFalse(zone2->ContainsWindow(window)); + Assert::IsFalse(zone3->ContainsWindow(window)); + } + + TEST_METHOD(MoveWindowIntoZoneByPointEmpty) + { + m_set->MoveWindowIntoZoneByPoint(Mocks::Window(), Mocks::Window(), POINT{ 0, 0 }); + } + + TEST_METHOD(MoveWindowIntoZoneByPointOuterPoint) + { + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }); + m_set->AddZone(zone1); + + auto window = Mocks::Window(); + m_set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 101, 101 }); + + Assert::IsFalse(zone1->ContainsWindow(window)); + } + + TEST_METHOD(MoveWindowIntoZoneByPointInnerPoint) + { + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }); + m_set->AddZone(zone1); + + auto window = Mocks::Window(); + m_set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 50, 50 }); + + Assert::IsTrue(zone1->ContainsWindow(window)); + } + + TEST_METHOD(MoveWindowIntoZoneByPointInnerPointOverlappingZones) + { + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }); + winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }); + m_set->AddZone(zone1); + m_set->AddZone(zone2); + + auto window = Mocks::Window(); + m_set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 50, 50 }); + + Assert::IsFalse(zone1->ContainsWindow(window)); + Assert::IsTrue(zone2->ContainsWindow(window)); + } + + TEST_METHOD(MoveWindowIntoZoneByPointDropAddWindow) + { + const auto window = Mocks::Window(); + const auto zoneWindow = Mocks::Window(); + + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }); + winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }); + + zone1->AddWindowToZone(window, zoneWindow, false); + + m_set->AddZone(zone1); + m_set->AddZone(zone2); + + m_set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 50, 50 }); + + Assert::IsFalse(zone1->ContainsWindow(window)); + Assert::IsTrue(zone2->ContainsWindow(window)); + } + + TEST_METHOD(MoveWindowIntoZoneByPointDropAddWindowToSameZone) + { + const auto window = Mocks::Window(); + const auto zoneWindow = Mocks::Window(); + + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }); + winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }); + + zone2->AddWindowToZone(window, zoneWindow, false); + + m_set->AddZone(zone1); + m_set->AddZone(zone2); + + m_set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 50, 50 }); + + Assert::IsFalse(zone1->ContainsWindow(window)); + Assert::IsTrue(zone2->ContainsWindow(window)); + } + + TEST_METHOD(MoveWindowIntoZoneByPointSeveralZonesWithSameWindow) + { + const auto window = Mocks::Window(); + const auto zoneWindow = Mocks::Window(); + + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }); + winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }); + winrt::com_ptr zone3 = MakeZone({ 20, 20, 80, 80 }); + + zone1->AddWindowToZone(window, zoneWindow, false); + zone2->AddWindowToZone(window, zoneWindow, false); + zone3->AddWindowToZone(window, zoneWindow, false); + + m_set->AddZone(zone1); + m_set->AddZone(zone2); + m_set->AddZone(zone3); + + m_set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 50, 50 }); + + Assert::IsFalse(zone1->ContainsWindow(window)); + Assert::IsFalse(zone2->ContainsWindow(window)); + Assert::IsTrue(zone3->ContainsWindow(window)); + } + }; + + // MoveWindowIntoZoneByDirection is complicated enough to warrant it's own test class + TEST_CLASS(ZoneSetsMoveWindowIntoZoneByDirectionUnitTests) { - HWND window = Mocks::Window(); - zone3->AddWindowToZone(window, Mocks::Window(), false /*stampZone*/); - set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_RIGHT); - Assert::IsTrue(zone1->ContainsWindow(window)); - Assert::IsFalse(zone2->ContainsWindow(window)); - Assert::IsFalse(zone3->ContainsWindow(window)); - } - - TEST_METHOD(MoveWindowIntoZoneByDirectionWrapAroundLeft) + winrt::com_ptr m_set; + winrt::com_ptr m_zone1; + winrt::com_ptr m_zone2; + winrt::com_ptr m_zone3; + + TEST_METHOD_INITIALIZE(Initialize) + { + ZoneSetConfig config({}, TZoneSetLayoutType::Custom, Mocks::Monitor(), L"WorkAreaIn"); + m_set = MakeZoneSet(config); + + // Add a couple of zones. + m_zone1 = MakeZone({ 0, 0, 100, 100 }); + m_zone2 = MakeZone({ 0, 0, 100, 100 }); + m_zone3 = MakeZone({ 0, 0, 100, 100 }); + m_set->AddZone(m_zone1); + m_set->AddZone(m_zone2); + m_set->AddZone(m_zone3); + } + + TEST_METHOD(EmptyZonesLeft) + { + ZoneSetConfig config({}, TZoneSetLayoutType::Custom, Mocks::Monitor(), L"WorkAreaIn"); + auto set = MakeZoneSet(config); + + set->MoveWindowIntoZoneByDirection(Mocks::Window(), Mocks::Window(), VK_LEFT); + } + + TEST_METHOD(EmptyZonesRight) + { + ZoneSetConfig config({}, TZoneSetLayoutType::Custom, Mocks::Monitor(), L"WorkAreaIn"); + auto set = MakeZoneSet(config); + + set->MoveWindowIntoZoneByDirection(Mocks::Window(), Mocks::Window(), VK_RIGHT); + } + + TEST_METHOD(MoveRightNoZones) + { + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_RIGHT); + Assert::IsTrue(m_zone1->ContainsWindow(window)); + Assert::IsFalse(m_zone2->ContainsWindow(window)); + Assert::IsFalse(m_zone3->ContainsWindow(window)); + } + + TEST_METHOD(MoveLeftNoZones) + { + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_LEFT); + Assert::IsFalse(m_zone1->ContainsWindow(window)); + Assert::IsFalse(m_zone2->ContainsWindow(window)); + Assert::IsTrue(m_zone3->ContainsWindow(window)); + } + + TEST_METHOD(MoveRightTwice) + { + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_RIGHT); + m_set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_RIGHT); + Assert::IsFalse(m_zone1->ContainsWindow(window)); + Assert::IsTrue(m_zone2->ContainsWindow(window)); + Assert::IsFalse(m_zone3->ContainsWindow(window)); + } + + TEST_METHOD(MoveLeftTwice) + { + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_LEFT); + m_set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_LEFT); + Assert::IsFalse(m_zone1->ContainsWindow(window)); + Assert::IsTrue(m_zone2->ContainsWindow(window)); + Assert::IsFalse(m_zone3->ContainsWindow(window)); + } + + TEST_METHOD(MoveRightMoreThanZonesCount) + { + HWND window = Mocks::Window(); + for (int i = 0; i <= m_set->GetZones().size(); i++) + { + m_set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_RIGHT); + } + + Assert::IsTrue(m_zone1->ContainsWindow(window)); + Assert::IsFalse(m_zone2->ContainsWindow(window)); + Assert::IsFalse(m_zone3->ContainsWindow(window)); + } + + TEST_METHOD(MoveLeftMoreThanZonesCount) + { + HWND window = Mocks::Window(); + for (int i = 0; i <= m_set->GetZones().size(); i++) + { + m_set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_LEFT); + } + + Assert::IsFalse(m_zone1->ContainsWindow(window)); + Assert::IsFalse(m_zone2->ContainsWindow(window)); + Assert::IsTrue(m_zone3->ContainsWindow(window)); + } + + TEST_METHOD(MoveWindowIntoZoneByDirectionRight) + { + HWND window = Mocks::Window(); + m_zone1->AddWindowToZone(window, Mocks::Window(), false /*stampZone*/); + m_set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_RIGHT); + Assert::IsFalse(m_zone1->ContainsWindow(window)); + Assert::IsTrue(m_zone2->ContainsWindow(window)); + Assert::IsFalse(m_zone3->ContainsWindow(window)); + + m_set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_RIGHT); + Assert::IsFalse(m_zone1->ContainsWindow(window)); + Assert::IsFalse(m_zone2->ContainsWindow(window)); + Assert::IsTrue(m_zone3->ContainsWindow(window)); + } + + TEST_METHOD(MoveRightWithSameWindowAdded) + { + HWND window = Mocks::Window(); + m_zone1->AddWindowToZone(window, Mocks::Window(), false /*stampZone*/); + m_zone2->AddWindowToZone(window, Mocks::Window(), false /*stampZone*/); + + Assert::IsTrue(m_zone1->ContainsWindow(window)); + Assert::IsTrue(m_zone2->ContainsWindow(window)); + Assert::IsFalse(m_zone3->ContainsWindow(window)); + + m_set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_RIGHT); + Assert::IsFalse(m_zone1->ContainsWindow(window)); + Assert::IsTrue(m_zone2->ContainsWindow(window)); + Assert::IsFalse(m_zone3->ContainsWindow(window)); + + m_set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_RIGHT); + Assert::IsFalse(m_zone1->ContainsWindow(window)); + Assert::IsFalse(m_zone2->ContainsWindow(window)); + Assert::IsTrue(m_zone3->ContainsWindow(window)); + } + + TEST_METHOD(MoveRightWithDifferentWindowsAdded) + { + HWND window1 = Mocks::Window(); + HWND window2 = Mocks::Window(); + m_zone1->AddWindowToZone(window1, Mocks::Window(), false /*stampZone*/); + m_zone2->AddWindowToZone(window2, Mocks::Window(), false /*stampZone*/); + + Assert::IsTrue(m_zone1->ContainsWindow(window1)); + Assert::IsFalse(m_zone2->ContainsWindow(window1)); + Assert::IsFalse(m_zone3->ContainsWindow(window1)); + Assert::IsFalse(m_zone1->ContainsWindow(window2)); + Assert::IsTrue(m_zone2->ContainsWindow(window2)); + Assert::IsFalse(m_zone3->ContainsWindow(window2)); + + m_set->MoveWindowIntoZoneByDirection(window1, Mocks::Window(), VK_RIGHT); + Assert::IsFalse(m_zone1->ContainsWindow(window1)); + Assert::IsTrue(m_zone2->ContainsWindow(window1)); + Assert::IsFalse(m_zone3->ContainsWindow(window1)); + Assert::IsFalse(m_zone1->ContainsWindow(window2)); + Assert::IsTrue(m_zone2->ContainsWindow(window2)); + Assert::IsFalse(m_zone3->ContainsWindow(window2)); + + m_set->MoveWindowIntoZoneByDirection(window1, Mocks::Window(), VK_RIGHT); + Assert::IsFalse(m_zone1->ContainsWindow(window1)); + Assert::IsFalse(m_zone2->ContainsWindow(window1)); + Assert::IsTrue(m_zone3->ContainsWindow(window1)); + Assert::IsFalse(m_zone1->ContainsWindow(window2)); + Assert::IsTrue(m_zone2->ContainsWindow(window2)); + Assert::IsFalse(m_zone3->ContainsWindow(window2)); + } + + TEST_METHOD(MoveWindowIntoZoneByDirectionLeft) + { + HWND window = Mocks::Window(); + m_zone3->AddWindowToZone(window, Mocks::Window(), false /*stampZone*/); + m_set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_LEFT); + Assert::IsFalse(m_zone1->ContainsWindow(window)); + Assert::IsTrue(m_zone2->ContainsWindow(window)); + Assert::IsFalse(m_zone3->ContainsWindow(window)); + + m_set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_LEFT); + Assert::IsTrue(m_zone1->ContainsWindow(window)); + Assert::IsFalse(m_zone2->ContainsWindow(window)); + Assert::IsFalse(m_zone3->ContainsWindow(window)); + } + + TEST_METHOD(MoveLeftWithSameWindowAdded) + { + HWND window = Mocks::Window(); + m_zone2->AddWindowToZone(window, Mocks::Window(), false /*stampZone*/); + m_zone3->AddWindowToZone(window, Mocks::Window(), false /*stampZone*/); + + Assert::IsFalse(m_zone1->ContainsWindow(window)); + Assert::IsTrue(m_zone2->ContainsWindow(window)); + Assert::IsTrue(m_zone3->ContainsWindow(window)); + + m_set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_LEFT); + Assert::IsTrue(m_zone1->ContainsWindow(window)); + Assert::IsFalse(m_zone2->ContainsWindow(window)); + Assert::IsTrue(m_zone3->ContainsWindow(window)); + + m_set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_LEFT); + Assert::IsFalse(m_zone1->ContainsWindow(window)); + Assert::IsFalse(m_zone2->ContainsWindow(window)); + Assert::IsTrue(m_zone3->ContainsWindow(window)); + } + + TEST_METHOD(MoveLeftWithDifferentWindowsAdded) + { + HWND window1 = Mocks::Window(); + HWND window2 = Mocks::Window(); + m_zone2->AddWindowToZone(window1, Mocks::Window(), false /*stampZone*/); + m_zone3->AddWindowToZone(window2, Mocks::Window(), false /*stampZone*/); + + Assert::IsFalse(m_zone1->ContainsWindow(window1)); + Assert::IsTrue(m_zone2->ContainsWindow(window1)); + Assert::IsFalse(m_zone3->ContainsWindow(window1)); + Assert::IsFalse(m_zone1->ContainsWindow(window2)); + Assert::IsFalse(m_zone2->ContainsWindow(window2)); + Assert::IsTrue(m_zone3->ContainsWindow(window2)); + + m_set->MoveWindowIntoZoneByDirection(window2, Mocks::Window(), VK_LEFT); + Assert::IsFalse(m_zone1->ContainsWindow(window1)); + Assert::IsTrue(m_zone2->ContainsWindow(window1)); + Assert::IsFalse(m_zone3->ContainsWindow(window1)); + Assert::IsFalse(m_zone1->ContainsWindow(window2)); + Assert::IsTrue(m_zone2->ContainsWindow(window2)); + Assert::IsFalse(m_zone3->ContainsWindow(window2)); + + m_set->MoveWindowIntoZoneByDirection(window2, Mocks::Window(), VK_LEFT); + Assert::IsFalse(m_zone1->ContainsWindow(window1)); + Assert::IsTrue(m_zone2->ContainsWindow(window1)); + Assert::IsFalse(m_zone3->ContainsWindow(window1)); + Assert::IsTrue(m_zone1->ContainsWindow(window2)); + Assert::IsFalse(m_zone2->ContainsWindow(window2)); + Assert::IsFalse(m_zone3->ContainsWindow(window2)); + } + + TEST_METHOD(MoveWindowIntoZoneByDirectionWrapAroundRight) + { + HWND window = Mocks::Window(); + m_zone3->AddWindowToZone(window, Mocks::Window(), false /*stampZone*/); + m_set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_RIGHT); + Assert::IsTrue(m_zone1->ContainsWindow(window)); + Assert::IsFalse(m_zone2->ContainsWindow(window)); + Assert::IsFalse(m_zone3->ContainsWindow(window)); + } + + TEST_METHOD(MoveWindowIntoZoneByDirectionWrapAroundLeft) + { + HWND window = Mocks::Window(); + m_zone1->AddWindowToZone(window, Mocks::Window(), false /*stampZone*/); + m_set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_LEFT); + Assert::IsFalse(m_zone1->ContainsWindow(window)); + Assert::IsFalse(m_zone2->ContainsWindow(window)); + Assert::IsTrue(m_zone3->ContainsWindow(window)); + } + + TEST_METHOD(MoveSecondWindowIntoSameZone) + { + HWND window1 = Mocks::Window(); + m_zone1->AddWindowToZone(window1, Mocks::Window(), false /*stampZone*/); + + HWND window2 = Mocks::Window(); + m_set->MoveWindowIntoZoneByDirection(window2, Mocks::Window(), VK_RIGHT); + + Assert::IsTrue(m_zone1->ContainsWindow(window1)); + Assert::IsFalse(m_zone2->ContainsWindow(window1)); + Assert::IsFalse(m_zone3->ContainsWindow(window1)); + + Assert::IsTrue(m_zone1->ContainsWindow(window2)); + Assert::IsFalse(m_zone2->ContainsWindow(window2)); + Assert::IsFalse(m_zone3->ContainsWindow(window2)); + } + }; + + TEST_CLASS(ZoneSetCalculateZonesUnitTests) { - HWND window = Mocks::Window(); - zone1->AddWindowToZone(window, Mocks::Window(), false /*stampZone*/); - set->MoveWindowIntoZoneByDirection(window, Mocks::Window(), VK_LEFT); - Assert::IsFalse(zone1->ContainsWindow(window)); - Assert::IsFalse(zone2->ContainsWindow(window)); - Assert::IsTrue(zone3->ContainsWindow(window)); - } -}; + GUID m_id; + const TZoneSetLayoutType m_layoutType = TZoneSetLayoutType::Custom; + const PCWSTR m_resolutionKey = L"WorkAreaIn"; + winrt::com_ptr m_set; + + HMONITOR m_monitor; + const std::array m_popularMonitors { + MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1024, .bottom = 768 } }, + MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1280, .bottom = 720 } }, + MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1280, .bottom = 800 } }, + MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1280, .bottom = 1024 } }, + MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1366, .bottom = 768 } }, + MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1440, .bottom = 900 } }, + MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1536, .bottom = 864 } }, + MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1600, .bottom = 900 } }, + MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1920, .bottom = 1080 } } + }; + + MONITORINFO m_monitorInfo; + + const std::wstring m_path = PTSettingsHelper::get_module_save_folder_location(L"FancyZones") + L"\\" + std::wstring(L"testzones.json"); + + TEST_METHOD_INITIALIZE(Init) + { + auto hres = CoCreateGuid(&m_id); + Assert::AreEqual(S_OK, hres); + + m_monitor = MonitorFromPoint(POINT{ 0, 0 }, MONITOR_DEFAULTTOPRIMARY); + + ZoneSetConfig m_config = ZoneSetConfig(m_id, m_layoutType, m_monitor, m_resolutionKey); + m_set = MakeZoneSet(m_config); + } + + TEST_METHOD_CLEANUP(Cleanup) + { + std::filesystem::remove(m_path); + } + + void checkZones(const winrt::com_ptr& set, size_t expectedCount, MONITORINFO monitorInfo) + { + auto zones = set->GetZones(); + Assert::AreEqual(expectedCount, zones.size()); + + for (const auto& zone : zones) + { + Assert::IsTrue(zone->IsEmpty()); + + const auto& zoneRect = zone->GetZoneRect(); + Assert::IsTrue(zoneRect.left >= 0, L"left border is less than zero"); + Assert::IsTrue(zoneRect.top >= 0, L"top border is less than zero"); + + Assert::IsTrue(zoneRect.left < zoneRect.right, L"rect.left >= rect.right"); + Assert::IsTrue(zoneRect.top < zoneRect.bottom, L"rect.top >= rect.bottom"); + + Assert::IsTrue(zoneRect.right <= monitorInfo.rcWork.right, L"right border is bigger than monitor work space"); + Assert::IsTrue(zoneRect.bottom <= monitorInfo.rcWork.bottom, L"bottom border is bigger than monitor work space"); + } + } + + public: + TEST_METHOD(ValidValues) + { + const int spacing = 10; + const int zoneCount = 10; + + for (int type = static_cast(JSONHelpers::ZoneSetLayoutType::Focus); type < static_cast(JSONHelpers::ZoneSetLayoutType::Custom); type++) + { + ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, m_resolutionKey); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto set = MakeZoneSet(m_config); + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, L""); + Assert::IsTrue(result); + checkZones(set, zoneCount, monitorInfo); + } + } + } + TEST_METHOD(InvalidMonitorInfo) + { + const int spacing = 10; + const int zoneCount = 10; + + for (int type = static_cast(JSONHelpers::ZoneSetLayoutType::Focus); type < static_cast(JSONHelpers::ZoneSetLayoutType::Custom); type++) + { + ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, m_resolutionKey); + auto set = MakeZoneSet(m_config); + + MONITORINFO info{}; + auto result = set->CalculateZones(info, zoneCount, spacing, L""); + Assert::IsFalse(result); + } + } + + TEST_METHOD(ZeroSpacing) + { + const int spacing = 0; + const int zoneCount = 10; + + for (int type = static_cast(JSONHelpers::ZoneSetLayoutType::Focus); type < static_cast(JSONHelpers::ZoneSetLayoutType::Custom); type++) + { + ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, m_resolutionKey); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto set = MakeZoneSet(m_config); + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, L""); + Assert::IsTrue(result); + checkZones(set, zoneCount, monitorInfo); + } + } + } + + TEST_METHOD(NegativeSpacing) + { + const int spacing = -1; + const int zoneCount = 10; + + for (int type = static_cast(JSONHelpers::ZoneSetLayoutType::Focus); type < static_cast(JSONHelpers::ZoneSetLayoutType::Custom); type++) + { + ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, m_resolutionKey); + auto set = MakeZoneSet(m_config); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, L""); + if (type == static_cast(JSONHelpers::ZoneSetLayoutType::Focus)) + { + //Focus doesn't depends on spacing + Assert::IsTrue(result); + } + else + { + Assert::IsFalse(result); + } + } + } + } + + TEST_METHOD(HorizontallyBigSpacing) + { + const int zoneCount = 10; + + for (int type = static_cast(JSONHelpers::ZoneSetLayoutType::Focus); type < static_cast(JSONHelpers::ZoneSetLayoutType::Custom); type++) + { + ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, m_resolutionKey); + auto set = MakeZoneSet(m_config); + + for (const auto& monitorInfo : m_popularMonitors) + { + const int spacing = monitorInfo.rcWork.right; + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, L""); + if (type == static_cast(JSONHelpers::ZoneSetLayoutType::Focus)) + { + //Focus doesn't depends on spacing + Assert::IsTrue(result); + } + else + { + Assert::IsFalse(result); + } + } + } + } + + TEST_METHOD(VerticallyBigSpacing) + { + const int zoneCount = 10; + + for (int type = static_cast(JSONHelpers::ZoneSetLayoutType::Focus); type < static_cast(JSONHelpers::ZoneSetLayoutType::Custom); type++) + { + ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, m_resolutionKey); + auto set = MakeZoneSet(m_config); + + for (const auto& monitorInfo : m_popularMonitors) + { + const int spacing = monitorInfo.rcWork.bottom; + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, L""); + if (type == static_cast(JSONHelpers::ZoneSetLayoutType::Focus)) + { + //Focus doesn't depends on spacing + Assert::IsTrue(result); + } + else + { + Assert::IsFalse(result); + } + } + } + } + + TEST_METHOD(ZeroZoneCount) + { + const int spacing = 10; + const int zoneCount = 0; + + for (int type = static_cast(JSONHelpers::ZoneSetLayoutType::Focus); type < static_cast(JSONHelpers::ZoneSetLayoutType::Custom); type++) + { + ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, m_resolutionKey); + auto set = MakeZoneSet(m_config); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, L""); + Assert::IsFalse(result); + } + } + } + + TEST_METHOD(BigZoneCount) + { + const int spacing = 1; + + for (int type = static_cast(JSONHelpers::ZoneSetLayoutType::Focus); type < static_cast(JSONHelpers::ZoneSetLayoutType::Custom); type++) + { + const int spacing = 10; + const int zoneCount = 40; //editor limit + + ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, m_resolutionKey); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto set = MakeZoneSet(m_config); + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, L""); + Assert::IsTrue(result); + checkZones(set, zoneCount, monitorInfo); + } + } + } + + TEST_METHOD(CustomZonesFromUnexistedFile) + { + const int spacing = 10; + const int zoneCount = 0; + + //be sure that file does not exist + if (std::filesystem::exists(m_path)) + { + std::filesystem::remove(m_path); + } + + ZoneSetConfig m_config = ZoneSetConfig(m_id, TZoneSetLayoutType::Custom, m_monitor, m_resolutionKey); + auto set = MakeZoneSet(m_config); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, m_path); + Assert::IsFalse(result); + } + } + + TEST_METHOD(CustomZoneFromEmptyFile) + { + const int spacing = 10; + const int zoneCount = 0; + + Assert::IsTrue(std::filesystem::create_directories(m_path)); + Assert::IsTrue(std::filesystem::exists(m_path)); + + ZoneSetConfig m_config = ZoneSetConfig(m_id, TZoneSetLayoutType::Custom, m_monitor, m_resolutionKey); + auto set = MakeZoneSet(m_config); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, m_path); + Assert::IsFalse(result); + } + } + + TEST_METHOD(CustomZoneFromInvalidCanvasLayoutInfo) + { + using namespace JSONHelpers; + + const std::wstring uuid = L"uuid"; + const CanvasLayoutInfo info{ -1, 100, { CanvasLayoutInfo::Rect{ -10, -10, 100, 100 }, CanvasLayoutInfo::Rect{ 50, 50, 150, 150 } } }; + CustomZoneSetJSON expected{ uuid, CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info } }; + json::to_file(m_path, CustomZoneSetJSON::ToJson(expected)); + Assert::IsTrue(std::filesystem::exists(m_path)); + + const int spacing = 10; + const auto zoneCount = info.zones.size(); + + ZoneSetConfig m_config = ZoneSetConfig(m_id, TZoneSetLayoutType::Custom, m_monitor, m_resolutionKey); + auto set = MakeZoneSet(m_config); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, m_path); + Assert::IsFalse(result); + } + } + + TEST_METHOD(CustomZoneFromInvalidGridLayoutInfo) + { + using namespace JSONHelpers; + + const std::wstring uuid = L"uuid"; + const GridLayoutInfo grid(GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{ + .rows = 1, + .columns = 3, + .rowsPercents = { -100 }, //rows percents are negative + .columnsPercents = { 2500, 2500 }, //column percents count is invalid + .cellChildMap = { { 0, 1, 2 } } })); + CustomZoneSetJSON expected{ uuid, CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } }; + json::to_file(m_path, CustomZoneSetJSON::ToJson(expected)); + Assert::IsTrue(std::filesystem::exists(m_path)); + + const int spacing = 0; + const int zoneCount = grid.rows() * grid.columns(); + + ZoneSetConfig m_config = ZoneSetConfig(m_id, TZoneSetLayoutType::Custom, m_monitor, m_resolutionKey); + auto set = MakeZoneSet(m_config); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, m_path); + Assert::IsFalse(result); + } + } + TEST_METHOD(CustomZoneFromValidCanvasLayoutInfo) + { + using namespace JSONHelpers; + + const std::wstring uuid = L"uuid"; + const CanvasLayoutInfo info{ 123, 321, { CanvasLayoutInfo::Rect{ 0, 0, 100, 100 }, CanvasLayoutInfo::Rect{ 50, 50, 150, 150 } } }; + CustomZoneSetJSON expected{ uuid, CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info } }; + json::to_file(m_path, CustomZoneSetJSON::ToJson(expected)); + Assert::IsTrue(std::filesystem::exists(m_path)); + + const int spacing = 10; + const auto zoneCount = info.zones.size(); + + ZoneSetConfig m_config = ZoneSetConfig(m_id, TZoneSetLayoutType::Custom, m_monitor, m_resolutionKey); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto set = MakeZoneSet(m_config); + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, m_path); + Assert::IsTrue(result); + checkZones(set, zoneCount, monitorInfo); + } + } + + TEST_METHOD(CustomZoneFromValidGridFullLayoutInfo) + { + using namespace JSONHelpers; + + const std::wstring uuid = L"uuid"; + const GridLayoutInfo grid(GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{ + .rows = 1, + .columns = 3, + .rowsPercents = { 10000 }, + .columnsPercents = { 2500, 5000, 2500 }, + .cellChildMap = { { 0, 1, 2 } } })); + CustomZoneSetJSON expected{ uuid, CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } }; + json::to_file(m_path, CustomZoneSetJSON::ToJson(expected)); + Assert::IsTrue(std::filesystem::exists(m_path)); + + const int spacing = 10; + const int zoneCount = grid.rows() * grid.columns(); + + ZoneSetConfig m_config = ZoneSetConfig(m_id, TZoneSetLayoutType::Custom, m_monitor, m_resolutionKey); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto set = MakeZoneSet(m_config); + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, m_path); + Assert::IsTrue(result); + checkZones(set, zoneCount, monitorInfo); + } + } + + TEST_METHOD(CustomZoneFromValidGridMinimalLayoutInfo) + { + using namespace JSONHelpers; + + const std::wstring uuid = L"uuid"; + const GridLayoutInfo grid(GridLayoutInfo(JSONHelpers::GridLayoutInfo::Minimal{ + .rows = 1, + .columns = 3 })); + CustomZoneSetJSON expected{ uuid, CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } }; + json::to_file(m_path, CustomZoneSetJSON::ToJson(expected)); + Assert::IsTrue(std::filesystem::exists(m_path)); + + const int spacing = 0; + const int zoneCount = grid.rows() * grid.columns(); + + ZoneSetConfig m_config = ZoneSetConfig(m_id, TZoneSetLayoutType::Custom, m_monitor, m_resolutionKey); + auto set = MakeZoneSet(m_config); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, m_path); + Assert::IsFalse(result); + } + } + }; } diff --git a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp index 82dac19251fd..359f845504e8 100644 --- a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp @@ -1,11 +1,36 @@ #include "pch.h" +#include + #include #include #include #include "Util.h" + +#include + using namespace Microsoft::VisualStudio::CppUnitTestFramework; +namespace +{ + std::wstring GenerateUniqueId(HMONITOR monitor, PCWSTR deviceId, PCWSTR virtualDesktopId) noexcept + { + wchar_t uniqueId[256]{}; // Parsed deviceId + resolution + virtualDesktopId + + MONITORINFOEXW mi; + mi.cbSize = sizeof(mi); + if (virtualDesktopId && GetMonitorInfo(monitor, &mi)) + { + wchar_t parsedId[256]{}; + ParseDeviceId(deviceId, parsedId, 256); + + Rect const monitorRect(mi.rcMonitor); + StringCchPrintf(uniqueId, ARRAYSIZE(uniqueId), L"%s_%d_%d_%s", parsedId, monitorRect.width(), monitorRect.height(), virtualDesktopId); + } + return std::wstring{ uniqueId }; + } +} + namespace FancyZonesUnitTests { struct MockZoneWindowHost : public winrt::implements @@ -17,10 +42,10 @@ namespace FancyZonesUnitTests { return RGB(0xFF, 0xFF, 0xFF); } - IFACEMETHODIMP_(GUID) - GetCurrentMonitorZoneSetId(HMONITOR monitor) noexcept + IFACEMETHODIMP_(IZoneSet*) + GetCurrentMonitorZoneSet(HMONITOR monitor) noexcept { - return m_guid; + return m_zoneSet; } IFACEMETHODIMP_(int) GetZoneHighlightOpacity() noexcept @@ -28,47 +53,621 @@ namespace FancyZonesUnitTests return 100; } - GUID m_guid; + IZoneSet* m_zoneSet; }; - TEST_CLASS(ZoneWindowUnitTests){ - public: + TEST_CLASS(ZoneWindowUnitTests) + { + const std::wstring m_deviceId = L"\\\\?\\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}"; + const std::wstring m_virtualDesktopId = L"MyVirtualDesktopId"; + std::wstringstream m_uniqueId; - TEST_METHOD(TestCreateZoneWindow){ - winrt::com_ptr zoneWindow = MakeZoneWindow(nullptr, Mocks::Instance(), Mocks::Monitor(), L"DeviceId", L"MyVirtualDesktopId", false); - Assert::IsNotNull(zoneWindow.get()); -} + HINSTANCE m_hInst{}; + HMONITOR m_monitor{}; + MONITORINFO m_monitorInfo{}; + MockZoneWindowHost m_zoneWindowHost{}; + IZoneWindowHost* m_hostPtr = m_zoneWindowHost.get_strong().get(); -TEST_METHOD(TestDeviceId) -{ - // Window initialization requires a valid HMONITOR - just use the primary for now. - HMONITOR pimaryMonitor = MonitorFromWindow(HWND(), MONITOR_DEFAULTTOPRIMARY); - MockZoneWindowHost host; - std::wstring expectedDeviceId = L"SomeRandomValue"; - winrt::com_ptr zoneWindow = MakeZoneWindow(dynamic_cast(&host), Mocks::Instance(), pimaryMonitor, expectedDeviceId.c_str(), L"MyVirtualDesktopId", false); + winrt::com_ptr m_zoneWindow; - Assert::AreEqual(expectedDeviceId, zoneWindow->DeviceId()); -} + JSONHelpers::FancyZonesData& m_fancyZonesData = JSONHelpers::FancyZonesDataInstance(); -TEST_METHOD(TestUniqueId) -{ - // Unique id of the format "ParsedMonitorDeviceId_MonitorWidth_MonitorHeight_VirtualDesktopId - // Example: "DELA026#5&10a58c63&0&UID16777488_1024_768_MyVirtualDesktopId" - std::wstring deviceId(L"\\\\?\\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}"); - // Window initialization requires a valid HMONITOR - just use the primary for now. - HMONITOR pimaryMonitor = MonitorFromWindow(HWND(), MONITOR_DEFAULTTOPRIMARY); - MONITORINFO info; - info.cbSize = sizeof(info); - Assert::IsTrue(GetMonitorInfo(pimaryMonitor, &info)); - - Rect monitorRect = Rect(info.rcMonitor); - std::wstringstream ss; - ss << L"DELA026#5&10a58c63&0&UID16777488_" << monitorRect.width() << "_" << monitorRect.height() << "_MyVirtualDesktopId"; - - MockZoneWindowHost host; - winrt::com_ptr zoneWindow = MakeZoneWindow(dynamic_cast(&host), Mocks::Instance(), pimaryMonitor, deviceId.c_str(), L"MyVirtualDesktopId", false); - Assert::AreEqual(zoneWindow->UniqueId().compare(ss.str()), 0); -} -} -; + std::wstring guidString() + { + GUID guid; + Assert::AreEqual(S_OK, CoCreateGuid(&guid)); + + OLECHAR* guidString; + Assert::AreEqual(S_OK, StringFromCLSID(guid, &guidString)); + + std::wstring guidStr{ guidString }; + CoTaskMemFree(guidString); + + return guidStr; + } + + TEST_METHOD_INITIALIZE(Init) + { + m_hInst = (HINSTANCE)GetModuleHandleW(nullptr); + + m_monitor = MonitorFromPoint(POINT{ 0, 0 }, MONITOR_DEFAULTTOPRIMARY); + m_monitorInfo.cbSize = sizeof(m_monitorInfo); + Assert::AreNotEqual(0, GetMonitorInfoW(m_monitor, &m_monitorInfo)); + + m_uniqueId << L"DELA026#5&10a58c63&0&UID16777488_" << m_monitorInfo.rcMonitor.right << "_" << m_monitorInfo.rcMonitor.bottom << "_MyVirtualDesktopId"; + + Assert::IsFalse(ZoneWindowUtils::GetActiveZoneSetTmpPath().empty()); + Assert::IsFalse(ZoneWindowUtils::GetAppliedZoneSetTmpPath().empty()); + Assert::IsFalse(ZoneWindowUtils::GetCustomZoneSetsTmpPath().empty()); + + Assert::IsFalse(std::filesystem::exists(ZoneWindowUtils::GetActiveZoneSetTmpPath())); + Assert::IsFalse(std::filesystem::exists(ZoneWindowUtils::GetAppliedZoneSetTmpPath())); + Assert::IsFalse(std::filesystem::exists(ZoneWindowUtils::GetCustomZoneSetsTmpPath())); + + m_fancyZonesData = JSONHelpers::FancyZonesData(); + } + + TEST_METHOD_CLEANUP(Cleanup) + { + //cleanup temp files if were created + std::filesystem::remove(ZoneWindowUtils::GetActiveZoneSetTmpPath()); + std::filesystem::remove(ZoneWindowUtils::GetAppliedZoneSetTmpPath()); + std::filesystem::remove(ZoneWindowUtils::GetCustomZoneSetsTmpPath()); + + m_zoneWindow = nullptr; + } + + winrt::com_ptr InitZoneWindowWithActiveZoneSet() + { + const auto activeZoneSetTempPath = ZoneWindowUtils::GetActiveZoneSetTmpPath(); + Logger::WriteMessage(activeZoneSetTempPath.c_str()); + Assert::IsFalse(std::filesystem::exists(activeZoneSetTempPath)); + + const auto type = JSONHelpers::ZoneSetLayoutType::Columns; + const auto expectedZoneSet = JSONHelpers::ZoneSetData{ guidString(), type, 5 }; + const auto data = JSONHelpers::DeviceInfoData{ expectedZoneSet, true, 16, 3 }; + const auto deviceInfo = JSONHelpers::DeviceInfoJSON{ m_uniqueId.str(), data }; + const auto json = JSONHelpers::DeviceInfoJSON::ToJson(deviceInfo); + json::to_file(activeZoneSetTempPath, json); + Assert::IsTrue(std::filesystem::exists(activeZoneSetTempPath)); + + return MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); + } + + void testZoneWindow(winrt::com_ptr zoneWindow) + { + const std::wstring expectedWorkArea = std::to_wstring(m_monitorInfo.rcMonitor.right) + L"_" + std::to_wstring(m_monitorInfo.rcMonitor.bottom); + + Assert::IsNotNull(zoneWindow.get()); + Assert::IsFalse(zoneWindow->IsDragEnabled()); + Assert::AreEqual(m_uniqueId.str().c_str(), zoneWindow->UniqueId().c_str()); + Assert::AreEqual(expectedWorkArea, zoneWindow->WorkAreaKey()); + } + + public: + TEST_METHOD(CreateZoneWindow) + { + m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); + testZoneWindow(m_zoneWindow); + Assert::IsNull(m_zoneWindow->ActiveZoneSet()); + } + + TEST_METHOD(CreateZoneWindowNoZoneWindowHost) + { + m_zoneWindow = MakeZoneWindow(nullptr, m_hInst, m_monitor, m_uniqueId.str(), false); + + Assert::IsNull(m_zoneWindow.get()); + } + + TEST_METHOD(CreateZoneWindowNoZoneWindowHostFlashZones) + { + m_zoneWindow = MakeZoneWindow(nullptr, m_hInst, m_monitor, m_uniqueId.str(), true); + + Assert::IsNull(m_zoneWindow.get()); + } + + TEST_METHOD(CreateZoneWindowNoHinst) + { + m_zoneWindow = MakeZoneWindow(m_hostPtr, {}, m_monitor, m_uniqueId.str(), false); + + testZoneWindow(m_zoneWindow); + Assert::IsNull(m_zoneWindow->ActiveZoneSet()); + } + + TEST_METHOD(CreateZoneWindowNoHinstFlashZones) + { + m_zoneWindow = MakeZoneWindow(m_hostPtr, {}, m_monitor, m_uniqueId.str(), true); + + testZoneWindow(m_zoneWindow); + Assert::IsNull(m_zoneWindow->ActiveZoneSet()); + } + + TEST_METHOD(CreateZoneWindowNoMonitor) + { + m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, {}, m_uniqueId.str(), false); + + Assert::IsNull(m_zoneWindow.get()); + Assert::IsNotNull(m_hostPtr); + } + + TEST_METHOD(CreateZoneWindowNoMonitorFlashZones) + { + m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, {}, m_uniqueId.str(), true); + + Assert::IsNull(m_zoneWindow.get()); + Assert::IsNotNull(m_hostPtr); + } + + TEST_METHOD(CreateZoneWindowNoDeviceId) + { + // Generate unique id without device id + std::wstring uniqueId = GenerateUniqueId(m_monitor, {}, m_virtualDesktopId.c_str()); + m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, uniqueId, false); + + const std::wstring expectedWorkArea = std::to_wstring(m_monitorInfo.rcMonitor.right) + L"_" + std::to_wstring(m_monitorInfo.rcMonitor.bottom); + const std::wstring expectedUniqueId = L"FallbackDevice_" + std::to_wstring(m_monitorInfo.rcMonitor.right) + L"_" + std::to_wstring(m_monitorInfo.rcMonitor.bottom) + L"_" + m_virtualDesktopId; + + Assert::IsNotNull(m_zoneWindow.get()); + Assert::IsFalse(m_zoneWindow->IsDragEnabled()); + Assert::AreEqual(expectedUniqueId.c_str(), m_zoneWindow->UniqueId().c_str()); + Assert::AreEqual(expectedWorkArea, m_zoneWindow->WorkAreaKey()); + Assert::IsNull(m_zoneWindow->ActiveZoneSet()); + } + + TEST_METHOD(CreateZoneWindowNoDesktopId) + { + // Generate unique id without virtual desktop id + std::wstring uniqueId = GenerateUniqueId(m_monitor, m_deviceId.c_str(), nullptr); + m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, uniqueId, false); + + const std::wstring expectedWorkArea = std::to_wstring(m_monitorInfo.rcMonitor.right) + L"_" + std::to_wstring(m_monitorInfo.rcMonitor.bottom); + Assert::IsNotNull(m_zoneWindow.get()); + Assert::IsFalse(m_zoneWindow->IsDragEnabled()); + Assert::IsTrue(m_zoneWindow->UniqueId().empty()); + Assert::IsNull(m_zoneWindow->ActiveZoneSet()); + Assert::IsNull(m_zoneWindow->ActiveZoneSet()); + } + + TEST_METHOD(CreateZoneWindowWithActiveZoneTmpFile) + { + using namespace JSONHelpers; + + const auto activeZoneSetTempPath = ZoneWindowUtils::GetActiveZoneSetTmpPath(); + + for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) + { + const auto expectedZoneSet = ZoneSetData{ guidString(), static_cast(type), 5 }; + const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; + const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data }; + const auto json = DeviceInfoJSON::ToJson(deviceInfo); + json::to_file(activeZoneSetTempPath, json); + + //temp file read on initialization + auto actual = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); + + testZoneWindow(actual); + + Assert::IsNotNull(actual->ActiveZoneSet()); + const auto actualZoneSet = actual->ActiveZoneSet()->GetZones(); + Assert::AreEqual((size_t)expectedZoneSet.zoneCount.value(), actualZoneSet.size()); + } + } + + TEST_METHOD(CreateZoneWindowWithActiveCustomZoneTmpFile) + { + using namespace JSONHelpers; + + const auto activeZoneSetTempPath = ZoneWindowUtils::GetActiveZoneSetTmpPath(); + + const ZoneSetLayoutType type = ZoneSetLayoutType::Custom; + const auto expectedZoneSet = ZoneSetData{ guidString(), type, 5 }; + const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; + const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data }; + const auto json = DeviceInfoJSON::ToJson(deviceInfo); + json::to_file(activeZoneSetTempPath, json); + + //temp file read on initialization + auto actual = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); + + testZoneWindow(actual); + + //custom zone needs temp file for applied zone + Assert::IsNotNull(actual->ActiveZoneSet()); + const auto actualZoneSet = actual->ActiveZoneSet()->GetZones(); + Assert::AreEqual((size_t)0, actualZoneSet.size()); + } + + TEST_METHOD(CreateZoneWindowWithActiveCustomZoneAppliedTmpFile) + { + using namespace JSONHelpers; + + //save required data + const auto activeZoneSetTempPath = ZoneWindowUtils::GetActiveZoneSetTmpPath(); + const auto appliedZoneSetTempPath = ZoneWindowUtils::GetAppliedZoneSetTmpPath(); + + const ZoneSetLayoutType type = ZoneSetLayoutType::Custom; + const auto expectedZoneSet = ZoneSetData{ guidString(), type, 5 }; + const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; + const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data }; + const auto json = DeviceInfoJSON::ToJson(deviceInfo); + json::to_file(activeZoneSetTempPath, json); + + const auto info = CanvasLayoutInfo{ + 100, 100, std::vector{ CanvasLayoutInfo::Rect{ 0, 0, 100, 100 } } + }; + const auto customZoneData = CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info }; + auto customZoneJson = CustomZoneSetJSON::ToJson(CustomZoneSetJSON{ guidString(), customZoneData }); + json::to_file(appliedZoneSetTempPath, customZoneJson); + + //temp file read on initialization + auto actual = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); + + testZoneWindow(actual); + + //custom zone needs temp file for applied zone + Assert::IsNotNull(actual->ActiveZoneSet()); + const auto actualZoneSet = actual->ActiveZoneSet()->GetZones(); + Assert::AreEqual((size_t)1, actualZoneSet.size()); + } + + TEST_METHOD(CreateZoneWindowWithActiveCustomZoneAppliedTmpFileWithDeletedCustomZones) + { + using namespace JSONHelpers; + + //save required data + const auto activeZoneSetTempPath = ZoneWindowUtils::GetActiveZoneSetTmpPath(); + const auto appliedZoneSetTempPath = ZoneWindowUtils::GetAppliedZoneSetTmpPath(); + const auto deletedZonesTempPath = ZoneWindowUtils::GetCustomZoneSetsTmpPath(); + + const ZoneSetLayoutType type = ZoneSetLayoutType::Custom; + const auto expectedZoneSet = ZoneSetData{ guidString(), type, 5 }; + const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; + const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data }; + const auto json = DeviceInfoJSON::ToJson(deviceInfo); + json::to_file(activeZoneSetTempPath, json); + + const auto info = CanvasLayoutInfo{ + 100, 100, std::vector{ CanvasLayoutInfo::Rect{ 0, 0, 100, 100 } } + }; + const auto customZoneData = CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info }; + const auto customZoneSet = CustomZoneSetJSON{ guidString(), customZoneData }; + auto customZoneJson = CustomZoneSetJSON::ToJson(customZoneSet); + json::to_file(appliedZoneSetTempPath, customZoneJson); + + //save same zone as deleted + json::JsonObject deletedCustomZoneSets = {}; + json::JsonArray zonesArray{}; + zonesArray.Append(json::JsonValue::CreateStringValue(customZoneSet.uuid.substr(1, customZoneSet.uuid.size() - 2).c_str())); + deletedCustomZoneSets.SetNamedValue(L"deleted-custom-zone-sets", zonesArray); + json::to_file(deletedZonesTempPath, deletedCustomZoneSets); + + //temp file read on initialization + auto actual = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); + + testZoneWindow(actual); + + Assert::IsNotNull(actual->ActiveZoneSet()); + const auto actualZoneSet = actual->ActiveZoneSet()->GetZones(); + Assert::AreEqual((size_t)1, actualZoneSet.size()); + } + + TEST_METHOD(CreateZoneWindowWithActiveCustomZoneAppliedTmpFileWithUnusedDeletedCustomZones) + { + using namespace JSONHelpers; + + //save required data + const auto activeZoneSetTempPath = ZoneWindowUtils::GetActiveZoneSetTmpPath(); + const auto appliedZoneSetTempPath = ZoneWindowUtils::GetAppliedZoneSetTmpPath(); + const auto deletedZonesTempPath = ZoneWindowUtils::GetCustomZoneSetsTmpPath(); + + const ZoneSetLayoutType type = ZoneSetLayoutType::Custom; + const auto expectedZoneSet = ZoneSetData{ guidString(), type, 5 }; + const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; + const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data }; + const auto json = DeviceInfoJSON::ToJson(deviceInfo); + json::to_file(activeZoneSetTempPath, json); + + const auto info = CanvasLayoutInfo{ + 100, 100, std::vector{ CanvasLayoutInfo::Rect{ 0, 0, 100, 100 } } + }; + const auto customZoneData = CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info }; + const auto customZoneSet = CustomZoneSetJSON{ guidString(), customZoneData }; + auto customZoneJson = CustomZoneSetJSON::ToJson(customZoneSet); + json::to_file(appliedZoneSetTempPath, customZoneJson); + + //save different zone as deleted + json::JsonObject deletedCustomZoneSets = {}; + json::JsonArray zonesArray{}; + const auto uuid = guidString(); + zonesArray.Append(json::JsonValue::CreateStringValue(uuid.substr(1, uuid.size() - 2).c_str())); + deletedCustomZoneSets.SetNamedValue(L"deleted-custom-zone-sets", zonesArray); + json::to_file(deletedZonesTempPath, deletedCustomZoneSets); + + //temp file read on initialization + auto actual = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); + + testZoneWindow(actual); + + Assert::IsNotNull(actual->ActiveZoneSet()); + const auto actualZoneSet = actual->ActiveZoneSet()->GetZones(); + Assert::AreEqual((size_t)1, actualZoneSet.size()); + } + + TEST_METHOD(MoveSizeEnter) + { + m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); + + const auto expected = S_OK; + const auto actual = m_zoneWindow->MoveSizeEnter(Mocks::Window(), true); + + Assert::AreEqual(expected, actual); + Assert::IsTrue(m_zoneWindow->IsDragEnabled()); + } + + TEST_METHOD(MoveSizeEnterTwice) + { + m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); + + const auto expected = E_INVALIDARG; + + m_zoneWindow->MoveSizeEnter(Mocks::Window(), true); + const auto actual = m_zoneWindow->MoveSizeEnter(Mocks::Window(), false); + + Assert::AreEqual(expected, actual); + Assert::IsTrue(m_zoneWindow->IsDragEnabled()); + } + + TEST_METHOD(MoveSizeUpdate) + { + m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); + + const auto expected = S_OK; + const auto actual = m_zoneWindow->MoveSizeUpdate(POINT{ 0, 0 }, true); + + Assert::AreEqual(expected, actual); + Assert::IsTrue(m_zoneWindow->IsDragEnabled()); + } + + TEST_METHOD(MoveSizeUpdatePointNegativeCoordinates) + { + m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); + + const auto expected = S_OK; + const auto actual = m_zoneWindow->MoveSizeUpdate(POINT{ -10, -10 }, true); + + Assert::AreEqual(expected, actual); + Assert::IsTrue(m_zoneWindow->IsDragEnabled()); + } + + TEST_METHOD(MoveSizeUpdatePointBigCoordinates) + { + m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); + + const auto expected = S_OK; + const auto actual = m_zoneWindow->MoveSizeUpdate(POINT{ m_monitorInfo.rcMonitor.right + 1, m_monitorInfo.rcMonitor.bottom + 1 }, true); + + Assert::AreEqual(expected, actual); + Assert::IsTrue(m_zoneWindow->IsDragEnabled()); + } + + TEST_METHOD(MoveSizeEnd) + { + auto zoneWindow = InitZoneWindowWithActiveZoneSet(); + + const auto window = Mocks::Window(); + zoneWindow->MoveSizeEnter(window, true); + + const auto expected = S_OK; + const auto actual = zoneWindow->MoveSizeEnd(window, POINT{ 0, 0 }); + Assert::AreEqual(expected, actual); + + const auto zoneSet = zoneWindow->ActiveZoneSet(); + zoneSet->MoveWindowIntoZoneByIndex(window, Mocks::Window(), false); + const auto actualZoneIndex = zoneSet->GetZoneIndexFromWindow(window); + Assert::AreNotEqual(-1, actualZoneIndex); + } + + TEST_METHOD(MoveSizeEndWindowNotAdded) + { + auto zoneWindow = InitZoneWindowWithActiveZoneSet(); + + const auto window = Mocks::Window(); + zoneWindow->MoveSizeEnter(window, true); + + const auto expected = S_OK; + const auto actual = zoneWindow->MoveSizeEnd(window, POINT{ 0, 0 }); + Assert::AreEqual(expected, actual); + + const auto zoneSet = zoneWindow->ActiveZoneSet(); + const auto actualZoneIndex = zoneSet->GetZoneIndexFromWindow(window); + Assert::AreEqual(-1, actualZoneIndex); + } + + TEST_METHOD(MoveSizeEndDifferentWindows) + { + m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); + + const auto window = Mocks::Window(); + m_zoneWindow->MoveSizeEnter(window, true); + + const auto expected = E_INVALIDARG; + const auto actual = m_zoneWindow->MoveSizeEnd(Mocks::Window(), POINT{ 0, 0 }); + + Assert::AreEqual(expected, actual); + } + + TEST_METHOD(MoveSizeEndWindowNotSet) + { + m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); + + const auto expected = E_INVALIDARG; + const auto actual = m_zoneWindow->MoveSizeEnd(Mocks::Window(), POINT{ 0, 0 }); + + Assert::AreEqual(expected, actual); + } + + TEST_METHOD(MoveSizeEndInvalidPoint) + { + auto zoneWindow = InitZoneWindowWithActiveZoneSet(); + + const auto window = Mocks::Window(); + zoneWindow->MoveSizeEnter(window, true); + + const auto expected = S_OK; + const auto actual = zoneWindow->MoveSizeEnd(window, POINT{ -1, -1 }); + Assert::AreEqual(expected, actual); + + const auto zoneSet = zoneWindow->ActiveZoneSet(); + zoneSet->MoveWindowIntoZoneByIndex(window, Mocks::Window(), false); + const auto actualZoneIndex = zoneSet->GetZoneIndexFromWindow(window); + Assert::AreNotEqual(-1, actualZoneIndex); //with invalid point zone remains the same + } + + TEST_METHOD(MoveSizeCancel) + { + m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); + + const auto expected = S_OK; + const auto actual = m_zoneWindow->MoveSizeCancel(); + + Assert::AreEqual(expected, actual); + } + + TEST_METHOD(MoveWindowIntoZoneByIndexNoActiveZoneSet) + { + m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); + Assert::IsNull(m_zoneWindow->ActiveZoneSet()); + + m_zoneWindow->MoveWindowIntoZoneByIndex(Mocks::Window(), 0); + } + + TEST_METHOD(MoveWindowIntoZoneByIndex) + { + m_zoneWindow = InitZoneWindowWithActiveZoneSet(); + Assert::IsNotNull(m_zoneWindow->ActiveZoneSet()); + + m_zoneWindow->MoveWindowIntoZoneByIndex(Mocks::Window(), 0); + + const auto actual = m_zoneWindow->ActiveZoneSet(); + } + + TEST_METHOD(MoveWindowIntoZoneByDirectionNoActiveZoneSet) + { + m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); + Assert::IsNull(m_zoneWindow->ActiveZoneSet()); + + m_zoneWindow->MoveWindowIntoZoneByIndex(Mocks::Window(), 0); + } + + TEST_METHOD(MoveWindowIntoZoneByDirection) + { + m_zoneWindow = InitZoneWindowWithActiveZoneSet(); + Assert::IsNotNull(m_zoneWindow->ActiveZoneSet()); + + const auto window = Mocks::WindowCreate(m_hInst); + m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT); + + const auto actualAppZoneHistory = m_fancyZonesData.GetAppZoneHistoryMap(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + Assert::AreEqual(0, actualAppZoneHistory.begin()->second.zoneIndex); + } + + TEST_METHOD(MoveWindowIntoZoneByDirectionManyTimes) + { + m_zoneWindow = InitZoneWindowWithActiveZoneSet(); + Assert::IsNotNull(m_zoneWindow->ActiveZoneSet()); + + const auto window = Mocks::WindowCreate(m_hInst); + m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT); + m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT); + m_zoneWindow->MoveWindowIntoZoneByDirection(window, VK_RIGHT); + + const auto actualAppZoneHistory = m_fancyZonesData.GetAppZoneHistoryMap(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + Assert::AreEqual(2, actualAppZoneHistory.begin()->second.zoneIndex); + } + + TEST_METHOD(SaveWindowProcessToZoneIndexNoActiveZoneSet) + { + m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); + Assert::IsNull(m_zoneWindow->ActiveZoneSet()); + + m_zoneWindow->SaveWindowProcessToZoneIndex(Mocks::Window()); + + const auto actualAppZoneHistory = m_fancyZonesData.GetAppZoneHistoryMap(); + Assert::IsTrue(actualAppZoneHistory.empty()); + } + + TEST_METHOD(SaveWindowProcessToZoneIndexNullptrWindow) + { + m_zoneWindow = InitZoneWindowWithActiveZoneSet(); + Assert::IsNotNull(m_zoneWindow->ActiveZoneSet()); + + m_zoneWindow->SaveWindowProcessToZoneIndex(nullptr); + + const auto actualAppZoneHistory = m_fancyZonesData.GetAppZoneHistoryMap(); + Assert::IsTrue(actualAppZoneHistory.empty()); + } + + TEST_METHOD(SaveWindowProcessToZoneIndexNoWindowAdded) + { + m_zoneWindow = InitZoneWindowWithActiveZoneSet(); + Assert::IsNotNull(m_zoneWindow->ActiveZoneSet()); + + auto window = Mocks::WindowCreate(m_hInst); + auto zone = MakeZone(RECT{ 0, 0, 100, 100 }); + m_zoneWindow->ActiveZoneSet()->AddZone(zone); + + m_zoneWindow->SaveWindowProcessToZoneIndex(window); + + const auto actualAppZoneHistory = m_fancyZonesData.GetAppZoneHistoryMap(); + Assert::IsTrue(actualAppZoneHistory.empty()); + } + + TEST_METHOD(SaveWindowProcessToZoneIndexNoWindowAddedWithFilledAppZoneHistory) + { + m_zoneWindow = InitZoneWindowWithActiveZoneSet(); + Assert::IsNotNull(m_zoneWindow->ActiveZoneSet()); + + const auto window = Mocks::WindowCreate(m_hInst); + const auto processPath = get_process_path(window); + + //fill app zone history map + Assert::IsTrue(m_fancyZonesData.SetAppLastZone(window, processPath.c_str(), 0)); + Assert::AreEqual((size_t)1, m_fancyZonesData.GetAppZoneHistoryMap().size()); + Assert::AreEqual(0, m_fancyZonesData.GetAppZoneHistoryMap().begin()->second.zoneIndex); + + //add zone without window + const auto zone = MakeZone(RECT{ 0, 0, 100, 100 }); + m_zoneWindow->ActiveZoneSet()->AddZone(zone); + + m_zoneWindow->SaveWindowProcessToZoneIndex(window); + Assert::AreEqual((size_t)1, m_fancyZonesData.GetAppZoneHistoryMap().size()); + Assert::AreEqual(0, m_fancyZonesData.GetAppZoneHistoryMap().begin()->second.zoneIndex); + } + + TEST_METHOD(SaveWindowProcessToZoneIndexWindowAdded) + { + m_zoneWindow = InitZoneWindowWithActiveZoneSet(); + Assert::IsNotNull(m_zoneWindow->ActiveZoneSet()); + + auto window = Mocks::WindowCreate(m_hInst); + const auto processPath = get_process_path(window); + + auto zone = MakeZone(RECT{ 0, 0, 100, 100 }); + zone->AddWindowToZone(window, Mocks::Window(), false); + m_zoneWindow->ActiveZoneSet()->AddZone(zone); + + //fill app zone history map + Assert::IsTrue(m_fancyZonesData.SetAppLastZone(window, processPath.c_str(), 2)); + Assert::AreEqual((size_t)1, m_fancyZonesData.GetAppZoneHistoryMap().size()); + Assert::AreEqual(2, m_fancyZonesData.GetAppZoneHistoryMap().begin()->second.zoneIndex); + + m_zoneWindow->SaveWindowProcessToZoneIndex(window); + + const auto actualAppZoneHistory = m_fancyZonesData.GetAppZoneHistoryMap(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + Assert::AreEqual(m_zoneWindow->ActiveZoneSet()->GetZoneIndexFromWindow(window), actualAppZoneHistory.begin()->second.zoneIndex); + } + }; } From 06adfe4d0a113af32e8675d43f36a510ca341dbb Mon Sep 17 00:00:00 2001 From: Stefan S Date: Sat, 1 Feb 2020 18:22:22 +0100 Subject: [PATCH 02/32] Address PR comment: Remove redundant check --- src/modules/fancyzones/lib/FancyZones.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/fancyzones/lib/FancyZones.cpp b/src/modules/fancyzones/lib/FancyZones.cpp index b5c1b96b4801..77dca87d1433 100644 --- a/src/modules/fancyzones/lib/FancyZones.cpp +++ b/src/modules/fancyzones/lib/FancyZones.cpp @@ -82,7 +82,7 @@ struct FancyZones : public winrt::implementssecond->ActiveZoneSet()) + if (auto it = m_zoneWindowMap.find(monitor); it != m_zoneWindowMap.end()) { return it->second->ActiveZoneSet(); } From 89a656ba18e56e07b1ed5a2baa889ea31f138ee4 Mon Sep 17 00:00:00 2001 From: Stefan S Date: Sat, 1 Feb 2020 19:25:06 +0100 Subject: [PATCH 03/32] Addres PR comment: Remove unused Dpi and add CmdArgs enum --- .../FancyZonesEditor/Models/Settings.cs | 47 +++++++------------ src/modules/fancyzones/lib/FancyZones.cpp | 7 ++- 2 files changed, 20 insertions(+), 34 deletions(-) diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs index 35bca6e9f18b..1a1ad7d559a2 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs @@ -19,6 +19,16 @@ namespace FancyZonesEditor // Other UIs in the editor will subscribe to change events on the properties to stay up to date as these properties change public class Settings : INotifyPropertyChanged { + private enum CmdArgs + { + MonitorHandle = 1, + X_Y_Width_Height, + ResolutionKey, + ActiveZoneSetTmpFile, + AppliedZoneSetTmpFile, + CustomZoneSetsTmpFile, + } + private static CanvasLayoutModel _blankCustomModel; private readonly CanvasLayoutModel _focusModel; private readonly GridLayoutModel _rowsModel; @@ -282,8 +292,6 @@ public static string CustomZoneSetsTmpFile public static string WorkAreaKey { get; private set; } - public static float Dpi { get; private set; } - // UpdateLayoutModels // Update the five default layouts based on the new ZoneCount private void UpdateLayoutModels() @@ -427,24 +435,16 @@ private void ParseCommandLineArgs() { _workArea = SystemParameters.WorkArea; Monitor = 0; - Dpi = 1; string[] args = Environment.GetCommandLineArgs(); - if (args.Length == 8) + if (args.Length == 7) { - // 1 = handle to monitor (passed back to engine to persist data) - // 2 = X_Y_Width_Height in a dpi-scaled-but-unaware coords (where EditorOverlay shows up) - // 3 = resolution key (passed back to engine to persist data) - // 4 = monitor DPI (float) - // 5 = temp file for active zone set - // 6 = temp file for applied zone set - // 7 = temp file for custom zone sets - if (uint.TryParse(args[1], out uint monitor)) + if (uint.TryParse(args[(int)CmdArgs.MonitorHandle], out uint monitor)) { Monitor = monitor; } - var parsedLocation = args[2].Split('_'); + var parsedLocation = args[(int)CmdArgs.X_Y_Width_Height].Split('_'); var x = int.Parse(parsedLocation[0]); var y = int.Parse(parsedLocation[1]); var width = int.Parse(parsedLocation[2]); @@ -452,24 +452,11 @@ private void ParseCommandLineArgs() _workArea = new Rect(x, y, width, height); - WorkAreaKey = args[3]; - - // Try invariant culture first, caller likely uses invariant i.e. "C" locale to construct parameters - foreach (var cultureInfo in new[] { CultureInfo.InvariantCulture, CultureInfo.CurrentCulture, CultureInfo.CurrentUICulture }) - { - try - { - Dpi = float.Parse(args[4], cultureInfo); - break; - } - catch (FormatException) - { - } - } + WorkAreaKey = args[(int)CmdArgs.ResolutionKey]; - _activeZoneSetTmpFile = args[5]; - _appliedZoneSetTmpFile = args[6]; - _customZoneSetsTmpFile = args[7]; + _activeZoneSetTmpFile = args[(int)CmdArgs.ActiveZoneSetTmpFile]; + _appliedZoneSetTmpFile = args[(int)CmdArgs.AppliedZoneSetTmpFile]; + _customZoneSetsTmpFile = args[(int)CmdArgs.CustomZoneSetsTmpFile]; ParseDeviceInfoData(); } diff --git a/src/modules/fancyzones/lib/FancyZones.cpp b/src/modules/fancyzones/lib/FancyZones.cpp index 77dca87d1433..afe0f81e054c 100644 --- a/src/modules/fancyzones/lib/FancyZones.cpp +++ b/src/modules/fancyzones/lib/FancyZones.cpp @@ -425,10 +425,9 @@ void FancyZones::ToggleEditor() noexcept /*1*/ std::to_wstring(reinterpret_cast(monitor)) + L" " + /*2*/ editorLocation + L" " + /*3*/ zoneWindow->WorkAreaKey() + L" " + - /*4*/ std::to_wstring(static_cast(dpi_x) / DPIAware::DEFAULT_DPI) + L" " + - /*5*/ ZoneWindowUtils::GetActiveZoneSetTmpPath() + L" " + - /*6*/ ZoneWindowUtils::GetAppliedZoneSetTmpPath() + L" " + - /*7*/ ZoneWindowUtils::GetCustomZoneSetsTmpPath(); + /*4*/ ZoneWindowUtils::GetActiveZoneSetTmpPath() + L" " + + /*5*/ ZoneWindowUtils::GetAppliedZoneSetTmpPath() + L" " + + /*6*/ ZoneWindowUtils::GetCustomZoneSetsTmpPath(); SHELLEXECUTEINFO sei{ sizeof(sei) }; sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI }; From eb3172e5b69a01e46d347e207f2b5a8cfd753ea9 Mon Sep 17 00:00:00 2001 From: Stefan S Date: Sat, 1 Feb 2020 19:58:22 +0100 Subject: [PATCH 04/32] Address PR comment: Make methods const and inline --- src/modules/fancyzones/lib/JsonHelpers.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/fancyzones/lib/JsonHelpers.h b/src/modules/fancyzones/lib/JsonHelpers.h index 916529e2ad29..757292f48ff0 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.h +++ b/src/modules/fancyzones/lib/JsonHelpers.h @@ -182,17 +182,17 @@ namespace JSONHelpers return deviceInfoMap; } - const TCustomZoneSetsMap& GetCustomZoneSetsMap() + inline const TCustomZoneSetsMap& GetCustomZoneSetsMap() const { return customZoneSetsMap; } - const TAppZoneHistoryMap& GetAppZoneHistoryMap() + inline const TAppZoneHistoryMap& GetAppZoneHistoryMap() const { return appZoneHistoryMap; } - const TDeviceID GetActiveDeviceId() + inline const TDeviceID GetActiveDeviceId() const { return activeDeviceId; } From b66771cec6d2d1ef9537ee72572670dc6f51d7aa Mon Sep 17 00:00:00 2001 From: vldmr11080 z Date: Sun, 2 Feb 2020 11:37:02 +0100 Subject: [PATCH 05/32] Address PR comments: Expose GenerateUniqueId function and use const ref instead of passing wstring by value --- src/modules/fancyzones/lib/FancyZones.cpp | 3 --- src/modules/fancyzones/lib/FancyZones.h | 3 +++ src/modules/fancyzones/lib/ZoneWindow.cpp | 6 +++--- src/modules/fancyzones/lib/ZoneWindow.h | 2 +- .../tests/UnitTests/ZoneWindow.Spec.cpp | 21 +------------------ 5 files changed, 8 insertions(+), 27 deletions(-) diff --git a/src/modules/fancyzones/lib/FancyZones.cpp b/src/modules/fancyzones/lib/FancyZones.cpp index afe0f81e054c..aa7e7f6a2f15 100644 --- a/src/modules/fancyzones/lib/FancyZones.cpp +++ b/src/modules/fancyzones/lib/FancyZones.cpp @@ -169,9 +169,6 @@ struct FancyZones : public winrt::implements MakeFancyZones(HINSTANCE hinstance, const winrt::com_ptr& settings) noexcept; + +// Generate unique identifier which will be assigned to all ZoneWindow objects +std::wstring GenerateUniqueId(HMONITOR monitor, PCWSTR deviceId, PCWSTR virtualDesktopId) noexcept; diff --git a/src/modules/fancyzones/lib/ZoneWindow.cpp b/src/modules/fancyzones/lib/ZoneWindow.cpp index 7f78a4a9d1f6..1384de8154dc 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.cpp +++ b/src/modules/fancyzones/lib/ZoneWindow.cpp @@ -258,7 +258,7 @@ struct ZoneWindow : public winrt::implements { public: ZoneWindow(HINSTANCE hinstance); - bool Init(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, std::wstring uniqueId, bool flashZones); + bool Init(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, const std::wstring& uniqueId, bool flashZones); IFACEMETHODIMP MoveSizeEnter(HWND window, bool dragEnabled) noexcept; IFACEMETHODIMP MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled) noexcept; @@ -320,7 +320,7 @@ ZoneWindow::ZoneWindow(HINSTANCE hinstance) RegisterClassExW(&wcex); } -bool ZoneWindow::Init(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, std::wstring uniqueId, bool flashZones) +bool ZoneWindow::Init(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, const std::wstring& uniqueId, bool flashZones) { if (!host) { @@ -762,7 +762,7 @@ LRESULT CALLBACK ZoneWindow::s_WndProc(HWND window, UINT message, WPARAM wparam, DefWindowProc(window, message, wparam, lparam); } -winrt::com_ptr MakeZoneWindow(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, std::wstring uniqueId, bool flashZones) noexcept +winrt::com_ptr MakeZoneWindow(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, const std::wstring& uniqueId, bool flashZones) noexcept { auto self = winrt::make_self(hinstance); if (self->Init(host, hinstance, monitor, uniqueId, flashZones)) diff --git a/src/modules/fancyzones/lib/ZoneWindow.h b/src/modules/fancyzones/lib/ZoneWindow.h index 2ed484910740..3ad1ef0b6d9d 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.h +++ b/src/modules/fancyzones/lib/ZoneWindow.h @@ -26,4 +26,4 @@ interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IZoneWindow }; winrt::com_ptr MakeZoneWindow(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, - std::wstring uniqueId, bool flashZones) noexcept; + const std::wstring& uniqueId, bool flashZones) noexcept; diff --git a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp index 359f845504e8..2b09dc50a958 100644 --- a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp @@ -5,32 +5,13 @@ #include #include #include +#include #include "Util.h" #include using namespace Microsoft::VisualStudio::CppUnitTestFramework; -namespace -{ - std::wstring GenerateUniqueId(HMONITOR monitor, PCWSTR deviceId, PCWSTR virtualDesktopId) noexcept - { - wchar_t uniqueId[256]{}; // Parsed deviceId + resolution + virtualDesktopId - - MONITORINFOEXW mi; - mi.cbSize = sizeof(mi); - if (virtualDesktopId && GetMonitorInfo(monitor, &mi)) - { - wchar_t parsedId[256]{}; - ParseDeviceId(deviceId, parsedId, 256); - - Rect const monitorRect(mi.rcMonitor); - StringCchPrintf(uniqueId, ARRAYSIZE(uniqueId), L"%s_%d_%d_%s", parsedId, monitorRect.width(), monitorRect.height(), virtualDesktopId); - } - return std::wstring{ uniqueId }; - } -} - namespace FancyZonesUnitTests { struct MockZoneWindowHost : public winrt::implements From a2efd2995f6d4f849cddbb4b069f4d531e8142dd Mon Sep 17 00:00:00 2001 From: Stefan S Date: Sun, 2 Feb 2020 12:27:04 +0100 Subject: [PATCH 06/32] Address PR comment: Use lamdba as callback --- src/modules/fancyzones/lib/JsonHelpers.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/modules/fancyzones/lib/JsonHelpers.cpp b/src/modules/fancyzones/lib/JsonHelpers.cpp index 7f98d34cbfc8..ba80d8eb5098 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.cpp +++ b/src/modules/fancyzones/lib/JsonHelpers.cpp @@ -10,15 +10,6 @@ namespace { - using TMonitors = std::vector; - - BOOL CALLBACK CollectMonitorsData(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) - { - TMonitors* monitors = reinterpret_cast(dwData); - monitors->push_back(hMonitor); - return true; - } - // From Settings.cs constexpr int c_focusModelId = 0xFFFF; constexpr int c_rowsModelId = 0xFFFE; @@ -26,7 +17,6 @@ namespace constexpr int c_gridModelId = 0xFFFC; constexpr int c_priorityGridModelId = 0xFFFB; constexpr int c_blankCustomModelId = 0xFFFA; - } namespace JSONHelpers @@ -485,8 +475,15 @@ namespace JSONHelpers void FancyZonesData::MigrateAppZoneHistoryFromRegistry() { - TMonitors monitors; - EnumDisplayMonitors(NULL, NULL, &CollectMonitorsData, reinterpret_cast(&monitors)); + auto collectMonitorsData = [](HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) -> BOOL { + std::vector* monitors = reinterpret_cast*>(dwData); + monitors->push_back(hMonitor); + return true; + }; + + std::vector monitors; + + EnumDisplayMonitors(NULL, NULL, collectMonitorsData, reinterpret_cast(&monitors)); for (HMONITOR monitor : monitors) { From e1424da4b8cff582bcfeb306059b26b916bac434 Mon Sep 17 00:00:00 2001 From: vldmr11080 z Date: Sun, 2 Feb 2020 13:06:04 +0100 Subject: [PATCH 07/32] Address PR comment: Move GenerateUniqueId to ZoneWindowUtils namespace --- src/modules/fancyzones/lib/FancyZones.cpp | 19 +------------------ src/modules/fancyzones/lib/FancyZones.h | 3 --- src/modules/fancyzones/lib/ZoneWindow.cpp | 17 +++++++++++++++++ src/modules/fancyzones/lib/ZoneWindow.h | 1 + .../tests/UnitTests/ZoneWindow.Spec.cpp | 4 ++-- 5 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/modules/fancyzones/lib/FancyZones.cpp b/src/modules/fancyzones/lib/FancyZones.cpp index aa7e7f6a2f15..91148d2280ca 100644 --- a/src/modules/fancyzones/lib/FancyZones.cpp +++ b/src/modules/fancyzones/lib/FancyZones.cpp @@ -591,7 +591,7 @@ void FancyZones::AddZoneWindow(HMONITOR monitor, PCWSTR deviceId) noexcept wil::unique_cotaskmem_string virtualDesktopId; if (SUCCEEDED_LOG(StringFromCLSID(m_currentVirtualDesktopId, &virtualDesktopId))) { - std::wstring uniqueId = GenerateUniqueId(monitor, deviceId, virtualDesktopId.get()); + std::wstring uniqueId = ZoneWindowUtils::GenerateUniqueId(monitor, deviceId, virtualDesktopId.get()); bool newVirtualDesktop = true; if (auto it = m_virtualDesktopIds.find(m_currentVirtualDesktopId); it != end(m_virtualDesktopIds)) { @@ -947,23 +947,6 @@ void FancyZones::HandleVirtualDesktopUpdates(HANDLE fancyZonesDestroyedEvent) no } } -std::wstring GenerateUniqueId(HMONITOR monitor, PCWSTR deviceId, PCWSTR virtualDesktopId) noexcept -{ - wchar_t uniqueId[256]{}; // Parsed deviceId + resolution + virtualDesktopId - - MONITORINFOEXW mi; - mi.cbSize = sizeof(mi); - if (virtualDesktopId && GetMonitorInfo(monitor, &mi)) - { - wchar_t parsedId[256]{}; - ParseDeviceId(deviceId, parsedId, 256); - - Rect const monitorRect(mi.rcMonitor); - StringCchPrintf(uniqueId, ARRAYSIZE(uniqueId), L"%s_%d_%d_%s", parsedId, monitorRect.width(), monitorRect.height(), virtualDesktopId); - } - return std::wstring{ uniqueId }; -} - winrt::com_ptr MakeFancyZones(HINSTANCE hinstance, const winrt::com_ptr& settings) noexcept { if (!settings) diff --git a/src/modules/fancyzones/lib/FancyZones.h b/src/modules/fancyzones/lib/FancyZones.h index 41eb59d3b8e7..d9a6e039abc6 100644 --- a/src/modules/fancyzones/lib/FancyZones.h +++ b/src/modules/fancyzones/lib/FancyZones.h @@ -32,6 +32,3 @@ interface __declspec(uuid("{5C8D99D6-34B2-4F4A-A8E5-7483F6869775}")) IZoneWindow }; winrt::com_ptr MakeFancyZones(HINSTANCE hinstance, const winrt::com_ptr& settings) noexcept; - -// Generate unique identifier which will be assigned to all ZoneWindow objects -std::wstring GenerateUniqueId(HMONITOR monitor, PCWSTR deviceId, PCWSTR virtualDesktopId) noexcept; diff --git a/src/modules/fancyzones/lib/ZoneWindow.cpp b/src/modules/fancyzones/lib/ZoneWindow.cpp index 1384de8154dc..98b385794835 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.cpp +++ b/src/modules/fancyzones/lib/ZoneWindow.cpp @@ -62,6 +62,23 @@ namespace ZoneWindowUtils return customZoneSetsTmpFileName; } + + std::wstring GenerateUniqueId(HMONITOR monitor, PCWSTR deviceId, PCWSTR virtualDesktopId) + { + wchar_t uniqueId[256]{}; // Parsed deviceId + resolution + virtualDesktopId + + MONITORINFOEXW mi; + mi.cbSize = sizeof(mi); + if (virtualDesktopId && GetMonitorInfo(monitor, &mi)) + { + wchar_t parsedId[256]{}; + ParseDeviceId(deviceId, parsedId, 256); + + Rect const monitorRect(mi.rcMonitor); + StringCchPrintf(uniqueId, ARRAYSIZE(uniqueId), L"%s_%d_%d_%s", parsedId, monitorRect.width(), monitorRect.height(), virtualDesktopId); + } + return std::wstring{ uniqueId }; + } } namespace ZoneWindowDrawUtils diff --git a/src/modules/fancyzones/lib/ZoneWindow.h b/src/modules/fancyzones/lib/ZoneWindow.h index 3ad1ef0b6d9d..93a195ae6507 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.h +++ b/src/modules/fancyzones/lib/ZoneWindow.h @@ -7,6 +7,7 @@ namespace ZoneWindowUtils const std::wstring& GetActiveZoneSetTmpPath(); const std::wstring& GetAppliedZoneSetTmpPath(); const std::wstring& GetCustomZoneSetsTmpPath(); + std::wstring GenerateUniqueId(HMONITOR monitor, PCWSTR deviceId, PCWSTR virtualDesktopId); } interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IZoneWindow : public IUnknown diff --git a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp index 2b09dc50a958..d59a767a3132 100644 --- a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp @@ -182,7 +182,7 @@ namespace FancyZonesUnitTests TEST_METHOD(CreateZoneWindowNoDeviceId) { // Generate unique id without device id - std::wstring uniqueId = GenerateUniqueId(m_monitor, {}, m_virtualDesktopId.c_str()); + std::wstring uniqueId = ZoneWindowUtils::GenerateUniqueId(m_monitor, nullptr, m_virtualDesktopId.c_str()); m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, uniqueId, false); const std::wstring expectedWorkArea = std::to_wstring(m_monitorInfo.rcMonitor.right) + L"_" + std::to_wstring(m_monitorInfo.rcMonitor.bottom); @@ -198,7 +198,7 @@ namespace FancyZonesUnitTests TEST_METHOD(CreateZoneWindowNoDesktopId) { // Generate unique id without virtual desktop id - std::wstring uniqueId = GenerateUniqueId(m_monitor, m_deviceId.c_str(), nullptr); + std::wstring uniqueId = ZoneWindowUtils::GenerateUniqueId(m_monitor, m_deviceId.c_str(), nullptr); m_zoneWindow = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, uniqueId, false); const std::wstring expectedWorkArea = std::to_wstring(m_monitorInfo.rcMonitor.right) + L"_" + std::to_wstring(m_monitorInfo.rcMonitor.bottom); From 20f9c32781062b2d6918477080765955a5e5f5b5 Mon Sep 17 00:00:00 2001 From: vldmr11080 z Date: Sun, 2 Feb 2020 13:12:56 +0100 Subject: [PATCH 08/32] Address PR comment: Use regular comparison instead of std::wstring::compare --- src/modules/fancyzones/lib/JsonHelpers.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/modules/fancyzones/lib/JsonHelpers.cpp b/src/modules/fancyzones/lib/JsonHelpers.cpp index ba80d8eb5098..ff8a09f1a7a2 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.cpp +++ b/src/modules/fancyzones/lib/JsonHelpers.cpp @@ -85,23 +85,23 @@ namespace JSONHelpers ZoneSetLayoutType TypeFromString(const std::wstring& typeStr) { - if (typeStr.compare(L"focus") == 0) + if (typeStr == L"focus") { return JSONHelpers::ZoneSetLayoutType::Focus; } - else if (typeStr.compare(L"columns") == 0) + else if (typeStr == L"columns") { return JSONHelpers::ZoneSetLayoutType::Columns; } - else if (typeStr.compare(L"rows") == 0) + else if (typeStr == L"rows") { return JSONHelpers::ZoneSetLayoutType::Rows; } - else if (typeStr.compare(L"grid") == 0) + else if (typeStr == L"grid") { return JSONHelpers::ZoneSetLayoutType::Grid; } - else if (typeStr.compare(L"priority-grid") == 0) + else if (typeStr == L"priority-grid") { return JSONHelpers::ZoneSetLayoutType::PriorityGrid; } From 0fcdb3a4a8319319f77cfb1b6ba6866b73aa2edf Mon Sep 17 00:00:00 2001 From: vldmr11080 z Date: Sun, 2 Feb 2020 14:37:20 +0100 Subject: [PATCH 09/32] Address PR comment: Use std::wstring_view for tmp file paths --- src/modules/fancyzones/lib/JsonHelpers.cpp | 10 +++++----- src/modules/fancyzones/lib/JsonHelpers.h | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/modules/fancyzones/lib/JsonHelpers.cpp b/src/modules/fancyzones/lib/JsonHelpers.cpp index ff8a09f1a7a2..3144599a881c 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.cpp +++ b/src/modules/fancyzones/lib/JsonHelpers.cpp @@ -185,13 +185,13 @@ namespace JSONHelpers } } - void FancyZonesData::SerializeDeviceInfoToTmpFile(const DeviceInfoJSON& deviceInfo, const std::wstring& tmpFilePath) const + void FancyZonesData::SerializeDeviceInfoToTmpFile(const DeviceInfoJSON& deviceInfo, std::wstring_view tmpFilePath) const { json::JsonObject deviceInfoJson = DeviceInfoJSON::ToJson(deviceInfo); json::to_file(tmpFilePath, deviceInfoJson); } - void FancyZonesData::ParseDeviceInfoFromTmpFile(const std::wstring& tmpFilePath) + void FancyZonesData::ParseDeviceInfoFromTmpFile(std::wstring_view tmpFilePath) { if (std::filesystem::exists(tmpFilePath)) { @@ -213,7 +213,7 @@ namespace JSONHelpers } } - bool FancyZonesData::ParseCustomZoneSetFromTmpFile(const std::wstring& tmpFilePath, const TZoneSetUUID& uuid) + bool FancyZonesData::ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath, const TZoneSetUUID& uuid) { bool res = true; if (std::filesystem::exists(tmpFilePath)) @@ -240,7 +240,7 @@ namespace JSONHelpers return res; } - bool FancyZonesData::ParseDeletedCustomZoneSetsFromTmpFile(const std::wstring& tmpFilePath) + bool FancyZonesData::ParseDeletedCustomZoneSetsFromTmpFile(std::wstring_view tmpFilePath) { bool res = true; if (std::filesystem::exists(tmpFilePath)) @@ -380,7 +380,7 @@ namespace JSONHelpers return customZoneSetsJSON; } - void FancyZonesData::CustomZoneSetsToJsonFile(const std::wstring& filePath) const + void FancyZonesData::CustomZoneSetsToJsonFile(std::wstring_view filePath) const { const auto& customZoneSetsJson = SerializeCustomZoneSets(); json::JsonObject root{}; diff --git a/src/modules/fancyzones/lib/JsonHelpers.h b/src/modules/fancyzones/lib/JsonHelpers.h index 757292f48ff0..ca3708b6d525 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.h +++ b/src/modules/fancyzones/lib/JsonHelpers.h @@ -202,9 +202,9 @@ namespace JSONHelpers activeDeviceId = deviceId; } - inline bool DeleteTmpFile(const std::wstring& tmpFilePath) + inline bool DeleteTmpFile(std::wstring_view tmpFilePath) const { - return DeleteFileW(tmpFilePath.c_str()); + return DeleteFileW(tmpFilePath.data()); } int GetAppLastZone(HWND window, PCWSTR appPath) const; @@ -212,11 +212,11 @@ namespace JSONHelpers void SetActiveZoneSet(const TDeviceID& deviceId, const TZoneSetUUID& uuid); - void SerializeDeviceInfoToTmpFile(const DeviceInfoJSON& deviceInfo, const std::wstring& tmpFilePath) const; - void ParseDeviceInfoFromTmpFile(const std::wstring& tmpFilePath); + void SerializeDeviceInfoToTmpFile(const DeviceInfoJSON& deviceInfo, std::wstring_view tmpFilePath) const; + void ParseDeviceInfoFromTmpFile(std::wstring_view tmpFilePath); - bool ParseCustomZoneSetFromTmpFile(const std::wstring& tmpFilePath, const TZoneSetUUID& uuid); - bool ParseDeletedCustomZoneSetsFromTmpFile(const std::wstring& tmpFilePath); + bool ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath, const TZoneSetUUID& uuid); + bool ParseDeletedCustomZoneSetsFromTmpFile(std::wstring_view tmpFilePath); bool ParseAppZoneHistory(const json::JsonObject& fancyZonesDataJSON); json::JsonArray SerializeAppZoneHistory() const; @@ -224,7 +224,7 @@ namespace JSONHelpers json::JsonArray SerializeDeviceInfos() const; bool ParseCustomZoneSets(const json::JsonObject& fancyZonesDataJSON); json::JsonArray SerializeCustomZoneSets() const; - void CustomZoneSetsToJsonFile(const std::wstring& filePath) const; + void CustomZoneSetsToJsonFile(std::wstring_view filePath) const; void LoadFancyZonesData(); void SaveFancyZonesData() const; From fc511daed14b6d2b9156e03fa8e5ca1edccc006d Mon Sep 17 00:00:00 2001 From: vldmr11080 z Date: Sun, 2 Feb 2020 15:57:55 +0100 Subject: [PATCH 10/32] Address PR comment: Use scoped lock when accessing member data --- src/modules/fancyzones/lib/FancyZones.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/modules/fancyzones/lib/FancyZones.cpp b/src/modules/fancyzones/lib/FancyZones.cpp index 91148d2280ca..e02483d5a826 100644 --- a/src/modules/fancyzones/lib/FancyZones.cpp +++ b/src/modules/fancyzones/lib/FancyZones.cpp @@ -549,9 +549,8 @@ void FancyZones::OnDisplayChange(DisplayChangeType changeType) noexcept GUID currentVirtualDesktopId{}; if (SUCCEEDED(RegistryHelpers::GetCurrentVirtualDesktop(¤tVirtualDesktopId))) { - m_lock.lock(); + std::unique_lock writeLock(m_lock); m_currentVirtualDesktopId = currentVirtualDesktopId; - m_lock.unlock(); } else { From 64360b0450faf21722aade2fa3063c0bf32874a9 Mon Sep 17 00:00:00 2001 From: vldmr11080 z Date: Sun, 2 Feb 2020 17:28:01 +0100 Subject: [PATCH 11/32] Address PR comment: Remove typedefs to increase code readability --- src/modules/fancyzones/lib/JsonHelpers.cpp | 16 ++++---- src/modules/fancyzones/lib/JsonHelpers.h | 48 ++++++++-------------- 2 files changed, 27 insertions(+), 37 deletions(-) diff --git a/src/modules/fancyzones/lib/JsonHelpers.cpp b/src/modules/fancyzones/lib/JsonHelpers.cpp index 3144599a881c..cd9638e4b4cb 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.cpp +++ b/src/modules/fancyzones/lib/JsonHelpers.cpp @@ -17,6 +17,8 @@ namespace constexpr int c_gridModelId = 0xFFFC; constexpr int c_priorityGridModelId = 0xFFFB; constexpr int c_blankCustomModelId = 0xFFFA; + + const wchar_t* FANCY_ZONES_DATA_FILE = L"PersistFancyZones.json"; } namespace JSONHelpers @@ -149,7 +151,7 @@ namespace JSONHelpers if (auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL)) { - TAppPath path{ appPath }; + std::wstring path{ appPath }; if (appZoneHistoryMap.contains(path)) { iZoneIndex = appZoneHistoryMap.at(path).zoneIndex; @@ -165,19 +167,19 @@ namespace JSONHelpers { if (zoneIndex == -1) { - appZoneHistoryMap.erase(TAppPath{ appPath }); + appZoneHistoryMap.erase(std::wstring{ appPath }); } else { //TODO(stefan) provide correct uuid in the future - appZoneHistoryMap[TAppPath{ appPath }] = AppZoneHistoryData{ L"", static_cast(zoneIndex) }; + appZoneHistoryMap[std::wstring{ appPath }] = AppZoneHistoryData{ L"", static_cast(zoneIndex) }; } return true; } return false; } - void FancyZonesData::SetActiveZoneSet(const TDeviceID& deviceId, const TZoneSetUUID& uuid) + void FancyZonesData::SetActiveZoneSet(const std::wstring& deviceId, const std::wstring& uuid) { if (!uuid.empty() && deviceInfoMap.find(deviceId) != deviceInfoMap.end()) { @@ -213,7 +215,7 @@ namespace JSONHelpers } } - bool FancyZonesData::ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath, const TZoneSetUUID& uuid) + bool FancyZonesData::ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath, const std::wstring& uuid) { bool res = true; if (std::filesystem::exists(tmpFilePath)) @@ -508,7 +510,7 @@ namespace JSONHelpers } } - void FancyZonesData::MigrateDeviceInfoFromRegistry(const TDeviceID& deviceId) + void FancyZonesData::MigrateDeviceInfoFromRegistry(const std::wstring& deviceId) { wchar_t key[256]; StringCchPrintf(key, ARRAYSIZE(key), L"%s\\%s", RegistryHelpers::REG_SETTINGS, deviceId.c_str()); @@ -552,7 +554,7 @@ namespace JSONHelpers // int version = data[0] * 256 + data[1]; - Not used anymore std::wstring uuid = std::to_wstring(data[3] * 256 + data[4]); - auto it = std::find_if(appliedZoneSetsMap.begin(), appliedZoneSetsMap.end(), [&uuid](std::pair zoneSetMap) { + auto it = std::find_if(appliedZoneSetsMap.begin(), appliedZoneSetsMap.end(), [&uuid](std::pair zoneSetMap) { return zoneSetMap.second.uuid.compare(uuid) == 0; }); diff --git a/src/modules/fancyzones/lib/JsonHelpers.h b/src/modules/fancyzones/lib/JsonHelpers.h index ca3708b6d525..f75d0634ccd5 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.h +++ b/src/modules/fancyzones/lib/JsonHelpers.h @@ -36,11 +36,6 @@ namespace JSONHelpers ZoneSetLayoutType TypeFromLayoutId(int layoutID); - using TZoneCount = int; - using TZoneSetUUID = std::wstring; - using TAppPath = std::wstring; - using TDeviceID = std::wstring; - struct CanvasLayoutInfo { int referenceWidth; @@ -111,7 +106,7 @@ namespace JSONHelpers struct CustomZoneSetJSON { - TZoneSetUUID uuid; + std::wstring uuid; CustomZoneSetData data; static json::JsonObject ToJson(const CustomZoneSetJSON& device); @@ -121,7 +116,7 @@ namespace JSONHelpers // TODO(stefan): This needs to be moved to ZoneSet.h (probably) struct ZoneSetData { - TZoneSetUUID uuid; + std::wstring uuid; ZoneSetLayoutType type; std::optional zoneCount; @@ -131,14 +126,14 @@ namespace JSONHelpers struct AppZoneHistoryData { - TZoneSetUUID zoneSetUuid; //TODO(stefan): is this nessecary? It doesn't exist with registry impl. + std::wstring zoneSetUuid; //TODO(stefan): is this nessecary? It doesn't exist with registry impl. int zoneIndex; //TODO(stefan): Also, do we need DeviceID here? Do we want to support that - app history per monitor? }; struct AppZoneHistoryJSON { - TAppPath appPath; + std::wstring appPath; AppZoneHistoryData data; static json::JsonObject ToJson(const AppZoneHistoryJSON& appZoneHistory); @@ -155,20 +150,13 @@ namespace JSONHelpers struct DeviceInfoJSON { - TDeviceID deviceId; + std::wstring deviceId; DeviceInfoData data; static json::JsonObject ToJson(const DeviceInfoJSON& device); static std::optional FromJson(const json::JsonObject& device); }; - using TDeviceInfosMap = std::unordered_map; - using TCustomZoneSetsMap = std::unordered_map; - using TAppliedZoneSetsMap = std::unordered_map; - using TAppZoneHistoryMap = std::unordered_map; - - static const std::wstring FANCY_ZONES_DATA_FILE = L"PersistFancyZones.json"; - class FancyZonesData { public: @@ -177,27 +165,27 @@ namespace JSONHelpers const std::wstring& GetPersistFancyZonesJSONPath() const; json::JsonObject GetPersistFancyZonesJSON(); - TDeviceInfosMap& GetDeviceInfoMap() + std::unordered_map& GetDeviceInfoMap() { return deviceInfoMap; } - inline const TCustomZoneSetsMap& GetCustomZoneSetsMap() const + inline const std::unordered_map& GetCustomZoneSetsMap() const { return customZoneSetsMap; } - inline const TAppZoneHistoryMap& GetAppZoneHistoryMap() const + inline const std::unordered_map& GetAppZoneHistoryMap() const { return appZoneHistoryMap; } - inline const TDeviceID GetActiveDeviceId() const + inline const std::wstring GetActiveDeviceId() const { return activeDeviceId; } - void SetActiveDeviceId(TDeviceID deviceId) + void SetActiveDeviceId(std::wstring deviceId) { activeDeviceId = deviceId; } @@ -210,12 +198,12 @@ namespace JSONHelpers int GetAppLastZone(HWND window, PCWSTR appPath) const; bool SetAppLastZone(HWND window, PCWSTR appPath, DWORD zoneIndex); //TODO(stefan): Missing zone uuid (pass as arg) - void SetActiveZoneSet(const TDeviceID& deviceId, const TZoneSetUUID& uuid); + void SetActiveZoneSet(const std::wstring& deviceId, const std::wstring& uuid); void SerializeDeviceInfoToTmpFile(const DeviceInfoJSON& deviceInfo, std::wstring_view tmpFilePath) const; void ParseDeviceInfoFromTmpFile(std::wstring_view tmpFilePath); - bool ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath, const TZoneSetUUID& uuid); + bool ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath, const std::wstring& uuid); bool ParseDeletedCustomZoneSetsFromTmpFile(std::wstring_view tmpFilePath); bool ParseAppZoneHistory(const json::JsonObject& fancyZonesDataJSON); @@ -229,19 +217,19 @@ namespace JSONHelpers void LoadFancyZonesData(); void SaveFancyZonesData() const; - void MigrateDeviceInfoFromRegistry(const TDeviceID& deviceId); + void MigrateDeviceInfoFromRegistry(const std::wstring& deviceId); private: void TmpMigrateAppliedZoneSetsFromRegistry(); void MigrateAppZoneHistoryFromRegistry(); //TODO(stefan): If uuid is needed here, it needs to be resolved here some how void MigrateCustomZoneSetsFromRegistry(); - TAppliedZoneSetsMap appliedZoneSetsMap{}; - TAppZoneHistoryMap appZoneHistoryMap{}; - TDeviceInfosMap deviceInfoMap{}; - TCustomZoneSetsMap customZoneSetsMap{}; + std::unordered_map appliedZoneSetsMap{}; + std::unordered_map appZoneHistoryMap{}; + std::unordered_map deviceInfoMap{}; + std::unordered_map customZoneSetsMap{}; - TDeviceID activeDeviceId; + std::wstring activeDeviceId; std::wstring jsonFilePath; }; From fe4e0e1267100b18c07c143ae831f44ffe5684ec Mon Sep 17 00:00:00 2001 From: Serafima Zykova Date: Mon, 3 Feb 2020 18:00:56 +0300 Subject: [PATCH 12/32] Address PR comment: removed nullptr checks with corresponding tests --- src/modules/fancyzones/lib/Settings.cpp | 29 ++++--------------- src/modules/fancyzones/lib/ZoneWindow.cpp | 4 --- .../UnitTests/FancyZonesSettings.Spec.cpp | 20 ------------- .../tests/UnitTests/ZoneWindow.Spec.cpp | 14 --------- 4 files changed, 5 insertions(+), 62 deletions(-) diff --git a/src/modules/fancyzones/lib/Settings.cpp b/src/modules/fancyzones/lib/Settings.cpp index dccec1363df0..f1bd179fa875 100644 --- a/src/modules/fancyzones/lib/Settings.cpp +++ b/src/modules/fancyzones/lib/Settings.cpp @@ -11,10 +11,9 @@ struct FancyZonesSettings : winrt::implements MakeFancyZonesSettings(HINSTANCE hinstance, PCWSTR name) noexcept { - auto self = winrt::make_self(hinstance, name); - if (self->Init(name, true /*fromFile*/)) - { - return self; - } - - return nullptr; + return winrt::make_self(hinstance, name); } \ No newline at end of file diff --git a/src/modules/fancyzones/lib/ZoneWindow.cpp b/src/modules/fancyzones/lib/ZoneWindow.cpp index 98b385794835..cc175e7e1f73 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.cpp +++ b/src/modules/fancyzones/lib/ZoneWindow.cpp @@ -339,10 +339,6 @@ ZoneWindow::ZoneWindow(HINSTANCE hinstance) bool ZoneWindow::Init(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, const std::wstring& uniqueId, bool flashZones) { - if (!host) - { - return false; - } m_host.copy_from(host); MONITORINFO mi{}; diff --git a/src/modules/fancyzones/tests/UnitTests/FancyZonesSettings.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/FancyZonesSettings.Spec.cpp index 543ea77d4645..528156b0db4d 100644 --- a/src/modules/fancyzones/tests/UnitTests/FancyZonesSettings.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/FancyZonesSettings.Spec.cpp @@ -92,12 +92,6 @@ namespace FancyZonesUnitTests compareSettings(m_defaultSettings, actualSettings); } - TEST_METHOD(CreateWithNameNullptr) - { - auto actual = MakeFancyZonesSettings(m_hInst, nullptr); - Assert::IsTrue(actual == nullptr); - } - TEST_METHOD(Create) { //prepare data @@ -716,19 +710,5 @@ namespace FancyZonesUnitTests Assert::IsTrue(std::filesystem::exists(settingsFile)); } - - TEST_METHOD(SetConfigNullptr) - { - //cleanup file before call set config - const auto settingsFile = PTSettingsHelper::get_module_save_folder_location(m_moduleName) + L"\\settings.json"; - std::filesystem::remove(settingsFile); - - const auto expected = m_settings->GetSettings(); - m_settings->SetConfig(nullptr); - - auto actual = m_settings->GetSettings(); - compareSettings(expected, actual); - Assert::IsTrue(std::filesystem::exists(settingsFile)); - } }; } \ No newline at end of file diff --git a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp index d59a767a3132..f82f8c09b632 100644 --- a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp @@ -133,20 +133,6 @@ namespace FancyZonesUnitTests Assert::IsNull(m_zoneWindow->ActiveZoneSet()); } - TEST_METHOD(CreateZoneWindowNoZoneWindowHost) - { - m_zoneWindow = MakeZoneWindow(nullptr, m_hInst, m_monitor, m_uniqueId.str(), false); - - Assert::IsNull(m_zoneWindow.get()); - } - - TEST_METHOD(CreateZoneWindowNoZoneWindowHostFlashZones) - { - m_zoneWindow = MakeZoneWindow(nullptr, m_hInst, m_monitor, m_uniqueId.str(), true); - - Assert::IsNull(m_zoneWindow.get()); - } - TEST_METHOD(CreateZoneWindowNoHinst) { m_zoneWindow = MakeZoneWindow(m_hostPtr, {}, m_monitor, m_uniqueId.str(), false); From 83c1e0575b1a83f8dc60dc4a5c669589c51f84fe Mon Sep 17 00:00:00 2001 From: vldmr11080 z Date: Mon, 3 Feb 2020 17:26:13 +0100 Subject: [PATCH 13/32] Address PR comment: Move ZoneSet object instead of copying --- src/modules/fancyzones/lib/JsonHelpers.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/modules/fancyzones/lib/JsonHelpers.cpp b/src/modules/fancyzones/lib/JsonHelpers.cpp index cd9638e4b4cb..733bef9f1cca 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.cpp +++ b/src/modules/fancyzones/lib/JsonHelpers.cpp @@ -713,10 +713,9 @@ namespace JSONHelpers result.deviceId = device.GetNamedString(L"device-id"); - const auto zoneSet = ZoneSetData::FromJson(device.GetNamedObject(L"active-zoneset")); - if (zoneSet.has_value()) + if (auto zoneSet = ZoneSetData::FromJson(device.GetNamedObject(L"active-zoneset")); zoneSet.has_value()) { - result.data.activeZoneSet = *zoneSet; + result.data.activeZoneSet = std::move(zoneSet.value()); } else { From 2517a1488b6a21b715b61bad9d1054b0a683ea3e Mon Sep 17 00:00:00 2001 From: Stefan S Date: Mon, 3 Feb 2020 17:42:33 +0100 Subject: [PATCH 14/32] Address PR comment: Make FancyZonesData instance const where possible --- src/modules/fancyzones/dll/dllmain.cpp | 3 ++- src/modules/fancyzones/lib/FancyZones.cpp | 10 ++++++---- src/modules/fancyzones/lib/JsonHelpers.cpp | 13 ++++++++++++- src/modules/fancyzones/lib/JsonHelpers.h | 4 +++- src/modules/fancyzones/lib/ZoneWindow.cpp | 14 ++++---------- 5 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/modules/fancyzones/dll/dllmain.cpp b/src/modules/fancyzones/dll/dllmain.cpp index 9c481d441e74..1358c5ed1c5c 100644 --- a/src/modules/fancyzones/dll/dllmain.cpp +++ b/src/modules/fancyzones/dll/dllmain.cpp @@ -136,7 +136,8 @@ class FancyZonesModule : public PowertoyModuleIface void Disable(bool const traceEvent) { if (m_app) { - JSONHelpers::FancyZonesDataInstance().SaveFancyZonesData(); + const auto& fancyZonesData = JSONHelpers::FancyZonesDataInstance(); + fancyZonesData.SaveFancyZonesData(); if (traceEvent) { Trace::FancyZones::EnableFancyZones(false); diff --git a/src/modules/fancyzones/lib/FancyZones.cpp b/src/modules/fancyzones/lib/FancyZones.cpp index e02483d5a826..b0984dcc65b3 100644 --- a/src/modules/fancyzones/lib/FancyZones.cpp +++ b/src/modules/fancyzones/lib/FancyZones.cpp @@ -281,7 +281,8 @@ IFACEMETHODIMP_(void) FancyZones::WindowCreated(HWND window) noexcept auto processPath = get_process_path(window); if (!processPath.empty()) { - int zoneIndex = JSONHelpers::FancyZonesDataInstance().GetAppLastZone(window, processPath.data()); + const auto& fancyZonesData = JSONHelpers::FancyZonesDataInstance(); + int zoneIndex = fancyZonesData.GetAppLastZone(window, processPath.data()); if (zoneIndex != -1) { @@ -397,7 +398,8 @@ void FancyZones::ToggleEditor() noexcept auto zoneWindow = iter->second; - JSONHelpers::FancyZonesDataInstance().CustomZoneSetsToJsonFile(ZoneWindowUtils::GetCustomZoneSetsTmpPath()); + const auto& fancyZonesData = JSONHelpers::FancyZonesDataInstance(); + fancyZonesData.CustomZoneSetsToJsonFile(ZoneWindowUtils::GetCustomZoneSetsTmpPath()); const auto taskbar_x_offset = MulDiv(mi.rcWork.left - mi.rcMonitor.left, DPIAware::DEFAULT_DPI, dpi_x); const auto taskbar_y_offset = MulDiv(mi.rcWork.top - mi.rcMonitor.top, DPIAware::DEFAULT_DPI, dpi_y); @@ -413,10 +415,10 @@ void FancyZones::ToggleEditor() noexcept std::to_wstring(width) + L"_" + std::to_wstring(height); - const auto& deviceInfo = JSONHelpers::FancyZonesDataInstance().GetDeviceInfoMap().at(zoneWindow->UniqueId()); + const auto& deviceInfo = fancyZonesData.GetDeviceInfoMap().at(zoneWindow->UniqueId()); JSONHelpers::DeviceInfoJSON deviceInfoJson{ zoneWindow->UniqueId(), deviceInfo }; - JSONHelpers::FancyZonesDataInstance().SerializeDeviceInfoToTmpFile(deviceInfoJson, ZoneWindowUtils::GetActiveZoneSetTmpPath()); + fancyZonesData.SerializeDeviceInfoToTmpFile(deviceInfoJson, ZoneWindowUtils::GetActiveZoneSetTmpPath()); const std::wstring params = /*1*/ std::to_wstring(reinterpret_cast(monitor)) + L" " + diff --git a/src/modules/fancyzones/lib/JsonHelpers.cpp b/src/modules/fancyzones/lib/JsonHelpers.cpp index 733bef9f1cca..931639f74ddd 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.cpp +++ b/src/modules/fancyzones/lib/JsonHelpers.cpp @@ -145,6 +145,17 @@ namespace JSONHelpers } } + void FancyZonesData::AddDevice(const std::wstring& deviceId) + { + if (!deviceInfoMap.contains(deviceId)) + { + // Creates default entry in map when ZoneWindow is created + deviceInfoMap[deviceId] = DeviceInfoData{ ZoneSetData{ L"null", ZoneSetLayoutType::Grid, 1 }, true, 16, 3 }; + + MigrateDeviceInfoFromRegistry(deviceId); + } + } + int FancyZonesData::GetAppLastZone(HWND window, PCWSTR appPath) const { int iZoneIndex = -1; @@ -725,7 +736,7 @@ namespace JSONHelpers result.data.showSpacing = device.GetNamedBoolean(L"editor-show-spacing"); result.data.spacing = static_cast(device.GetNamedNumber(L"editor-spacing")); result.data.zoneCount = static_cast( - device.GetNamedNumber(L"editor-zone-count")); + device.GetNamedNumber(L"editor-zone-count")); return result; } diff --git a/src/modules/fancyzones/lib/JsonHelpers.h b/src/modules/fancyzones/lib/JsonHelpers.h index f75d0634ccd5..d6ccfb4bd568 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.h +++ b/src/modules/fancyzones/lib/JsonHelpers.h @@ -165,7 +165,7 @@ namespace JSONHelpers const std::wstring& GetPersistFancyZonesJSONPath() const; json::JsonObject GetPersistFancyZonesJSON(); - std::unordered_map& GetDeviceInfoMap() + inline const std::unordered_map& GetDeviceInfoMap() const { return deviceInfoMap; } @@ -195,6 +195,8 @@ namespace JSONHelpers return DeleteFileW(tmpFilePath.data()); } + void AddDevice(const std::wstring& deviceId); + int GetAppLastZone(HWND window, PCWSTR appPath) const; bool SetAppLastZone(HWND window, PCWSTR appPath, DWORD zoneIndex); //TODO(stefan): Missing zone uuid (pass as arg) diff --git a/src/modules/fancyzones/lib/ZoneWindow.cpp b/src/modules/fancyzones/lib/ZoneWindow.cpp index cc175e7e1f73..78d806fd8be4 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.cpp +++ b/src/modules/fancyzones/lib/ZoneWindow.cpp @@ -539,14 +539,7 @@ void ZoneWindow::HideZoneWindow() noexcept void ZoneWindow::LoadSettings() noexcept { - auto& deviceInfoMap = JSONHelpers::FancyZonesDataInstance().GetDeviceInfoMap(); - if (!deviceInfoMap.contains(m_uniqueId)) - { - // Creates entry in map when ZoneWindow is created - deviceInfoMap[m_uniqueId] = JSONHelpers::DeviceInfoData{ JSONHelpers::ZoneSetData{ L"null", JSONHelpers::ZoneSetLayoutType::Grid, 1 }, true, 16, 3 }; - - JSONHelpers::FancyZonesDataInstance().MigrateDeviceInfoFromRegistry(m_uniqueId); - } + JSONHelpers::FancyZonesDataInstance().AddDevice(m_uniqueId); JSONHelpers::FancyZonesDataInstance().ParseDeviceInfoFromTmpFile(ZoneWindowUtils::GetActiveZoneSetTmpPath()); JSONHelpers::FancyZonesDataInstance().ParseDeletedCustomZoneSetsFromTmpFile(ZoneWindowUtils::GetCustomZoneSetsTmpPath()); @@ -564,8 +557,9 @@ void ZoneWindow::InitializeZoneSets(MONITORINFO const& mi) noexcept void ZoneWindow::CalculateZoneSet() noexcept { - const auto& deviceInfoMap = JSONHelpers::FancyZonesDataInstance().GetDeviceInfoMap(); - const auto& activeDeviceId = JSONHelpers::FancyZonesDataInstance().GetActiveDeviceId(); + const auto& fancyZonesData = JSONHelpers::FancyZonesDataInstance(); + const auto& deviceInfoMap = fancyZonesData.GetDeviceInfoMap(); + const auto& activeDeviceId = fancyZonesData.GetActiveDeviceId(); const auto& activeZoneSet = deviceInfoMap.at(m_uniqueId).activeZoneSet; if (!activeDeviceId.empty() && activeDeviceId.compare(m_uniqueId) != 0 && !activeZoneSet.uuid.empty()) From 466b7348c555e42fad8d957345ff2b36ce5e9863 Mon Sep 17 00:00:00 2001 From: vldmr11080 z Date: Mon, 3 Feb 2020 20:21:24 +0100 Subject: [PATCH 15/32] Remove unnecessary gutter variable during calculating zone coordinates --- src/modules/fancyzones/lib/Zone.cpp | 2 +- src/modules/fancyzones/lib/Zone.h | 2 +- src/modules/fancyzones/lib/ZoneSet.cpp | 70 +++++++++++++------------- 3 files changed, 36 insertions(+), 38 deletions(-) diff --git a/src/modules/fancyzones/lib/Zone.cpp b/src/modules/fancyzones/lib/Zone.cpp index 91c7050ad785..e8ef37dc885f 100644 --- a/src/modules/fancyzones/lib/Zone.cpp +++ b/src/modules/fancyzones/lib/Zone.cpp @@ -129,7 +129,7 @@ void Zone::StampZone(HWND window, bool stamp) noexcept } } -winrt::com_ptr MakeZone(RECT zoneRect) noexcept +winrt::com_ptr MakeZone(const RECT& zoneRect) noexcept { return winrt::make_self(zoneRect); } diff --git a/src/modules/fancyzones/lib/Zone.h b/src/modules/fancyzones/lib/Zone.h index da4b954509a2..f2c779489732 100644 --- a/src/modules/fancyzones/lib/Zone.h +++ b/src/modules/fancyzones/lib/Zone.h @@ -11,4 +11,4 @@ interface __declspec(uuid("{8228E934-B6EF-402A-9892-15A1441BF8B0}")) IZone : pub IFACEMETHOD_(size_t, Id)() = 0; }; -winrt::com_ptr MakeZone(RECT zoneRect) noexcept; +winrt::com_ptr MakeZone(const RECT& zoneRect) noexcept; diff --git a/src/modules/fancyzones/lib/ZoneSet.cpp b/src/modules/fancyzones/lib/ZoneSet.cpp index ff42b024f3fe..2469e17a3aae 100644 --- a/src/modules/fancyzones/lib/ZoneSet.cpp +++ b/src/modules/fancyzones/lib/ZoneSet.cpp @@ -224,7 +224,7 @@ ZoneSet::MoveWindowIntoZoneByIndex(HWND window, HWND windowZone, int index) noex return; } - if (index >= static_cast(m_zones.size())) + if (index >= int(m_zones.size())) { index = 0; } @@ -244,7 +244,9 @@ IFACEMETHODIMP_(void) ZoneSet::MoveWindowIntoZoneByDirection(HWND window, HWND windowZone, DWORD vkCode) noexcept { if (m_zones.empty()) + { return; + } winrt::com_ptr oldZone = nullptr; winrt::com_ptr newZone = nullptr; @@ -340,13 +342,15 @@ bool ZoneSet::CalculateFocusLayout(Rect workArea, int zoneCount) noexcept { bool success = true; - LONG left{ static_cast(workArea.width() * 0.1) }; - LONG top{ static_cast(workArea.height() * 0.1) }; - LONG right{ static_cast(workArea.width() * 0.6) }; - LONG bottom{ static_cast(workArea.height() * 0.6) }; + long left{ long(workArea.width() * 0.1) }; + long top{ long(workArea.height() * 0.1) }; + long right{ long(workArea.width() * 0.6) }; + long bottom{ long(workArea.height() * 0.6) }; + RECT focusZoneRect{ left, top, right, bottom }; - int focusRectXIncrement = (zoneCount <= 1) ? 0 : (int)(workArea.width() * 0.2) / (zoneCount - 1); - int focusRectYIncrement = (zoneCount <= 1) ? 0 : (int)(workArea.height() * 0.2) / (zoneCount - 1); + + long focusRectXIncrement = (zoneCount <= 1) ? 0 : (int)(workArea.width() * 0.2) / (zoneCount - 1); + long focusRectYIncrement = (zoneCount <= 1) ? 0 : (int)(workArea.height() * 0.2) / (zoneCount - 1); if (left >= right || top >= bottom || left < 0 || right < 0 || top < 0 || bottom < 0) { @@ -369,35 +373,33 @@ bool ZoneSet::CalculateColumnsAndRowsLayout(Rect workArea, JSONHelpers::ZoneSetL { bool success = true; - int gutter = spacing; - int zonePercent = C_MULTIPLIER / zoneCount; - LONG totalWidth; - LONG totalHeight; + long totalWidth; + long totalHeight; - LONG cellWidth; - LONG cellHeight; + long cellWidth; + long cellHeight; if (type == JSONHelpers::ZoneSetLayoutType::Columns) { - totalWidth = workArea.width() - (gutter * 2) - (spacing * (zoneCount - 1)); - totalHeight = workArea.height() - (gutter * 2); + totalWidth = workArea.width() - (spacing * (zoneCount + 1)); + totalHeight = workArea.height() - (spacing * 2); cellWidth = totalWidth * zonePercent / C_MULTIPLIER; cellHeight = totalHeight; } else { //Rows - totalWidth = workArea.width() - (gutter * 2); - totalHeight = workArea.height() - (gutter * 2) - (spacing * (zoneCount - 1)); + totalWidth = workArea.width() - (spacing * 2); + totalHeight = workArea.height() - (spacing * (zoneCount + 1)); cellWidth = totalWidth; cellHeight = totalHeight * zonePercent / C_MULTIPLIER; } - LONG top = spacing; - LONG left = spacing; - LONG bottom = top + cellHeight; - LONG right = left + cellWidth; + long top = spacing; + long left = spacing; + long bottom = top + cellHeight; + long right = left + cellWidth; for (int zone = 0; zone < zoneCount; zone++) { @@ -521,8 +523,7 @@ bool ZoneSet::CalculateCustomLayout(Rect workArea, const std::wstring& customZon DPIAware::Convert(m_config.Monitor, x, y); DPIAware::Convert(m_config.Monitor, width, height); - RECT focusZoneRect{ x, y, x + width, y + height }; - AddZone(MakeZone(focusZoneRect)); + AddZone(MakeZone(RECT{ x, y, x + width, y + height })); } return true; @@ -541,20 +542,18 @@ bool ZoneSet::CalculateGridZones(Rect workArea, JSONHelpers::GridLayoutInfo grid { bool success = true; - int gutter = spacing; - - LONG totalWidth = static_cast(workArea.width()) - (gutter * 2) - (spacing * (gridLayoutInfo.columns() - 1)); - LONG totalHeight = static_cast(workArea.height()) - (gutter * 2) - (spacing * (gridLayoutInfo.rows() - 1)); + long totalWidth = workArea.width() - (spacing * (gridLayoutInfo.columns() + 1)); + long totalHeight = workArea.height() - (spacing * (gridLayoutInfo.rows() + 1)); struct Info { - LONG Extent; - LONG Start; - LONG End; + long Extent; + long Start; + long End; }; Info rowInfo[JSONHelpers::MAX_ZONE_COUNT]; Info columnInfo[JSONHelpers::MAX_ZONE_COUNT]; - LONG top = gutter; + long top = spacing; for (int row = 0; row < gridLayoutInfo.rows(); row++) { rowInfo[row].Start = top; @@ -563,7 +562,7 @@ bool ZoneSet::CalculateGridZones(Rect workArea, JSONHelpers::GridLayoutInfo grid top += rowInfo[row].Extent + spacing; } - LONG left = gutter; + long left = spacing; for (int col = 0; col < gridLayoutInfo.columns(); col++) { columnInfo[col].Start = left; @@ -594,16 +593,15 @@ bool ZoneSet::CalculateGridZones(Rect workArea, JSONHelpers::GridLayoutInfo grid maxCol++; } - LONG right = columnInfo[maxCol].End; - LONG bottom = rowInfo[maxRow].End; + long right = columnInfo[maxCol].End; + long bottom = rowInfo[maxRow].End; if (left >= right || top >= bottom || left < 0 || right < 0 || top < 0 || bottom < 0) { success = false; } - RECT focusZoneRect{ left, top, right, bottom }; - AddZone(MakeZone(focusZoneRect)); + AddZone(MakeZone(RECT{ left, top, right, bottom })); } } } From a25b4b28608f5e993c1a110b2dd1be01c48aaeb8 Mon Sep 17 00:00:00 2001 From: Stefan S Date: Mon, 3 Feb 2020 23:18:01 +0100 Subject: [PATCH 16/32] Remove uneeded subclass --- .../FancyZonesEditor/Models/LayoutModel.cs | 8 ++-- .../FancyZonesEditor/Models/Settings.cs | 41 ------------------- 2 files changed, 5 insertions(+), 44 deletions(-) diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs index db9955816e2e..1a31301b6830 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs @@ -278,9 +278,11 @@ public void Apply(System.Windows.Int32Rect[] zones) writer.WriteEndObject(); - writer.WriteBoolean("editor-show-spacing", Settings._settingsToPersist.ShowSpacing); - writer.WriteNumber("editor-spacing", Settings._settingsToPersist.Spacing); - writer.WriteNumber("editor-zone-count", Settings._settingsToPersist.ZoneCount); + Settings settings = ((App)Application.Current).ZoneSettings; + + writer.WriteBoolean("editor-show-spacing", settings.ShowSpacing); + writer.WriteNumber("editor-spacing", settings.Spacing); + writer.WriteNumber("editor-zone-count", settings.ZoneCount); writer.WriteEndObject(); writer.Flush(); outputStream.Close(); diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs index 1a1ad7d559a2..6c8c8725ab7b 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs @@ -109,8 +109,6 @@ public Settings() _blankCustomModel = new CanvasLayoutModel("Create new custom", LayoutType.Blank, (int)_workArea.Width, (int)_workArea.Height); - _settingsToPersist = new SettingsToPersist(_showSpacing, _spacing, _zoneCount); - UpdateLayoutModels(); } @@ -127,7 +125,6 @@ public int ZoneCount if (_zoneCount != value) { _zoneCount = value; - _settingsToPersist.ZoneCount = value; UpdateLayoutModels(); FirePropertyChanged("ZoneCount"); } @@ -149,7 +146,6 @@ public int Spacing if (_spacing != value) { _spacing = value; - _settingsToPersist.Spacing = value; FirePropertyChanged("Spacing"); } } @@ -170,7 +166,6 @@ public bool ShowSpacing if (_showSpacing != value) { _showSpacing = value; - _settingsToPersist.ShowSpacing = value; FirePropertyChanged("ShowSpacing"); } } @@ -178,42 +173,6 @@ public bool ShowSpacing private bool _showSpacing; - public class SettingsToPersist - { - public SettingsToPersist(bool showSpacing, int spacing, int zoneCount) - { - _showSpacing = showSpacing; - _spacing = spacing; - _zoneCount = zoneCount; - } - - private bool _showSpacing; - - public bool ShowSpacing - { - get { return _showSpacing; } - set { _showSpacing = value; } - } - - private int _spacing; - - public int Spacing - { - get { return _spacing; } - set { _spacing = value; } - } - - private int _zoneCount; - - public int ZoneCount - { - get { return _zoneCount; } - set { _zoneCount = value; } - } - } - - public static SettingsToPersist _settingsToPersist; - // IsShiftKeyPressed - is the shift key currently being held down public bool IsShiftKeyPressed { From 0c557b0278cf098864438233206895b6084063e9 Mon Sep 17 00:00:00 2001 From: vldmr11080 z Date: Mon, 3 Feb 2020 23:37:02 +0100 Subject: [PATCH 17/32] Avoid unnecessary copying and reserve space for vector if possible --- src/modules/fancyzones/lib/JsonHelpers.cpp | 47 +++++++++------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/src/modules/fancyzones/lib/JsonHelpers.cpp b/src/modules/fancyzones/lib/JsonHelpers.cpp index 931639f74ddd..7e779acfc7f9 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.cpp +++ b/src/modules/fancyzones/lib/JsonHelpers.cpp @@ -208,14 +208,12 @@ namespace JSONHelpers { if (std::filesystem::exists(tmpFilePath)) { - auto zoneSetJson = json::from_file(tmpFilePath); - if (zoneSetJson.has_value()) + if (auto zoneSetJson = json::from_file(tmpFilePath); zoneSetJson.has_value()) { - const auto deviceInfo = DeviceInfoJSON::FromJson(*zoneSetJson); - activeDeviceId = deviceInfo->deviceId; - if (deviceInfo.has_value()) + if (auto deviceInfo = DeviceInfoJSON::FromJson(zoneSetJson.value()); deviceInfo.has_value()) { - deviceInfoMap[activeDeviceId] = deviceInfo->data; + activeDeviceId = deviceInfo->deviceId; + deviceInfoMap[activeDeviceId] = std::move(deviceInfo->data); DeleteTmpFile(tmpFilePath); } } @@ -233,13 +231,11 @@ namespace JSONHelpers { try { - auto customZoneSetJson = json::from_file(tmpFilePath); - if (customZoneSetJson.has_value()) + if (auto customZoneSetJson = json::from_file(tmpFilePath); customZoneSetJson.has_value()) { - const auto customZoneSet = CustomZoneSetJSON::FromJson(*customZoneSetJson); - if (customZoneSet.has_value()) + if (auto customZoneSet = CustomZoneSetJSON::FromJson(customZoneSetJson.value()); customZoneSet.has_value()) { - customZoneSetsMap[uuid] = customZoneSet->data; + customZoneSetsMap[uuid] = std::move(customZoneSet->data); } } } @@ -288,10 +284,9 @@ namespace JSONHelpers for (uint32_t i = 0; i < appLastZones.Size(); ++i) { json::JsonObject appLastZone = appLastZones.GetObjectAt(i); - const auto appZoneHistory = AppZoneHistoryJSON::FromJson(appLastZone); - if (appZoneHistory.has_value()) + if (auto appZoneHistory = AppZoneHistoryJSON::FromJson(appLastZone); appZoneHistory.has_value()) { - appZoneHistoryMap[appZoneHistory->appPath] = appZoneHistory->data; + appZoneHistoryMap[appZoneHistory->appPath] = std::move(appZoneHistory->data); } else { @@ -327,10 +322,9 @@ namespace JSONHelpers for (uint32_t i = 0; i < devices.Size(); ++i) { - const auto device = DeviceInfoJSON::DeviceInfoJSON::FromJson(devices.GetObjectAt(i)); - if (device.has_value()) + if (auto device = DeviceInfoJSON::DeviceInfoJSON::FromJson(devices.GetObjectAt(i)); device.has_value()) { - deviceInfoMap[device->deviceId] = device->data; + deviceInfoMap[device->deviceId] = std::move(device->data); } else { @@ -366,10 +360,9 @@ namespace JSONHelpers for (uint32_t i = 0; i < customZoneSets.Size(); ++i) { - const auto zoneSet = CustomZoneSetJSON::FromJson(customZoneSets.GetObjectAt(i)); - if (zoneSet.has_value()) + if (auto zoneSet = CustomZoneSetJSON::FromJson(customZoneSets.GetObjectAt(i)); zoneSet.has_value()) { - customZoneSetsMap[zoneSet->uuid] = zoneSet->data; + customZoneSetsMap[zoneSet->uuid] = std::move(zoneSet->data); } } @@ -774,7 +767,9 @@ namespace JSONHelpers info.referenceWidth = static_cast(infoJson.GetNamedNumber(L"ref-width")); info.referenceHeight = static_cast(infoJson.GetNamedNumber(L"ref-height")); json::JsonArray zonesJson = infoJson.GetNamedArray(L"zones"); - for (uint32_t i = 0; i < zonesJson.Size(); ++i) + uint32_t size = zonesJson.Size(); + info.zones.reserve(size); + for (uint32_t i = 0; i < size; ++i) { json::JsonObject zoneJson = zonesJson.GetObjectAt(i); const int x = static_cast(zoneJson.GetNamedNumber(L"X")); @@ -921,11 +916,10 @@ namespace JSONHelpers std::wstring zoneSetType = std::wstring{ customZoneSet.GetNamedString(L"type") }; if (zoneSetType.compare(L"canvas") == 0) { - const auto info = CanvasLayoutInfo::FromJson(infoJson); - if (info.has_value()) + if (auto info = CanvasLayoutInfo::FromJson(infoJson); info.has_value()) { result.data.type = CustomLayoutType::Canvas; - result.data.info = *info; + result.data.info = std::move(info.value()); } else { @@ -934,11 +928,10 @@ namespace JSONHelpers } else if (zoneSetType.compare(L"grid") == 0) { - const auto info = GridLayoutInfo::FromJson(infoJson); - if (info.has_value()) + if (auto info = GridLayoutInfo::FromJson(infoJson); info.has_value()) { result.data.type = CustomLayoutType::Grid; - result.data.info = *info; + result.data.info = std::move(info.value()); } else { From 8910fafe341e885be818c876b7e540dba1934df5 Mon Sep 17 00:00:00 2001 From: vldmr11080 z Date: Tue, 4 Feb 2020 22:29:18 +0100 Subject: [PATCH 18/32] Save FancyZones data after exiting editor --- .../Models/CanvasLayoutModel.cs | 2 +- .../Models/GridLayoutModel.cs | 2 +- src/modules/fancyzones/lib/JsonHelpers.cpp | 8 +++-- src/modules/fancyzones/lib/JsonHelpers.h | 6 ++-- src/modules/fancyzones/lib/ZoneSet.cpp | 13 ++++--- src/modules/fancyzones/lib/ZoneSet.h | 3 +- src/modules/fancyzones/lib/ZoneWindow.cpp | 8 +++-- .../tests/UnitTests/JsonHelpers.Tests.cpp | 31 ++++------------- .../tests/UnitTests/ZoneSet.Spec.cpp | 34 +++++++++++-------- .../tests/UnitTests/ZoneWindow.Spec.cpp | 15 ++++---- 10 files changed, 57 insertions(+), 65 deletions(-) diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/CanvasLayoutModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/CanvasLayoutModel.cs index 642e341619dd..094aa3a03aee 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/CanvasLayoutModel.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/CanvasLayoutModel.cs @@ -125,7 +125,7 @@ protected override void PersistData() using (var writer = new Utf8JsonWriter(outputStream, writerOptions)) { writer.WriteStartObject(); - writer.WriteString("uuid", Guid.ToString()); + writer.WriteString("uuid", "{" + Guid.ToString().ToUpper() + "}"); writer.WriteString("name", Name); writer.WriteString("type", "canvas"); diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs index 510c20d30647..9641dbb63f78 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs @@ -174,7 +174,7 @@ protected override void PersistData() using (var writer = new Utf8JsonWriter(outputStream, options: default)) { writer.WriteStartObject(); - writer.WriteString("uuid", Guid.ToString()); + writer.WriteString("uuid", "{" + Guid.ToString().ToUpper() + "}"); writer.WriteString("name", Name); writer.WriteString("type", "grid"); diff --git a/src/modules/fancyzones/lib/JsonHelpers.cpp b/src/modules/fancyzones/lib/JsonHelpers.cpp index 7e779acfc7f9..21faba8bd7cb 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.cpp +++ b/src/modules/fancyzones/lib/JsonHelpers.cpp @@ -224,8 +224,12 @@ namespace JSONHelpers } } - bool FancyZonesData::ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath, const std::wstring& uuid) + bool FancyZonesData::ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath, const std::wstring& deviceId) { + if (deviceInfoMap.at(deviceId).activeZoneSet.type != JSONHelpers::ZoneSetLayoutType::Custom) + { + return false; + } bool res = true; if (std::filesystem::exists(tmpFilePath)) { @@ -235,7 +239,7 @@ namespace JSONHelpers { if (auto customZoneSet = CustomZoneSetJSON::FromJson(customZoneSetJson.value()); customZoneSet.has_value()) { - customZoneSetsMap[uuid] = std::move(customZoneSet->data); + customZoneSetsMap[customZoneSet->uuid] = std::move(customZoneSet->data); } } } diff --git a/src/modules/fancyzones/lib/JsonHelpers.h b/src/modules/fancyzones/lib/JsonHelpers.h index d6ccfb4bd568..a0486e3ea8cc 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.h +++ b/src/modules/fancyzones/lib/JsonHelpers.h @@ -185,7 +185,7 @@ namespace JSONHelpers return activeDeviceId; } - void SetActiveDeviceId(std::wstring deviceId) + void SetActiveDeviceId(const std::wstring& deviceId) { activeDeviceId = deviceId; } @@ -203,9 +203,9 @@ namespace JSONHelpers void SetActiveZoneSet(const std::wstring& deviceId, const std::wstring& uuid); void SerializeDeviceInfoToTmpFile(const DeviceInfoJSON& deviceInfo, std::wstring_view tmpFilePath) const; - void ParseDeviceInfoFromTmpFile(std::wstring_view tmpFilePath); - bool ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath, const std::wstring& uuid); + void ParseDeviceInfoFromTmpFile(std::wstring_view tmpFilePath); + bool ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath, const std::wstring& deviceId); bool ParseDeletedCustomZoneSetsFromTmpFile(std::wstring_view tmpFilePath); bool ParseAppZoneHistory(const json::JsonObject& fancyZonesDataJSON); diff --git a/src/modules/fancyzones/lib/ZoneSet.cpp b/src/modules/fancyzones/lib/ZoneSet.cpp index 2469e17a3aae..cce49c61e36b 100644 --- a/src/modules/fancyzones/lib/ZoneSet.cpp +++ b/src/modules/fancyzones/lib/ZoneSet.cpp @@ -135,14 +135,14 @@ struct ZoneSet : winrt::implements IFACEMETHODIMP_(void) MoveWindowIntoZoneByPoint(HWND window, HWND zoneWindow, POINT ptClient) noexcept; IFACEMETHODIMP_(bool) - CalculateZones(MONITORINFO monitorInfo, int zoneCount, int spacing, const std::wstring& customZoneSetFilePath) noexcept; + CalculateZones(MONITORINFO monitorInfo, int zoneCount, int spacing) noexcept; private: bool CalculateFocusLayout(Rect workArea, int zoneCount) noexcept; bool CalculateColumnsAndRowsLayout(Rect workArea, JSONHelpers::ZoneSetLayoutType type, int zoneCount, int spacing) noexcept; bool CalculateGridLayout(Rect workArea, JSONHelpers::ZoneSetLayoutType type, int zoneCount, int spacing) noexcept; bool CalculateUniquePriorityGridLayout(Rect workArea, int zoneCount, int spacing) noexcept; - bool CalculateCustomLayout(Rect workArea, const std::wstring& customZoneSetFilePath, int spacing) noexcept; + bool CalculateCustomLayout(Rect workArea, int spacing) noexcept; bool CalculateGridZones(Rect workArea, JSONHelpers::GridLayoutInfo gridLayoutInfo, int spacing); @@ -301,7 +301,7 @@ ZoneSet::MoveWindowIntoZoneByPoint(HWND window, HWND zoneWindow, POINT ptClient) } IFACEMETHODIMP_(bool) -ZoneSet::CalculateZones(MONITORINFO monitorInfo, int zoneCount, int spacing, const std::wstring& customZoneSetFilePath) noexcept +ZoneSet::CalculateZones(MONITORINFO monitorInfo, int zoneCount, int spacing) noexcept { Rect const workArea(monitorInfo.rcWork); //invalid work area @@ -331,7 +331,7 @@ ZoneSet::CalculateZones(MONITORINFO monitorInfo, int zoneCount, int spacing, con success = CalculateGridLayout(workArea, m_config.LayoutType, zoneCount, spacing); break; case JSONHelpers::ZoneSetLayoutType::Custom: - success = CalculateCustomLayout(workArea, customZoneSetFilePath, spacing); + success = CalculateCustomLayout(workArea, spacing); break; } @@ -491,13 +491,12 @@ bool ZoneSet::CalculateUniquePriorityGridLayout(Rect workArea, int zoneCount, in return CalculateGridZones(workArea, predefinedPriorityGridLayouts[zoneCount - 1], spacing); } -bool ZoneSet::CalculateCustomLayout(Rect workArea, const std::wstring& customZoneSetFilePath, int spacing) noexcept +bool ZoneSet::CalculateCustomLayout(Rect workArea, int spacing) noexcept { wil::unique_cotaskmem_string guuidStr; if (SUCCEEDED_LOG(StringFromCLSID(m_config.Id, &guuidStr))) { - const auto guuid = guuidStr.get(); - JSONHelpers::FancyZonesDataInstance().ParseCustomZoneSetFromTmpFile(customZoneSetFilePath, guuid); + const std::wstring guuid = guuidStr.get(); const auto& customZoneSets = JSONHelpers::FancyZonesDataInstance().GetCustomZoneSetsMap(); if (!customZoneSets.contains(guuid)) { diff --git a/src/modules/fancyzones/lib/ZoneSet.h b/src/modules/fancyzones/lib/ZoneSet.h index 4b5c8c8cd851..c500d4b11df7 100644 --- a/src/modules/fancyzones/lib/ZoneSet.h +++ b/src/modules/fancyzones/lib/ZoneSet.h @@ -15,8 +15,7 @@ interface __declspec(uuid("{E4839EB7-669D-49CF-84A9-71A2DFD851A3}")) IZoneSet : IFACEMETHOD_(void, MoveWindowIntoZoneByIndex)(HWND window, HWND zoneWindow, int index) = 0; IFACEMETHOD_(void, MoveWindowIntoZoneByDirection)(HWND window, HWND zoneWindow, DWORD vkCode) = 0; IFACEMETHOD_(void, MoveWindowIntoZoneByPoint)(HWND window, HWND zoneWindow, POINT ptClient) = 0; - IFACEMETHOD_(bool, CalculateZones) - (MONITORINFO monitorInfo, int zoneCount, int spacing, const std::wstring& customZoneSetFilePath) = 0; + IFACEMETHOD_(bool, CalculateZones)(MONITORINFO monitorInfo, int zoneCount, int spacing) = 0; }; #define VERSION_PERSISTEDDATA 0x0000F00D diff --git a/src/modules/fancyzones/lib/ZoneWindow.cpp b/src/modules/fancyzones/lib/ZoneWindow.cpp index 78d806fd8be4..23adc300ea80 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.cpp +++ b/src/modules/fancyzones/lib/ZoneWindow.cpp @@ -210,8 +210,7 @@ namespace ZoneWindowDrawUtils DrawIndex(hdc, offset, index, padding, size, false, true, colorFill); // bottom left } - void DrawActiveZoneSet(wil::unique_hdc& hdc, COLORREF highlightColor, int highlightOpacity, const std::vector>& zones - , const winrt::com_ptr& highlightZone, bool flashMode, bool drawHints) noexcept + void DrawActiveZoneSet(wil::unique_hdc& hdc, COLORREF highlightColor, int highlightOpacity, const std::vector>& zones, const winrt::com_ptr& highlightZone, bool flashMode, bool drawHints) noexcept { static constexpr std::array colors{ RGB(75, 75, 85), @@ -543,6 +542,9 @@ void ZoneWindow::LoadSettings() noexcept JSONHelpers::FancyZonesDataInstance().ParseDeviceInfoFromTmpFile(ZoneWindowUtils::GetActiveZoneSetTmpPath()); JSONHelpers::FancyZonesDataInstance().ParseDeletedCustomZoneSetsFromTmpFile(ZoneWindowUtils::GetCustomZoneSetsTmpPath()); + JSONHelpers::FancyZonesDataInstance().ParseCustomZoneSetFromTmpFile(ZoneWindowUtils::GetAppliedZoneSetTmpPath(), m_uniqueId); + + JSONHelpers::FancyZonesDataInstance().SaveFancyZonesData(); } void ZoneWindow::InitializeZoneSets(MONITORINFO const& mi) noexcept @@ -581,7 +583,7 @@ void ZoneWindow::CalculateZoneSet() noexcept bool showSpacing = deviceInfoMap.at(m_uniqueId).showSpacing; int spacing = showSpacing ? deviceInfoMap.at(m_uniqueId).spacing : 0; int zoneCount = activeZoneSet.zoneCount.has_value() ? activeZoneSet.zoneCount.value() : 0; - zoneSet->CalculateZones(monitorInfo, zoneCount, spacing, ZoneWindowUtils::GetAppliedZoneSetTmpPath()); + zoneSet->CalculateZones(monitorInfo, zoneCount, spacing); UpdateActiveZoneSet(zoneSet.get()); } } diff --git a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp index 5d06d8a079ab..e94e79d79733 100644 --- a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp +++ b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp @@ -1257,6 +1257,7 @@ namespace FancyZonesUnitTests compareJsonArrays(expected, actual); } +#if 0 TEST_METHOD(CustomZoneSetsReadTemp) { const std::wstring uuid = L"uuid"; @@ -1272,7 +1273,7 @@ namespace FancyZonesUnitTests const std::wstring path = data.GetPersistFancyZonesJSONPath() + L".test_tmp"; json::to_file(path, CustomZoneSetJSON::ToJson(expected)); - data.ParseCustomZoneSetFromTmpFile(path, uuid); + data.ParseCustomZoneSetFromTmpFile(path); bool actualFileExists = std::filesystem::exists(path); if (actualFileExists) @@ -1292,40 +1293,20 @@ namespace FancyZonesUnitTests Assert::AreEqual(expectedGrid.rows(), actualGrid.rows()); Assert::AreEqual(expectedGrid.columns(), actualGrid.columns()); } +#endif +#if 0 TEST_METHOD(CustomZoneSetsReadTempUnexsisted) { - const std::wstring uuid = L"uuid"; - FancyZonesData data; - - const std::wstring path = data.GetPersistFancyZonesJSONPath() + L".test_tmp"; - - data.ParseCustomZoneSetFromTmpFile(path, uuid); - auto devices = data.GetDeviceInfoMap(); - Assert::AreEqual((size_t)0, devices.size()); - } - - TEST_METHOD(CustomZoneSetsReadTempMissedUuid) - { - const std::wstring uuid = L"uuid"; - CustomZoneSetJSON expected{ uuid, CustomZoneSetData{ L"name", CustomLayoutType::Grid, GridLayoutInfo(GridLayoutInfo::Minimal{ 1, 2 }) } }; FancyZonesData data; const std::wstring path = data.GetPersistFancyZonesJSONPath() + L".test_tmp"; - json::to_file(path, CustomZoneSetJSON::ToJson(expected)); - data.ParseCustomZoneSetFromTmpFile(path, L"another_uuid"); - - bool actualFileExists = std::filesystem::exists(path); - if (actualFileExists) - { - std::filesystem::remove(path); //clean up before compare asserts - } - Assert::IsFalse(actualFileExists); - + data.ParseCustomZoneSetFromTmpFile(path); auto devices = data.GetDeviceInfoMap(); Assert::AreEqual((size_t)0, devices.size()); } +#endif TEST_METHOD(SetActiveZoneSet) { diff --git a/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp index 288ff2eb3798..247a03957fb4 100644 --- a/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp @@ -842,7 +842,7 @@ namespace FancyZonesUnitTests for (const auto& monitorInfo : m_popularMonitors) { auto set = MakeZoneSet(m_config); - auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, L""); + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing); Assert::IsTrue(result); checkZones(set, zoneCount, monitorInfo); } @@ -859,7 +859,7 @@ namespace FancyZonesUnitTests auto set = MakeZoneSet(m_config); MONITORINFO info{}; - auto result = set->CalculateZones(info, zoneCount, spacing, L""); + auto result = set->CalculateZones(info, zoneCount, spacing); Assert::IsFalse(result); } } @@ -876,7 +876,7 @@ namespace FancyZonesUnitTests for (const auto& monitorInfo : m_popularMonitors) { auto set = MakeZoneSet(m_config); - auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, L""); + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing); Assert::IsTrue(result); checkZones(set, zoneCount, monitorInfo); } @@ -895,7 +895,7 @@ namespace FancyZonesUnitTests for (const auto& monitorInfo : m_popularMonitors) { - auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, L""); + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing); if (type == static_cast(JSONHelpers::ZoneSetLayoutType::Focus)) { //Focus doesn't depends on spacing @@ -921,7 +921,7 @@ namespace FancyZonesUnitTests for (const auto& monitorInfo : m_popularMonitors) { const int spacing = monitorInfo.rcWork.right; - auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, L""); + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing); if (type == static_cast(JSONHelpers::ZoneSetLayoutType::Focus)) { //Focus doesn't depends on spacing @@ -947,7 +947,7 @@ namespace FancyZonesUnitTests for (const auto& monitorInfo : m_popularMonitors) { const int spacing = monitorInfo.rcWork.bottom; - auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, L""); + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing); if (type == static_cast(JSONHelpers::ZoneSetLayoutType::Focus)) { //Focus doesn't depends on spacing @@ -973,7 +973,7 @@ namespace FancyZonesUnitTests for (const auto& monitorInfo : m_popularMonitors) { - auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, L""); + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing); Assert::IsFalse(result); } } @@ -993,7 +993,7 @@ namespace FancyZonesUnitTests for (const auto& monitorInfo : m_popularMonitors) { auto set = MakeZoneSet(m_config); - auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, L""); + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing); Assert::IsTrue(result); checkZones(set, zoneCount, monitorInfo); } @@ -1016,7 +1016,7 @@ namespace FancyZonesUnitTests for (const auto& monitorInfo : m_popularMonitors) { - auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, m_path); + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing); Assert::IsFalse(result); } } @@ -1034,7 +1034,7 @@ namespace FancyZonesUnitTests for (const auto& monitorInfo : m_popularMonitors) { - auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, m_path); + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing); Assert::IsFalse(result); } } @@ -1057,7 +1057,7 @@ namespace FancyZonesUnitTests for (const auto& monitorInfo : m_popularMonitors) { - auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, m_path); + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing); Assert::IsFalse(result); } } @@ -1085,10 +1085,11 @@ namespace FancyZonesUnitTests for (const auto& monitorInfo : m_popularMonitors) { - auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, m_path); + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing); Assert::IsFalse(result); } } +#if 0 TEST_METHOD(CustomZoneFromValidCanvasLayoutInfo) { using namespace JSONHelpers; @@ -1107,12 +1108,14 @@ namespace FancyZonesUnitTests for (const auto& monitorInfo : m_popularMonitors) { auto set = MakeZoneSet(m_config); - auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, m_path); + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing); Assert::IsTrue(result); checkZones(set, zoneCount, monitorInfo); } } +#endif +#if 0 TEST_METHOD(CustomZoneFromValidGridFullLayoutInfo) { using namespace JSONHelpers; @@ -1136,11 +1139,12 @@ namespace FancyZonesUnitTests for (const auto& monitorInfo : m_popularMonitors) { auto set = MakeZoneSet(m_config); - auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, m_path); + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing); Assert::IsTrue(result); checkZones(set, zoneCount, monitorInfo); } } +#endif TEST_METHOD(CustomZoneFromValidGridMinimalLayoutInfo) { @@ -1162,7 +1166,7 @@ namespace FancyZonesUnitTests for (const auto& monitorInfo : m_popularMonitors) { - auto result = set->CalculateZones(monitorInfo, zoneCount, spacing, m_path); + auto result = set->CalculateZones(monitorInfo, zoneCount, spacing); Assert::IsFalse(result); } } diff --git a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp index f82f8c09b632..d3d0d43ac6b6 100644 --- a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp @@ -253,7 +253,8 @@ namespace FancyZonesUnitTests const auto appliedZoneSetTempPath = ZoneWindowUtils::GetAppliedZoneSetTmpPath(); const ZoneSetLayoutType type = ZoneSetLayoutType::Custom; - const auto expectedZoneSet = ZoneSetData{ guidString(), type, 5 }; + const auto customSetGuid = guidString(); + const auto expectedZoneSet = ZoneSetData{ customSetGuid, type, 5 }; const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data }; const auto json = DeviceInfoJSON::ToJson(deviceInfo); @@ -263,7 +264,7 @@ namespace FancyZonesUnitTests 100, 100, std::vector{ CanvasLayoutInfo::Rect{ 0, 0, 100, 100 } } }; const auto customZoneData = CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info }; - auto customZoneJson = CustomZoneSetJSON::ToJson(CustomZoneSetJSON{ guidString(), customZoneData }); + auto customZoneJson = CustomZoneSetJSON::ToJson(CustomZoneSetJSON{ customSetGuid, customZoneData }); json::to_file(appliedZoneSetTempPath, customZoneJson); //temp file read on initialization @@ -287,7 +288,8 @@ namespace FancyZonesUnitTests const auto deletedZonesTempPath = ZoneWindowUtils::GetCustomZoneSetsTmpPath(); const ZoneSetLayoutType type = ZoneSetLayoutType::Custom; - const auto expectedZoneSet = ZoneSetData{ guidString(), type, 5 }; + const auto customSetGuid = guidString(); + const auto expectedZoneSet = ZoneSetData{ customSetGuid, type, 5 }; const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data }; const auto json = DeviceInfoJSON::ToJson(deviceInfo); @@ -297,7 +299,7 @@ namespace FancyZonesUnitTests 100, 100, std::vector{ CanvasLayoutInfo::Rect{ 0, 0, 100, 100 } } }; const auto customZoneData = CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info }; - const auto customZoneSet = CustomZoneSetJSON{ guidString(), customZoneData }; + const auto customZoneSet = CustomZoneSetJSON{ customSetGuid, customZoneData }; auto customZoneJson = CustomZoneSetJSON::ToJson(customZoneSet); json::to_file(appliedZoneSetTempPath, customZoneJson); @@ -328,7 +330,8 @@ namespace FancyZonesUnitTests const auto deletedZonesTempPath = ZoneWindowUtils::GetCustomZoneSetsTmpPath(); const ZoneSetLayoutType type = ZoneSetLayoutType::Custom; - const auto expectedZoneSet = ZoneSetData{ guidString(), type, 5 }; + const auto customSetGuid = guidString(); + const auto expectedZoneSet = ZoneSetData{ customSetGuid, type, 5 }; const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data }; const auto json = DeviceInfoJSON::ToJson(deviceInfo); @@ -338,7 +341,7 @@ namespace FancyZonesUnitTests 100, 100, std::vector{ CanvasLayoutInfo::Rect{ 0, 0, 100, 100 } } }; const auto customZoneData = CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info }; - const auto customZoneSet = CustomZoneSetJSON{ guidString(), customZoneData }; + const auto customZoneSet = CustomZoneSetJSON{ customSetGuid, customZoneData }; auto customZoneJson = CustomZoneSetJSON::ToJson(customZoneSet); json::to_file(appliedZoneSetTempPath, customZoneJson); From b79d74c9e687e69f33fa100e0d3c70407fc4d676 Mon Sep 17 00:00:00 2001 From: Seraphima Date: Wed, 5 Feb 2020 18:14:23 +0300 Subject: [PATCH 19/32] App zone history (#18) * added window and zone set ids to app zone history --- src/modules/fancyzones/lib/FancyZones.cpp | 84 ++++-- src/modules/fancyzones/lib/JsonHelpers.cpp | 65 +++-- src/modules/fancyzones/lib/JsonHelpers.h | 9 +- src/modules/fancyzones/lib/ZoneWindow.cpp | 11 +- .../tests/UnitTests/FancyZones.Spec.cpp | 17 -- .../tests/UnitTests/JsonHelpers.Tests.cpp | 274 ++++++++++++++++-- .../tests/UnitTests/ZoneWindow.Spec.cpp | 57 ++-- 7 files changed, 400 insertions(+), 117 deletions(-) diff --git a/src/modules/fancyzones/lib/FancyZones.cpp b/src/modules/fancyzones/lib/FancyZones.cpp index b0984dcc65b3..e48bf228cf62 100644 --- a/src/modules/fancyzones/lib/FancyZones.cpp +++ b/src/modules/fancyzones/lib/FancyZones.cpp @@ -82,9 +82,11 @@ struct FancyZones : public winrt::implementssecond->ActiveZoneSet(); + const auto& zoneWindowPtr = it->second; + return zoneWindowPtr->ActiveZoneSet(); } return nullptr; } @@ -278,15 +280,28 @@ IFACEMETHODIMP_(void) FancyZones::WindowCreated(HWND window) noexcept { if (m_settings->GetSettings().appLastZone_moveWindows && IsInterestingWindow(window)) { - auto processPath = get_process_path(window); - if (!processPath.empty()) + auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL); + if (monitor) { - const auto& fancyZonesData = JSONHelpers::FancyZonesDataInstance(); - int zoneIndex = fancyZonesData.GetAppLastZone(window, processPath.data()); - - if (zoneIndex != -1) + auto zoneWindow = m_zoneWindowMap.find(monitor); + if (zoneWindow != m_zoneWindowMap.end()) { - MoveWindowIntoZoneByIndex(window, zoneIndex); + const auto& zoneWindowPtr = zoneWindow->second; + const auto activeZoneSet = zoneWindowPtr->ActiveZoneSet(); + if (activeZoneSet) + { + const auto& fancyZonesData = JSONHelpers::FancyZonesDataInstance(); + + wil::unique_cotaskmem_string guidString; + if (SUCCEEDED_LOG(StringFromCLSID(activeZoneSet->Id(), &guidString))) + { + int zoneIndex = fancyZonesData.GetAppLastZoneIndex(window, zoneWindowPtr->UniqueId(), guidString.get()); + if (zoneIndex != -1) + { + MoveWindowIntoZoneByIndex(window, zoneIndex); + } + } + } } } } @@ -594,14 +609,17 @@ void FancyZones::AddZoneWindow(HMONITOR monitor, PCWSTR deviceId) noexcept { std::wstring uniqueId = ZoneWindowUtils::GenerateUniqueId(monitor, deviceId, virtualDesktopId.get()); bool newVirtualDesktop = true; - if (auto it = m_virtualDesktopIds.find(m_currentVirtualDesktopId); it != end(m_virtualDesktopIds)) + + auto it = m_virtualDesktopIds.find(m_currentVirtualDesktopId); + if (it != end(m_virtualDesktopIds)) { newVirtualDesktop = it->second; JSONHelpers::FancyZonesDataInstance().SetActiveDeviceId(uniqueId); } - const bool flash = m_settings->GetSettings().zoneSetChange_flashZones && newVirtualDesktop; - if (auto zoneWindow = MakeZoneWindow(this, m_hinstance, monitor, uniqueId, flash)) + const bool flash = m_settings->GetSettings().zoneSetChange_flashZones && newVirtualDesktop; + auto zoneWindow = MakeZoneWindow(this, m_hinstance, monitor, uniqueId, flash); + if (zoneWindow) { m_zoneWindowMap[monitor] = std::move(zoneWindow); } @@ -614,12 +632,14 @@ void FancyZones::MoveWindowIntoZoneByIndex(HWND window, int index) noexcept std::shared_lock readLock(m_lock); if (window != m_windowMoveSize) { - if (const HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL)) + const HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL); + if (monitor) { auto iter = m_zoneWindowMap.find(monitor); if (iter != m_zoneWindowMap.end()) { - iter->second->MoveWindowIntoZoneByIndex(window, index); + const auto& zoneWindowPtr = iter->second; + zoneWindowPtr->MoveWindowIntoZoneByIndex(window, index); } } } @@ -754,13 +774,16 @@ void FancyZones::CycleActiveZoneSet(DWORD vkCode) noexcept auto window = GetForegroundWindow(); if (IsInterestingWindow(window)) { - if (const HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL)) + const HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL); + if (monitor) { std::shared_lock readLock(m_lock); + auto iter = m_zoneWindowMap.find(monitor); if (iter != m_zoneWindowMap.end()) { - iter->second->CycleActiveZoneSet(vkCode); + const auto& zoneWindowPtr = iter->second; + zoneWindowPtr->CycleActiveZoneSet(vkCode); } } } @@ -771,13 +794,16 @@ bool FancyZones::OnSnapHotkey(DWORD vkCode) noexcept auto window = GetForegroundWindow(); if (IsInterestingWindow(window)) { - if (const HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL)) + const HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL); + if (monitor) { std::shared_lock readLock(m_lock); + auto iter = m_zoneWindowMap.find(monitor); if (iter != m_zoneWindowMap.end()) { - iter->second->MoveWindowIntoZoneByDirection(window, vkCode); + const auto& zoneWindowPtr = iter->second; + zoneWindowPtr->MoveWindowIntoZoneByDirection(window, vkCode); return true; } } @@ -843,10 +869,23 @@ void FancyZones::MoveSizeEndInternal(HWND window, POINT const& ptScreen, require { ::RemoveProp(window, ZONE_STAMP); - auto processPath = get_process_path(window); - if (!processPath.empty()) + auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL); + if (monitor) { - JSONHelpers::FancyZonesDataInstance().SetAppLastZone(window, processPath.data(), -1); + auto zoneWindow = m_zoneWindowMap.find(monitor); + if (zoneWindow != m_zoneWindowMap.end()) + { + const auto zoneWindowPtr = zoneWindow->second; + const auto activeZoneSet = zoneWindowPtr->ActiveZoneSet(); + if (activeZoneSet) + { + wil::unique_cotaskmem_string guidString; + if (SUCCEEDED_LOG(StringFromCLSID(activeZoneSet->Id(), &guidString))) + { + JSONHelpers::FancyZonesDataInstance().RemoveAppLastZone(window, zoneWindowPtr->UniqueId(), guidString.get()); + } + } + } } } } @@ -933,7 +972,8 @@ void FancyZones::HandleVirtualDesktopUpdates(HANDLE fancyZonesDestroyedEvent) no std::unique_lock writeLock(m_lock); for (auto it = begin(m_virtualDesktopIds); it != end(m_virtualDesktopIds);) { - if (auto iter = temp.find(it->first); iter == temp.end()) + auto iter = temp.find(it->first); + if (iter == temp.end()) { it = m_virtualDesktopIds.erase(it); // virtual desktop closed, remove it from map } diff --git a/src/modules/fancyzones/lib/JsonHelpers.cpp b/src/modules/fancyzones/lib/JsonHelpers.cpp index 21faba8bd7cb..8da771cbaa07 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.cpp +++ b/src/modules/fancyzones/lib/JsonHelpers.cpp @@ -3,6 +3,8 @@ #include "RegistryHelpers.h" #include "ZoneSet.h" +#include + #include #include #include @@ -156,40 +158,57 @@ namespace JSONHelpers } } - int FancyZonesData::GetAppLastZone(HWND window, PCWSTR appPath) const + int FancyZonesData::GetAppLastZoneIndex(HWND window, const std::wstring_view& deviceId, const std::wstring_view& zoneSetId) const { - int iZoneIndex = -1; - - if (auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL)) + auto processPath = get_process_path(window); + if (!processPath.empty()) { - std::wstring path{ appPath }; - if (appZoneHistoryMap.contains(path)) + auto history = appZoneHistoryMap.find(processPath); + if (history != appZoneHistoryMap.end()) { - iZoneIndex = appZoneHistoryMap.at(path).zoneIndex; + const auto& data = history->second; + if (data.zoneSetUuid == zoneSetId && data.deviceId == deviceId) + { + return history->second.zoneIndex; + } } } - return iZoneIndex; + + return -1; } - // Pass -1 for the zoneIndex to delete the entry from the map - bool FancyZonesData::SetAppLastZone(HWND window, PCWSTR appPath, DWORD zoneIndex) + bool FancyZonesData::RemoveAppLastZone(HWND window, const std::wstring_view& deviceId, const std::wstring_view& zoneSetId) { - if (auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL)) + auto processPath = get_process_path(window); + if (!processPath.empty()) { - if (zoneIndex == -1) + auto history = appZoneHistoryMap.find(processPath); + if (history != appZoneHistoryMap.end()) { - appZoneHistoryMap.erase(std::wstring{ appPath }); - } - else - { - //TODO(stefan) provide correct uuid in the future - appZoneHistoryMap[std::wstring{ appPath }] = AppZoneHistoryData{ L"", static_cast(zoneIndex) }; + const auto& data = history->second; + if (data.zoneSetUuid == zoneSetId && data.deviceId == deviceId) + { + appZoneHistoryMap.erase(processPath); + return true; + } } - return true; } + return false; } + bool FancyZonesData::SetAppLastZone(HWND window, const std::wstring& deviceId, const std::wstring& zoneSetId, int zoneIndex) + { + auto processPath = get_process_path(window); + if (processPath.empty()) + { + return false; + } + + appZoneHistoryMap[processPath] = AppZoneHistoryData{ .zoneSetUuid = zoneSetId, .deviceId = deviceId, .zoneIndex = zoneIndex }; + return true; + } + void FancyZonesData::SetActiveZoneSet(const std::wstring& deviceId, const std::wstring& uuid) { if (!uuid.empty() && deviceInfoMap.find(deviceId) != deviceInfoMap.end()) @@ -509,7 +528,7 @@ namespace JSONHelpers DWORD i = 0; while (RegEnumValueW(hkey, i++, value, &valueLength, nullptr, nullptr, reinterpret_cast(&zoneIndex), &dataSize) == ERROR_SUCCESS) { - appZoneHistoryMap[std::wstring{ value }] = AppZoneHistoryData{ L"", static_cast(zoneIndex) }; //TODO(stefan) provide correct uuid in the future + appZoneHistoryMap[std::wstring{ value }] = AppZoneHistoryData{ .zoneSetUuid = L"", .deviceId = L"", .zoneIndex = static_cast(zoneIndex) }; //TODO(stefan) provide correct uuid in the future valueLength = ARRAYSIZE(value); dataSize = sizeof(zoneIndex); @@ -676,8 +695,9 @@ namespace JSONHelpers json::JsonObject result{}; result.SetNamedValue(L"app-path", json::value(appZoneHistory.appPath)); - result.SetNamedValue(L"zoneset-uuid", json::value(appZoneHistory.data.zoneSetUuid)); result.SetNamedValue(L"zone-index", json::value(appZoneHistory.data.zoneIndex)); + result.SetNamedValue(L"device-id", json::value(appZoneHistory.data.deviceId)); + result.SetNamedValue(L"zoneset-uuid", json::value(appZoneHistory.data.zoneSetUuid)); return result; } @@ -689,8 +709,9 @@ namespace JSONHelpers AppZoneHistoryJSON result; result.appPath = zoneSet.GetNamedString(L"app-path"); - result.data.zoneSetUuid = zoneSet.GetNamedString(L"zoneset-uuid"); result.data.zoneIndex = static_cast(zoneSet.GetNamedNumber(L"zone-index")); + result.data.deviceId = zoneSet.GetNamedString(L"device-id"); + result.data.zoneSetUuid = zoneSet.GetNamedString(L"zoneset-uuid"); return result; } diff --git a/src/modules/fancyzones/lib/JsonHelpers.h b/src/modules/fancyzones/lib/JsonHelpers.h index a0486e3ea8cc..a1b40e441530 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.h +++ b/src/modules/fancyzones/lib/JsonHelpers.h @@ -126,9 +126,9 @@ namespace JSONHelpers struct AppZoneHistoryData { - std::wstring zoneSetUuid; //TODO(stefan): is this nessecary? It doesn't exist with registry impl. + std::wstring zoneSetUuid; + std::wstring deviceId; int zoneIndex; - //TODO(stefan): Also, do we need DeviceID here? Do we want to support that - app history per monitor? }; struct AppZoneHistoryJSON @@ -197,8 +197,9 @@ namespace JSONHelpers void AddDevice(const std::wstring& deviceId); - int GetAppLastZone(HWND window, PCWSTR appPath) const; - bool SetAppLastZone(HWND window, PCWSTR appPath, DWORD zoneIndex); //TODO(stefan): Missing zone uuid (pass as arg) + int GetAppLastZoneIndex(HWND window, const std::wstring_view& deviceId, const std::wstring_view& zoneSetId) const; + bool RemoveAppLastZone(HWND window, const std::wstring_view& deviceId, const std::wstring_view& zoneSetId); + bool SetAppLastZone(HWND window, const std::wstring& deviceId, const std::wstring& zoneSetId, int zoneIndex); void SetActiveZoneSet(const std::wstring& deviceId, const std::wstring& uuid); diff --git a/src/modules/fancyzones/lib/ZoneWindow.cpp b/src/modules/fancyzones/lib/ZoneWindow.cpp index 23adc300ea80..e7dc8083d405 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.cpp +++ b/src/modules/fancyzones/lib/ZoneWindow.cpp @@ -491,13 +491,18 @@ ZoneWindow::CycleActiveZoneSet(DWORD wparam) noexcept IFACEMETHODIMP_(void) ZoneWindow::SaveWindowProcessToZoneIndex(HWND window) noexcept { - auto processPath = get_process_path(window); - if (!processPath.empty() && m_activeZoneSet) + if (m_activeZoneSet) { DWORD zoneIndex = static_cast(m_activeZoneSet->GetZoneIndexFromWindow(window)); if (zoneIndex != -1) { - JSONHelpers::FancyZonesDataInstance().SetAppLastZone(window, processPath.data(), zoneIndex); + OLECHAR* guidString; + if (StringFromCLSID(m_activeZoneSet->Id(), &guidString) == S_OK) + { + JSONHelpers::FancyZonesDataInstance().SetAppLastZone(window, m_uniqueId, guidString, zoneIndex); + } + + CoTaskMemFree(guidString); } } } diff --git a/src/modules/fancyzones/tests/UnitTests/FancyZones.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/FancyZones.Spec.cpp index 1a3165c6d4fa..d3595033222f 100644 --- a/src/modules/fancyzones/tests/UnitTests/FancyZones.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/FancyZones.Spec.cpp @@ -321,23 +321,6 @@ namespace FancyZonesUnitTests Assert::IsFalse(m_fzCallback->InMoveSize()); } - TEST_METHOD(MoveSizeEndAppLastZoneTest) - { - const auto window = Mocks::WindowCreate(m_hInst); - const auto processPath = get_process_path(window); - - Assert::AreEqual(-1, m_fancyZonesData.GetAppLastZone(window, processPath.c_str())); - - m_fzCallback->MoveSizeEnd(window, POINT{ 0, 0 }); - Assert::AreEqual(-1, m_fancyZonesData.GetAppLastZone(window, processPath.c_str())); - - m_fancyZonesData.SetAppLastZone(window, processPath.c_str(), 1); - Assert::AreEqual(1, m_fancyZonesData.GetAppLastZone(window, processPath.c_str())); - - m_fzCallback->MoveSizeEnd(window, POINT{ 0, 0 }); - Assert::AreEqual(-1, m_fancyZonesData.GetAppLastZone(window, processPath.c_str())); - } - TEST_METHOD(OnKeyDownNothingPressed) { for (DWORD code = '0'; code <= '9'; code++) diff --git a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp index e94e79d79733..73d35171e5d3 100644 --- a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp +++ b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp @@ -2,6 +2,7 @@ #include #include +#include "util.h" #include @@ -611,8 +612,8 @@ namespace FancyZonesUnitTests { TEST_METHOD(ToJson) { - AppZoneHistoryJSON appZoneHistory{ L"appPath", AppZoneHistoryData{ L"zone-set-uuid", 54321 } }; - json::JsonObject expected = json::JsonObject::Parse(L"{\"app-path\": \"appPath\", \"zoneset-uuid\": \"zone-set-uuid\", \"zone-index\": 54321}"); + AppZoneHistoryJSON appZoneHistory{ L"appPath", AppZoneHistoryData{ .zoneSetUuid = L"zoneset-uuid", .deviceId = L"device-id", .zoneIndex = 54321 } }; + json::JsonObject expected = json::JsonObject::Parse(L"{\"app-path\": \"appPath\", \"device-id\": \"device-id\", \"zoneset-uuid\": \"zoneset-uuid\", \"zone-index\": 54321}"); auto actual = AppZoneHistoryJSON::ToJson(appZoneHistory); compareJsonObjects(expected, actual); @@ -620,20 +621,21 @@ namespace FancyZonesUnitTests TEST_METHOD(FromJson) { - AppZoneHistoryJSON expected{ L"appPath", AppZoneHistoryData{ L"zone-set-uuid", 54321 } }; - json::JsonObject json = json::JsonObject::Parse(L"{\"app-path\": \"appPath\", \"zoneset-uuid\": \"zone-set-uuid\", \"zone-index\": 54321}"); + AppZoneHistoryJSON expected{ L"appPath", AppZoneHistoryData{ .zoneSetUuid = L"zoneset-uuid", .deviceId = L"device-id", .zoneIndex = 54321 } }; + json::JsonObject json = json::JsonObject::Parse(L"{\"app-path\": \"appPath\", \"device-id\": \"device-id\", \"zoneset-uuid\": \"zoneset-uuid\", \"zone-index\": 54321}"); auto actual = AppZoneHistoryJSON::FromJson(json); Assert::IsTrue(actual.has_value()); Assert::AreEqual(expected.appPath.c_str(), actual->appPath.c_str()); Assert::AreEqual(expected.data.zoneIndex, actual->data.zoneIndex); + Assert::AreEqual(expected.data.deviceId.c_str(), actual->data.deviceId.c_str()); Assert::AreEqual(expected.data.zoneSetUuid.c_str(), actual->data.zoneSetUuid.c_str()); } TEST_METHOD(FromJsonMissingKeys) { - AppZoneHistoryJSON appZoneHistory{ L"appPath", AppZoneHistoryData{ L"zone-set-uuid", 54321 } }; + AppZoneHistoryJSON appZoneHistory{ L"appPath", AppZoneHistoryData{ .zoneSetUuid = L"zoneset-uuid", .deviceId = L"device-id", .zoneIndex = 54321 } }; const auto json = AppZoneHistoryJSON::ToJson(appZoneHistory); auto iter = json.First(); @@ -759,6 +761,8 @@ namespace FancyZonesUnitTests const json::JsonValue m_defaultCustomDeviceValue = json::JsonValue::Parse(m_defaultCustomDeviceStr); const json::JsonObject m_defaultCustomDeviceObj = json::JsonObject::Parse(m_defaultCustomDeviceStr); + HINSTANCE m_hInst{}; + void compareJsonArrays(const json::JsonArray& expected, const json::JsonArray& actual) { Assert::AreEqual(expected.Size(), actual.Size()); @@ -768,6 +772,11 @@ namespace FancyZonesUnitTests } } + TEST_METHOD_INITIALIZE(Init) + { + m_hInst = (HINSTANCE)GetModuleHandleW(nullptr); + } + public: TEST_METHOD(FancyZonesDataPath) { @@ -985,9 +994,13 @@ namespace FancyZonesUnitTests TEST_METHOD(AppZoneHistoryParseSingle) { - const std::wstring appPath = L"appPath"; + const std::wstring expectedAppPath = L"appPath"; + const std::wstring expectedDeviceId = L"device-id"; + const std::wstring expectedZoneSetId = L"zone-set-id"; + const int expectedIndex = 54321; + json::JsonObject json; - AppZoneHistoryJSON expected{ appPath, AppZoneHistoryData{ L"zone-set-uuid", 54321 } }; + AppZoneHistoryJSON expected{ expectedAppPath, AppZoneHistoryData{ .zoneSetUuid = expectedZoneSetId, .deviceId = expectedDeviceId, .zoneIndex = expectedIndex } }; json::JsonArray zoneHistoryArray; zoneHistoryArray.Append(AppZoneHistoryJSON::ToJson(expected)); json.SetNamedValue(L"app-zone-history", json::JsonValue::Parse(zoneHistoryArray.Stringify())); @@ -995,22 +1008,27 @@ namespace FancyZonesUnitTests FancyZonesData data; data.ParseAppZoneHistory(json); - auto actualMap = data.GetAppZoneHistoryMap(); - Assert::AreEqual((size_t)zoneHistoryArray.Size(), actualMap.size()); + const auto actualProcessHistoryMap = data.GetAppZoneHistoryMap(); + Assert::AreEqual((size_t)zoneHistoryArray.Size(), actualProcessHistoryMap.size()); + + const auto actualProcessHistory = actualProcessHistoryMap.begin(); + Assert::AreEqual(expectedAppPath.c_str(), actualProcessHistory->first.c_str()); - auto actual = actualMap.find(appPath)->second; - Assert::AreEqual(expected.data.zoneSetUuid.c_str(), actual.zoneSetUuid.c_str()); - Assert::AreEqual(expected.data.zoneIndex, actual.zoneIndex); + const auto actualAppZoneHistory = actualProcessHistory->second; + Assert::AreEqual(expectedZoneSetId.c_str(), actualAppZoneHistory.zoneSetUuid.c_str()); + Assert::AreEqual(expectedDeviceId.c_str(), actualAppZoneHistory.deviceId.c_str()); + Assert::AreEqual(expectedIndex, actualAppZoneHistory.zoneIndex); } - TEST_METHOD(AppZoneHistoryParseMany) + TEST_METHOD(AppZoneHistoryParseManyApps) { json::JsonObject json; json::JsonArray zoneHistoryArray; - zoneHistoryArray.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-1", AppZoneHistoryData{ L"zone-set-uuid", 54321 } })); - zoneHistoryArray.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-2", AppZoneHistoryData{ L"zone-set-uuid", 54321 } })); - zoneHistoryArray.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-3", AppZoneHistoryData{ L"zone-set-uuid", 54321 } })); - zoneHistoryArray.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-4", AppZoneHistoryData{ L"zone-set-uuid", 54321 } })); + zoneHistoryArray.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-1", AppZoneHistoryData{ .zoneSetUuid = L"zoneset-uuid-1", .deviceId = L"device-id-1", .zoneIndex = 1 } })); + zoneHistoryArray.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-2", AppZoneHistoryData{ .zoneSetUuid = L"zoneset-uuid-2", .deviceId = L"device-id-2", .zoneIndex = 2 } })); + zoneHistoryArray.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-3", AppZoneHistoryData{ .zoneSetUuid = L"zoneset-uuid-3", .deviceId = L"device-id-3", .zoneIndex = 3 } })); + zoneHistoryArray.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-4", AppZoneHistoryData{ .zoneSetUuid = L"zoneset-uuid-4", .deviceId = L"device-id-4", .zoneIndex = 4 } })); + json.SetNamedValue(L"app-zone-history", json::JsonValue::Parse(zoneHistoryArray.Stringify())); FancyZonesData data; @@ -1019,11 +1037,16 @@ namespace FancyZonesUnitTests auto actualMap = data.GetAppZoneHistoryMap(); Assert::AreEqual((size_t)zoneHistoryArray.Size(), actualMap.size()); + const auto actualProcessHistoryMap = data.GetAppZoneHistoryMap(); + Assert::AreEqual((size_t)zoneHistoryArray.Size(), actualProcessHistoryMap.size()); + auto iter = zoneHistoryArray.First(); while (iter.HasCurrent()) { - auto expected = AppZoneHistoryJSON::FromJson(json::JsonObject::Parse(iter.Current().Stringify())); - auto actual = actualMap.find(expected->appPath)->second; + auto expected = AppZoneHistoryJSON::FromJson(json::JsonObject::Parse(iter.Current().Stringify())); + + const auto actual = actualProcessHistoryMap.at(expected->appPath); + Assert::AreEqual(expected->data.deviceId.c_str(), actual.deviceId.c_str()); Assert::AreEqual(expected->data.zoneSetUuid.c_str(), actual.zoneSetUuid.c_str()); Assert::AreEqual(expected->data.zoneIndex, actual.zoneIndex); @@ -1031,6 +1054,31 @@ namespace FancyZonesUnitTests } } + TEST_METHOD(AppZoneHistoryParseManyZonesForSingleApp) + { + json::JsonObject json; + json::JsonArray zoneHistoryArray; + + const auto appPath = L"app-path"; + zoneHistoryArray.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ appPath, AppZoneHistoryData{ .zoneSetUuid = L"zoneset-uuid-1", .deviceId = L"device-id-1", .zoneIndex = 1 } })); + zoneHistoryArray.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ appPath, AppZoneHistoryData{ .zoneSetUuid = L"zoneset-uuid-2", .deviceId = L"device-id-2", .zoneIndex = 2 } })); + zoneHistoryArray.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ appPath, AppZoneHistoryData{ .zoneSetUuid = L"zoneset-uuid-3", .deviceId = L"device-id-3", .zoneIndex = 3 } })); + const auto expected = AppZoneHistoryData{ .zoneSetUuid = L"zoneset-uuid-4", .deviceId = L"device-id-4", .zoneIndex = 4 }; + zoneHistoryArray.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ appPath, expected })); + json.SetNamedValue(L"app-zone-history", json::JsonValue::Parse(zoneHistoryArray.Stringify())); + + FancyZonesData data; + data.ParseAppZoneHistory(json); + + const auto actualProcessHistoryMap = data.GetAppZoneHistoryMap(); + Assert::AreEqual((size_t)1, actualProcessHistoryMap.size()); + + const auto actual = actualProcessHistoryMap.at(appPath); + Assert::AreEqual(expected.deviceId.c_str(), actual.deviceId.c_str()); + Assert::AreEqual(expected.zoneSetUuid.c_str(), actual.zoneSetUuid.c_str()); + Assert::AreEqual(expected.zoneIndex, actual.zoneIndex); + } + TEST_METHOD(AppZoneHistoryParseEmpty) { FancyZonesData data; @@ -1044,7 +1092,7 @@ namespace FancyZonesUnitTests { const std::wstring appPath = L"appPath"; json::JsonObject json; - AppZoneHistoryJSON expected{ appPath, AppZoneHistoryData{ L"zone-set-uuid", 54321 } }; + AppZoneHistoryJSON expected{ appPath, AppZoneHistoryData{ .zoneSetUuid = L"zoneset-uuid", .deviceId = L"device-id", .zoneIndex = 54321 } }; json.SetNamedValue(L"app-zone-history", json::JsonValue::Parse(AppZoneHistoryJSON::ToJson(expected).Stringify())); FancyZonesData data; @@ -1057,7 +1105,7 @@ namespace FancyZonesUnitTests { const std::wstring appPath = L"appPath"; json::JsonArray expected; - expected.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ appPath, AppZoneHistoryData{ L"zone-set-uuid", 54321 } })); + expected.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ appPath, AppZoneHistoryData{ .zoneSetUuid = L"zoneset-uuid", .deviceId = L"device-id", .zoneIndex = 54321 } })); json::JsonObject json; json.SetNamedValue(L"app-zone-history", json::JsonValue::Parse(expected.Stringify())); @@ -1072,10 +1120,10 @@ namespace FancyZonesUnitTests { json::JsonObject json; json::JsonArray expected; - expected.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-1", AppZoneHistoryData{ L"zone-set-uuid", 54321 } })); - expected.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-2", AppZoneHistoryData{ L"zone-set-uuid", 54321 } })); - expected.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-3", AppZoneHistoryData{ L"zone-set-uuid", 54321 } })); - expected.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-4", AppZoneHistoryData{ L"zone-set-uuid", 54321 } })); + expected.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-1", AppZoneHistoryData{ .zoneSetUuid = L"zoneset-uuid", .deviceId = L"device-id", .zoneIndex = 54321 } })); + expected.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-2", AppZoneHistoryData{ .zoneSetUuid = L"zoneset-uuid", .deviceId = L"device-id", .zoneIndex = 54321 } })); + expected.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-3", AppZoneHistoryData{ .zoneSetUuid = L"zoneset-uuid", .deviceId = L"device-id", .zoneIndex = 54321 } })); + expected.Append(AppZoneHistoryJSON::ToJson(AppZoneHistoryJSON{ L"app-path-4", AppZoneHistoryData{ .zoneSetUuid = L"zoneset-uuid", .deviceId = L"device-id", .zoneIndex = 54321 } })); json.SetNamedValue(L"app-zone-history", json::JsonValue::Parse(expected.Stringify())); FancyZonesData data; @@ -1386,7 +1434,7 @@ namespace FancyZonesUnitTests .columnsPercents = { 2500, 5000, 2500 }, .cellChildMap = { { 0, 1, 2 } } })); CustomZoneSetJSON zoneSets{ L"zone-set-uuid", CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } }; - AppZoneHistoryJSON appZoneHistory{ L"app-path", AppZoneHistoryData{ L"zone-set-uuid", 54321 } }; + AppZoneHistoryJSON appZoneHistory{ L"app-path", AppZoneHistoryData{ .zoneSetUuid = L"zoneset-uuid", .deviceId = L"device-id", .zoneIndex = 54321 } }; DeviceInfoJSON deviceInfo{ L"uuid", DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 } }; json::JsonArray zoneSetsArray, appZonesArray, deviceInfoArray; zoneSetsArray.Append(CustomZoneSetJSON::ToJson(zoneSets)); @@ -1464,5 +1512,179 @@ namespace FancyZonesUnitTests Assert::IsTrue(actual); } + + TEST_METHOD(AppLastZoneIndex) + { + const std::wstring deviceId = L"device-id"; + const std::wstring zoneSetId = L"zoneset-uuid"; + const auto window = Mocks::WindowCreate(m_hInst); + FancyZonesData data; + + Assert::AreEqual(-1, data.GetAppLastZoneIndex(window, deviceId, zoneSetId)); + + const int expectedZoneIndex = 10; + Assert::IsTrue(data.SetAppLastZone(window, deviceId, zoneSetId, expectedZoneIndex)); + Assert::AreEqual(expectedZoneIndex, data.GetAppLastZoneIndex(window, deviceId, zoneSetId)); + } + + TEST_METHOD(AppLastZoneIndexZero) + { + const std::wstring zoneSetId = L"zoneset-uuid"; + const std::wstring deviceId = L"device-id"; + const auto window = Mocks::WindowCreate(m_hInst); + FancyZonesData data; + + const int expectedZoneIndex = 0; + Assert::IsTrue(data.SetAppLastZone(window, deviceId, zoneSetId, expectedZoneIndex)); + Assert::AreEqual(expectedZoneIndex, data.GetAppLastZoneIndex(window, deviceId, zoneSetId)); + } + + TEST_METHOD(AppLastZoneIndexNegative) + { + const std::wstring zoneSetId = L"zoneset-uuid"; + const std::wstring deviceId = L"device-id"; + const auto window = Mocks::WindowCreate(m_hInst); + FancyZonesData data; + + const int expectedZoneIndex = -1; + Assert::IsTrue(data.SetAppLastZone(window, deviceId, zoneSetId, expectedZoneIndex)); + Assert::AreEqual(expectedZoneIndex, data.GetAppLastZoneIndex(window, deviceId, zoneSetId)); + } + + TEST_METHOD(AppLastZoneIndexOverflow) + { + const std::wstring zoneSetId = L"zoneset-uuid"; + const std::wstring deviceId = L"device-id"; + const auto window = Mocks::WindowCreate(m_hInst); + FancyZonesData data; + + const long expectedZoneIndex = LONG_MAX; + Assert::IsTrue(data.SetAppLastZone(window, deviceId, zoneSetId, expectedZoneIndex)); + Assert::AreEqual(static_cast(expectedZoneIndex), data.GetAppLastZoneIndex(window, deviceId, zoneSetId)); + } + + TEST_METHOD(AppLastZoneIndexOverride) + { + const std::wstring zoneSetId = L"zoneset-uuid"; + const std::wstring deviceId = L"device-id"; + const auto window = Mocks::WindowCreate(m_hInst); + FancyZonesData data; + + const int expectedZoneIndex = 3; + Assert::IsTrue(data.SetAppLastZone(window, deviceId, zoneSetId, 1)); + Assert::IsTrue(data.SetAppLastZone(window, deviceId, zoneSetId, 2)); + Assert::IsTrue(data.SetAppLastZone(window, deviceId, zoneSetId, expectedZoneIndex)); + Assert::AreEqual(expectedZoneIndex, data.GetAppLastZoneIndex(window, deviceId, zoneSetId)); + } + + TEST_METHOD(AppLastZoneInvalidWindow) + { + const std::wstring zoneSetId = L"zoneset-uuid"; + const std::wstring deviceId = L"device-id"; + const auto window = Mocks::Window(); + FancyZonesData data; + + Assert::AreEqual(-1, data.GetAppLastZoneIndex(window, deviceId, zoneSetId)); + + const int expectedZoneIndex = 1; + Assert::IsFalse(data.SetAppLastZone(window, deviceId, zoneSetId, expectedZoneIndex)); + } + + TEST_METHOD(AppLastZoneNullWindow) + { + const std::wstring zoneSetId = L"zoneset-uuid"; + const auto window = nullptr; + FancyZonesData data; + + const int expectedZoneIndex = 1; + Assert::IsFalse(data.SetAppLastZone(window, L"device-id", zoneSetId, expectedZoneIndex)); + } + + TEST_METHOD(AppLastdeviceIdTest) + { + const std::wstring zoneSetId = L"zoneset-uuid"; + const std::wstring deviceId1 = L"device-id-1"; + const std::wstring deviceId2 = L"device-id-2"; + const auto window = Mocks::WindowCreate(m_hInst); + FancyZonesData data; + + const int expectedZoneIndex = 10; + Assert::IsTrue(data.SetAppLastZone(window, deviceId1, zoneSetId, expectedZoneIndex)); + Assert::AreEqual(expectedZoneIndex, data.GetAppLastZoneIndex(window, deviceId1, zoneSetId)); + Assert::AreEqual(-1, data.GetAppLastZoneIndex(window, deviceId2, zoneSetId)); + } + + TEST_METHOD(AppLastZoneSetIdTest) + { + const std::wstring zoneSetId1 = L"zoneset-uuid-1"; + const std::wstring zoneSetId2 = L"zoneset-uuid-2"; + const std::wstring deviceId = L"device-id"; + const auto window = Mocks::WindowCreate(m_hInst); + FancyZonesData data; + + const int expectedZoneIndex = 10; + Assert::IsTrue(data.SetAppLastZone(window, deviceId, zoneSetId1, expectedZoneIndex)); + Assert::AreEqual(expectedZoneIndex, data.GetAppLastZoneIndex(window, deviceId, zoneSetId1)); + Assert::AreEqual(-1, data.GetAppLastZoneIndex(window, deviceId, zoneSetId2)); + } + + TEST_METHOD(AppLastZoneRemoveWindow) + { + const std::wstring zoneSetId = L"zoneset-uuid"; + const std::wstring deviceId = L"device-id"; + const auto window = Mocks::WindowCreate(m_hInst); + FancyZonesData data; + + Assert::IsTrue(data.SetAppLastZone(window, deviceId, zoneSetId, 1)); + Assert::IsTrue(data.RemoveAppLastZone(window, deviceId, zoneSetId)); + Assert::AreEqual(-1, data.GetAppLastZoneIndex(window, deviceId, zoneSetId)); + } + + TEST_METHOD(AppLastZoneRemoveUnknownWindow) + { + const std::wstring zoneSetId = L"zoneset-uuid"; + const std::wstring deviceId = L"device-id"; + const auto window = Mocks::WindowCreate(m_hInst); + FancyZonesData data; + + Assert::IsFalse(data.RemoveAppLastZone(window, deviceId, zoneSetId)); + Assert::AreEqual(-1, data.GetAppLastZoneIndex(window, deviceId, zoneSetId)); + } + + TEST_METHOD(AppLastZoneRemoveUnknownZoneSetId) + { + const std::wstring zoneSetIdToInsert = L"zoneset-uuid-to-insert"; + const std::wstring zoneSetIdToRemove = L"zoneset-uuid-to-remove"; + const std::wstring deviceId = L"device-id"; + const auto window = Mocks::WindowCreate(m_hInst); + FancyZonesData data; + + Assert::IsTrue(data.SetAppLastZone(window, deviceId, zoneSetIdToInsert, 1)); + Assert::IsFalse(data.RemoveAppLastZone(window, deviceId, zoneSetIdToRemove)); + Assert::AreEqual(1, data.GetAppLastZoneIndex(window, deviceId, zoneSetIdToInsert)); + } + + TEST_METHOD(AppLastZoneRemoveUnknownWindowId) + { + const std::wstring zoneSetId = L"zoneset-uuid"; + const std::wstring deviceIdToInsert = L"device-id-insert"; + const std::wstring deviceIdToRemove = L"device-id-remove"; + const auto window = Mocks::WindowCreate(m_hInst); + FancyZonesData data; + + Assert::IsTrue(data.SetAppLastZone(window, deviceIdToInsert, zoneSetId, 1)); + Assert::IsFalse(data.RemoveAppLastZone(window, deviceIdToRemove, zoneSetId)); + Assert::AreEqual(1, data.GetAppLastZoneIndex(window, deviceIdToInsert, zoneSetId)); + } + + TEST_METHOD(AppLastZoneRemoveNullWindow) + { + const std::wstring zoneSetId = L"zoneset-uuid"; + const std::wstring deviceId = L"device-id"; + const auto window = Mocks::WindowCreate(m_hInst); + FancyZonesData data; + + Assert::IsFalse(data.RemoveAppLastZone(nullptr, deviceId, zoneSetId)); + } }; } \ No newline at end of file diff --git a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp index d3d0d43ac6b6..c5b9997d380f 100644 --- a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp @@ -2,14 +2,13 @@ #include +#include #include #include #include #include #include "Util.h" -#include - using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace FancyZonesUnitTests @@ -53,11 +52,8 @@ namespace FancyZonesUnitTests JSONHelpers::FancyZonesData& m_fancyZonesData = JSONHelpers::FancyZonesDataInstance(); - std::wstring guidString() + std::wstring GuidString(const GUID& guid) { - GUID guid; - Assert::AreEqual(S_OK, CoCreateGuid(&guid)); - OLECHAR* guidString; Assert::AreEqual(S_OK, StringFromCLSID(guid, &guidString)); @@ -67,6 +63,14 @@ namespace FancyZonesUnitTests return guidStr; } + std::wstring CreateGuidString() + { + GUID guid; + Assert::AreEqual(S_OK, CoCreateGuid(&guid)); + + return GuidString(guid); + } + TEST_METHOD_INITIALIZE(Init) { m_hInst = (HINSTANCE)GetModuleHandleW(nullptr); @@ -101,11 +105,10 @@ namespace FancyZonesUnitTests winrt::com_ptr InitZoneWindowWithActiveZoneSet() { const auto activeZoneSetTempPath = ZoneWindowUtils::GetActiveZoneSetTmpPath(); - Logger::WriteMessage(activeZoneSetTempPath.c_str()); Assert::IsFalse(std::filesystem::exists(activeZoneSetTempPath)); const auto type = JSONHelpers::ZoneSetLayoutType::Columns; - const auto expectedZoneSet = JSONHelpers::ZoneSetData{ guidString(), type, 5 }; + const auto expectedZoneSet = JSONHelpers::ZoneSetData{ CreateGuidString(), type, 5 }; const auto data = JSONHelpers::DeviceInfoData{ expectedZoneSet, true, 16, 3 }; const auto deviceInfo = JSONHelpers::DeviceInfoJSON{ m_uniqueId.str(), data }; const auto json = JSONHelpers::DeviceInfoJSON::ToJson(deviceInfo); @@ -203,7 +206,7 @@ namespace FancyZonesUnitTests for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) { - const auto expectedZoneSet = ZoneSetData{ guidString(), static_cast(type), 5 }; + const auto expectedZoneSet = ZoneSetData{ CreateGuidString(), static_cast(type), 5 }; const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data }; const auto json = DeviceInfoJSON::ToJson(deviceInfo); @@ -227,7 +230,7 @@ namespace FancyZonesUnitTests const auto activeZoneSetTempPath = ZoneWindowUtils::GetActiveZoneSetTmpPath(); const ZoneSetLayoutType type = ZoneSetLayoutType::Custom; - const auto expectedZoneSet = ZoneSetData{ guidString(), type, 5 }; + const auto expectedZoneSet = ZoneSetData{ CreateGuidString(), type, 5 }; const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data }; const auto json = DeviceInfoJSON::ToJson(deviceInfo); @@ -253,7 +256,7 @@ namespace FancyZonesUnitTests const auto appliedZoneSetTempPath = ZoneWindowUtils::GetAppliedZoneSetTmpPath(); const ZoneSetLayoutType type = ZoneSetLayoutType::Custom; - const auto customSetGuid = guidString(); + const auto customSetGuid = CreateGuidString(); const auto expectedZoneSet = ZoneSetData{ customSetGuid, type, 5 }; const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data }; @@ -288,7 +291,7 @@ namespace FancyZonesUnitTests const auto deletedZonesTempPath = ZoneWindowUtils::GetCustomZoneSetsTmpPath(); const ZoneSetLayoutType type = ZoneSetLayoutType::Custom; - const auto customSetGuid = guidString(); + const auto customSetGuid = CreateGuidString(); const auto expectedZoneSet = ZoneSetData{ customSetGuid, type, 5 }; const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data }; @@ -330,7 +333,7 @@ namespace FancyZonesUnitTests const auto deletedZonesTempPath = ZoneWindowUtils::GetCustomZoneSetsTmpPath(); const ZoneSetLayoutType type = ZoneSetLayoutType::Custom; - const auto customSetGuid = guidString(); + const auto customSetGuid = CreateGuidString(); const auto expectedZoneSet = ZoneSetData{ customSetGuid, type, 5 }; const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data }; @@ -348,7 +351,7 @@ namespace FancyZonesUnitTests //save different zone as deleted json::JsonObject deletedCustomZoneSets = {}; json::JsonArray zonesArray{}; - const auto uuid = guidString(); + const auto uuid = CreateGuidString(); zonesArray.Append(json::JsonValue::CreateStringValue(uuid.substr(1, uuid.size() - 2).c_str())); deletedCustomZoneSets.SetNamedValue(L"deleted-custom-zone-sets", zonesArray); json::to_file(deletedZonesTempPath, deletedCustomZoneSets); @@ -539,7 +542,8 @@ namespace FancyZonesUnitTests const auto actualAppZoneHistory = m_fancyZonesData.GetAppZoneHistoryMap(); Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - Assert::AreEqual(0, actualAppZoneHistory.begin()->second.zoneIndex); + const auto actual = actualAppZoneHistory.begin()->second; + Assert::AreEqual(0, actual.zoneIndex); } TEST_METHOD(MoveWindowIntoZoneByDirectionManyTimes) @@ -554,7 +558,8 @@ namespace FancyZonesUnitTests const auto actualAppZoneHistory = m_fancyZonesData.GetAppZoneHistoryMap(); Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - Assert::AreEqual(2, actualAppZoneHistory.begin()->second.zoneIndex); + const auto actual = actualAppZoneHistory.begin()->second; + Assert::AreEqual(2, actual.zoneIndex); } TEST_METHOD(SaveWindowProcessToZoneIndexNoActiveZoneSet) @@ -601,11 +606,13 @@ namespace FancyZonesUnitTests const auto window = Mocks::WindowCreate(m_hInst); const auto processPath = get_process_path(window); - + const auto deviceId = m_zoneWindow->UniqueId(); + const auto zoneSetId = m_zoneWindow->ActiveZoneSet()->Id(); + //fill app zone history map - Assert::IsTrue(m_fancyZonesData.SetAppLastZone(window, processPath.c_str(), 0)); + Assert::IsTrue(m_fancyZonesData.SetAppLastZone(window, deviceId, GuidString(zoneSetId), 0)); Assert::AreEqual((size_t)1, m_fancyZonesData.GetAppZoneHistoryMap().size()); - Assert::AreEqual(0, m_fancyZonesData.GetAppZoneHistoryMap().begin()->second.zoneIndex); + Assert::AreEqual(0, m_fancyZonesData.GetAppZoneHistoryMap().at(processPath).zoneIndex); //add zone without window const auto zone = MakeZone(RECT{ 0, 0, 100, 100 }); @@ -613,7 +620,7 @@ namespace FancyZonesUnitTests m_zoneWindow->SaveWindowProcessToZoneIndex(window); Assert::AreEqual((size_t)1, m_fancyZonesData.GetAppZoneHistoryMap().size()); - Assert::AreEqual(0, m_fancyZonesData.GetAppZoneHistoryMap().begin()->second.zoneIndex); + Assert::AreEqual(0, m_fancyZonesData.GetAppZoneHistoryMap().at(processPath).zoneIndex); } TEST_METHOD(SaveWindowProcessToZoneIndexWindowAdded) @@ -623,21 +630,25 @@ namespace FancyZonesUnitTests auto window = Mocks::WindowCreate(m_hInst); const auto processPath = get_process_path(window); + const auto deviceId = m_zoneWindow->UniqueId(); + const auto zoneSetId = m_zoneWindow->ActiveZoneSet()->Id(); auto zone = MakeZone(RECT{ 0, 0, 100, 100 }); zone->AddWindowToZone(window, Mocks::Window(), false); m_zoneWindow->ActiveZoneSet()->AddZone(zone); //fill app zone history map - Assert::IsTrue(m_fancyZonesData.SetAppLastZone(window, processPath.c_str(), 2)); + Assert::IsTrue(m_fancyZonesData.SetAppLastZone(window, deviceId, GuidString(zoneSetId), 2)); Assert::AreEqual((size_t)1, m_fancyZonesData.GetAppZoneHistoryMap().size()); - Assert::AreEqual(2, m_fancyZonesData.GetAppZoneHistoryMap().begin()->second.zoneIndex); + Assert::AreEqual(2, m_fancyZonesData.GetAppZoneHistoryMap().at(processPath).zoneIndex); m_zoneWindow->SaveWindowProcessToZoneIndex(window); const auto actualAppZoneHistory = m_fancyZonesData.GetAppZoneHistoryMap(); Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - Assert::AreEqual(m_zoneWindow->ActiveZoneSet()->GetZoneIndexFromWindow(window), actualAppZoneHistory.begin()->second.zoneIndex); + const auto expected = m_zoneWindow->ActiveZoneSet()->GetZoneIndexFromWindow(window); + const auto actual = m_fancyZonesData.GetAppZoneHistoryMap().at(processPath).zoneIndex; + Assert::AreEqual(expected, actual); } }; } From 3be9e05ee930dbad90febf3ddbe0851aa623d789 Mon Sep 17 00:00:00 2001 From: Stefan S Date: Wed, 5 Feb 2020 18:04:12 +0100 Subject: [PATCH 20/32] Rename JSON file --- src/modules/fancyzones/lib/JsonHelpers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/fancyzones/lib/JsonHelpers.cpp b/src/modules/fancyzones/lib/JsonHelpers.cpp index 8da771cbaa07..d0ff6e435c3b 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.cpp +++ b/src/modules/fancyzones/lib/JsonHelpers.cpp @@ -20,7 +20,7 @@ namespace constexpr int c_priorityGridModelId = 0xFFFB; constexpr int c_blankCustomModelId = 0xFFFA; - const wchar_t* FANCY_ZONES_DATA_FILE = L"PersistFancyZones.json"; + const wchar_t* FANCY_ZONES_DATA_FILE = L"zones-settings.json"; } namespace JSONHelpers From b3d94cd61d8785b82c68bf953309a46ee2cc653e Mon Sep 17 00:00:00 2001 From: Stefan S Date: Wed, 5 Feb 2020 19:30:46 +0100 Subject: [PATCH 21/32] Remove AppZoneHistory migration --- src/modules/fancyzones/lib/JsonHelpers.cpp | 38 +--------------------- src/modules/fancyzones/lib/JsonHelpers.h | 1 - 2 files changed, 1 insertion(+), 38 deletions(-) diff --git a/src/modules/fancyzones/lib/JsonHelpers.cpp b/src/modules/fancyzones/lib/JsonHelpers.cpp index d0ff6e435c3b..0e7e5c91f756 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.cpp +++ b/src/modules/fancyzones/lib/JsonHelpers.cpp @@ -425,9 +425,8 @@ namespace JSONHelpers { TmpMigrateAppliedZoneSetsFromRegistry(); - // Custom zone sets have to be migrated before applied zone sets! + // Custom zone sets have to be migrated after applied zone sets! MigrateCustomZoneSetsFromRegistry(); - MigrateAppZoneHistoryFromRegistry(); SaveFancyZonesData(); } @@ -502,41 +501,6 @@ namespace JSONHelpers } } - void FancyZonesData::MigrateAppZoneHistoryFromRegistry() - { - auto collectMonitorsData = [](HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) -> BOOL { - std::vector* monitors = reinterpret_cast*>(dwData); - monitors->push_back(hMonitor); - return true; - }; - - std::vector monitors; - - EnumDisplayMonitors(NULL, NULL, collectMonitorsData, reinterpret_cast(&monitors)); - - for (HMONITOR monitor : monitors) - { - wchar_t key[256]; - StringCchPrintf(key, ARRAYSIZE(key), L"%s\\%s\\%x", RegistryHelpers::REG_SETTINGS, RegistryHelpers::APP_ZONE_HISTORY_SUBKEY, monitor); - HKEY hkey; - if (RegOpenKeyExW(HKEY_CURRENT_USER, key, 0, KEY_ALL_ACCESS, &hkey) == ERROR_SUCCESS) - { - DWORD zoneIndex; - DWORD dataSize = sizeof(DWORD); - wchar_t value[256]{}; - DWORD valueLength = ARRAYSIZE(value); - DWORD i = 0; - while (RegEnumValueW(hkey, i++, value, &valueLength, nullptr, nullptr, reinterpret_cast(&zoneIndex), &dataSize) == ERROR_SUCCESS) - { - appZoneHistoryMap[std::wstring{ value }] = AppZoneHistoryData{ .zoneSetUuid = L"", .deviceId = L"", .zoneIndex = static_cast(zoneIndex) }; //TODO(stefan) provide correct uuid in the future - - valueLength = ARRAYSIZE(value); - dataSize = sizeof(zoneIndex); - } - } - } - } - void FancyZonesData::MigrateDeviceInfoFromRegistry(const std::wstring& deviceId) { wchar_t key[256]; diff --git a/src/modules/fancyzones/lib/JsonHelpers.h b/src/modules/fancyzones/lib/JsonHelpers.h index a1b40e441530..a3755ad57eca 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.h +++ b/src/modules/fancyzones/lib/JsonHelpers.h @@ -224,7 +224,6 @@ namespace JSONHelpers private: void TmpMigrateAppliedZoneSetsFromRegistry(); - void MigrateAppZoneHistoryFromRegistry(); //TODO(stefan): If uuid is needed here, it needs to be resolved here some how void MigrateCustomZoneSetsFromRegistry(); std::unordered_map appliedZoneSetsMap{}; From 1365c7146b1e53f8e21cf01ae9b5ac40a478a377 Mon Sep 17 00:00:00 2001 From: vldmr11080 z Date: Wed, 5 Feb 2020 20:42:47 +0100 Subject: [PATCH 22/32] Move parsing of ZoneWindow independent temp files outside of it --- src/modules/fancyzones/lib/FancyZones.cpp | 10 +++++++++- src/modules/fancyzones/lib/ZoneWindow.cpp | 4 ---- .../fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp | 13 +++++++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/modules/fancyzones/lib/FancyZones.cpp b/src/modules/fancyzones/lib/FancyZones.cpp index e48bf228cf62..d0b5750e6675 100644 --- a/src/modules/fancyzones/lib/FancyZones.cpp +++ b/src/modules/fancyzones/lib/FancyZones.cpp @@ -138,6 +138,7 @@ struct FancyZones : public winrt::implements(EditorExitKind::Exit)) { - // Don't reload settings if we terminated the editor + OnEditorExitEvent(); OnDisplayChange(DisplayChangeType::Editor); } @@ -988,6 +989,13 @@ void FancyZones::HandleVirtualDesktopUpdates(HANDLE fancyZonesDestroyedEvent) no } } +void FancyZones::OnEditorExitEvent() noexcept +{ + // Colect information about changes in zone layout after editor exited. + JSONHelpers::FancyZonesDataInstance().ParseDeviceInfoFromTmpFile(ZoneWindowUtils::GetActiveZoneSetTmpPath()); + JSONHelpers::FancyZonesDataInstance().ParseDeletedCustomZoneSetsFromTmpFile(ZoneWindowUtils::GetCustomZoneSetsTmpPath()); +} + winrt::com_ptr MakeFancyZones(HINSTANCE hinstance, const winrt::com_ptr& settings) noexcept { if (!settings) diff --git a/src/modules/fancyzones/lib/ZoneWindow.cpp b/src/modules/fancyzones/lib/ZoneWindow.cpp index e7dc8083d405..9f7e53726073 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.cpp +++ b/src/modules/fancyzones/lib/ZoneWindow.cpp @@ -544,11 +544,7 @@ void ZoneWindow::HideZoneWindow() noexcept void ZoneWindow::LoadSettings() noexcept { JSONHelpers::FancyZonesDataInstance().AddDevice(m_uniqueId); - - JSONHelpers::FancyZonesDataInstance().ParseDeviceInfoFromTmpFile(ZoneWindowUtils::GetActiveZoneSetTmpPath()); - JSONHelpers::FancyZonesDataInstance().ParseDeletedCustomZoneSetsFromTmpFile(ZoneWindowUtils::GetCustomZoneSetsTmpPath()); JSONHelpers::FancyZonesDataInstance().ParseCustomZoneSetFromTmpFile(ZoneWindowUtils::GetAppliedZoneSetTmpPath(), m_uniqueId); - JSONHelpers::FancyZonesDataInstance().SaveFancyZonesData(); } diff --git a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp index c5b9997d380f..70c792f9ce33 100644 --- a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp @@ -115,6 +115,8 @@ namespace FancyZonesUnitTests json::to_file(activeZoneSetTempPath, json); Assert::IsTrue(std::filesystem::exists(activeZoneSetTempPath)); + m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath); + return MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); } @@ -212,6 +214,8 @@ namespace FancyZonesUnitTests const auto json = DeviceInfoJSON::ToJson(deviceInfo); json::to_file(activeZoneSetTempPath, json); + m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath); + //temp file read on initialization auto actual = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); @@ -236,6 +240,8 @@ namespace FancyZonesUnitTests const auto json = DeviceInfoJSON::ToJson(deviceInfo); json::to_file(activeZoneSetTempPath, json); + m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath); + //temp file read on initialization auto actual = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); @@ -269,6 +275,7 @@ namespace FancyZonesUnitTests const auto customZoneData = CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info }; auto customZoneJson = CustomZoneSetJSON::ToJson(CustomZoneSetJSON{ customSetGuid, customZoneData }); json::to_file(appliedZoneSetTempPath, customZoneJson); + m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath); //temp file read on initialization auto actual = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); @@ -313,6 +320,9 @@ namespace FancyZonesUnitTests deletedCustomZoneSets.SetNamedValue(L"deleted-custom-zone-sets", zonesArray); json::to_file(deletedZonesTempPath, deletedCustomZoneSets); + m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath); + m_fancyZonesData.ParseDeletedCustomZoneSetsFromTmpFile(deletedZonesTempPath); + //temp file read on initialization auto actual = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); @@ -356,6 +366,9 @@ namespace FancyZonesUnitTests deletedCustomZoneSets.SetNamedValue(L"deleted-custom-zone-sets", zonesArray); json::to_file(deletedZonesTempPath, deletedCustomZoneSets); + m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath); + m_fancyZonesData.ParseDeletedCustomZoneSetsFromTmpFile(deletedZonesTempPath); + //temp file read on initialization auto actual = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); From 0a8e75e69a5b6b3accde54f54455118a2ac454e2 Mon Sep 17 00:00:00 2001 From: Seraphima Date: Thu, 6 Feb 2020 13:23:08 +0300 Subject: [PATCH 23/32] Unit tests update (#19) * check device existence in map * updated ZoneSet tests * updated JsonHelpers tests --- src/modules/fancyzones/lib/JsonHelpers.cpp | 4 +- .../tests/UnitTests/JsonHelpers.Tests.cpp | 34 ++++++++------ .../tests/UnitTests/ZoneSet.Spec.cpp | 44 ++++++++++++++----- 3 files changed, 58 insertions(+), 24 deletions(-) diff --git a/src/modules/fancyzones/lib/JsonHelpers.cpp b/src/modules/fancyzones/lib/JsonHelpers.cpp index 0e7e5c91f756..1846a0de803d 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.cpp +++ b/src/modules/fancyzones/lib/JsonHelpers.cpp @@ -245,10 +245,12 @@ namespace JSONHelpers bool FancyZonesData::ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath, const std::wstring& deviceId) { - if (deviceInfoMap.at(deviceId).activeZoneSet.type != JSONHelpers::ZoneSetLayoutType::Custom) + const auto device = deviceInfoMap.find(deviceId); + if (device == deviceInfoMap.end() || device->second.activeZoneSet.type != JSONHelpers::ZoneSetLayoutType::Custom) { return false; } + bool res = true; if (std::filesystem::exists(tmpFilePath)) { diff --git a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp index 73d35171e5d3..ea6875777575 100644 --- a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp +++ b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp @@ -762,6 +762,7 @@ namespace FancyZonesUnitTests const json::JsonObject m_defaultCustomDeviceObj = json::JsonObject::Parse(m_defaultCustomDeviceStr); HINSTANCE m_hInst{}; + FancyZonesData& m_fzData = FancyZonesDataInstance(); void compareJsonArrays(const json::JsonArray& expected, const json::JsonArray& actual) { @@ -775,6 +776,7 @@ namespace FancyZonesUnitTests TEST_METHOD_INITIALIZE(Init) { m_hInst = (HINSTANCE)GetModuleHandleW(nullptr); + m_fzData = FancyZonesData(); } public: @@ -1305,9 +1307,20 @@ namespace FancyZonesUnitTests compareJsonArrays(expected, actual); } -#if 0 TEST_METHOD(CustomZoneSetsReadTemp) { + //prepare device data + const std::wstring deviceId = L"default_device_id"; + + { + DeviceInfoJSON deviceInfo{ deviceId, DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 } }; + const std::wstring deviceInfoPath = m_fzData.GetPersistFancyZonesJSONPath() + L".device_info_tmp"; + m_fzData.SerializeDeviceInfoToTmpFile(deviceInfo, deviceInfoPath); + + m_fzData.ParseDeviceInfoFromTmpFile(deviceInfoPath); + std::filesystem::remove(deviceInfoPath); + } + const std::wstring uuid = L"uuid"; const GridLayoutInfo grid(GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{ .rows = 1, @@ -1319,9 +1332,8 @@ namespace FancyZonesUnitTests FancyZonesData data; const std::wstring path = data.GetPersistFancyZonesJSONPath() + L".test_tmp"; - json::to_file(path, CustomZoneSetJSON::ToJson(expected)); - - data.ParseCustomZoneSetFromTmpFile(path); + json::to_file(path, CustomZoneSetJSON::ToJson(expected)); + m_fzData.ParseCustomZoneSetFromTmpFile(path, deviceId); bool actualFileExists = std::filesystem::exists(path); if (actualFileExists) @@ -1330,7 +1342,7 @@ namespace FancyZonesUnitTests } Assert::IsFalse(actualFileExists); - auto devices = data.GetCustomZoneSetsMap(); + auto devices = m_fzData.GetCustomZoneSetsMap(); Assert::AreEqual((size_t)1, devices.size()); auto actual = devices.find(uuid)->second; @@ -1341,20 +1353,16 @@ namespace FancyZonesUnitTests Assert::AreEqual(expectedGrid.rows(), actualGrid.rows()); Assert::AreEqual(expectedGrid.columns(), actualGrid.columns()); } -#endif -#if 0 TEST_METHOD(CustomZoneSetsReadTempUnexsisted) { - FancyZonesData data; - - const std::wstring path = data.GetPersistFancyZonesJSONPath() + L".test_tmp"; + const std::wstring path = m_fzData.GetPersistFancyZonesJSONPath() + L".test_tmp"; + const std::wstring deviceId = L"default_device_id"; - data.ParseCustomZoneSetFromTmpFile(path); - auto devices = data.GetDeviceInfoMap(); + m_fzData.ParseCustomZoneSetFromTmpFile(path, deviceId); + auto devices = m_fzData.GetDeviceInfoMap(); Assert::AreEqual((size_t)0, devices.size()); } -#endif TEST_METHOD(SetActiveZoneSet) { diff --git a/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp index 247a03957fb4..3a2e04968511 100644 --- a/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp @@ -1089,22 +1089,35 @@ namespace FancyZonesUnitTests Assert::IsFalse(result); } } -#if 0 + TEST_METHOD(CustomZoneFromValidCanvasLayoutInfo) { using namespace JSONHelpers; - const std::wstring uuid = L"uuid"; + //prepare device data + { + const std::wstring zoneUuid = L"default_device_id"; + DeviceInfoJSON deviceInfo{ zoneUuid, DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 } }; + const std::wstring deviceInfoPath = FancyZonesDataInstance().GetPersistFancyZonesJSONPath() + L".device_info_tmp"; + FancyZonesDataInstance().SerializeDeviceInfoToTmpFile(deviceInfo, deviceInfoPath); + + FancyZonesDataInstance().ParseDeviceInfoFromTmpFile(deviceInfoPath); + std::filesystem::remove(deviceInfoPath); + } + + //prepare expected data + wil::unique_cotaskmem_string uuid; + Assert::AreEqual(S_OK, StringFromCLSID(m_id, &uuid)); const CanvasLayoutInfo info{ 123, 321, { CanvasLayoutInfo::Rect{ 0, 0, 100, 100 }, CanvasLayoutInfo::Rect{ 50, 50, 150, 150 } } }; - CustomZoneSetJSON expected{ uuid, CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info } }; + CustomZoneSetJSON expected{ uuid.get(), CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info } }; json::to_file(m_path, CustomZoneSetJSON::ToJson(expected)); Assert::IsTrue(std::filesystem::exists(m_path)); + FancyZonesDataInstance().ParseCustomZoneSetFromTmpFile(m_path, L"default_device_id"); + //test const int spacing = 10; const auto zoneCount = info.zones.size(); - ZoneSetConfig m_config = ZoneSetConfig(m_id, TZoneSetLayoutType::Custom, m_monitor, m_resolutionKey); - for (const auto& monitorInfo : m_popularMonitors) { auto set = MakeZoneSet(m_config); @@ -1113,23 +1126,35 @@ namespace FancyZonesUnitTests checkZones(set, zoneCount, monitorInfo); } } -#endif -#if 0 TEST_METHOD(CustomZoneFromValidGridFullLayoutInfo) { using namespace JSONHelpers; - const std::wstring uuid = L"uuid"; + //prepare device data + { + const std::wstring zoneUuid = L"default_device_id"; + DeviceInfoJSON deviceInfo{ zoneUuid, DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 } }; + const std::wstring deviceInfoPath = FancyZonesDataInstance().GetPersistFancyZonesJSONPath() + L".device_info_tmp"; + FancyZonesDataInstance().SerializeDeviceInfoToTmpFile(deviceInfo, deviceInfoPath); + + FancyZonesDataInstance().ParseDeviceInfoFromTmpFile(deviceInfoPath); + std::filesystem::remove(deviceInfoPath); + } + + //prepare expected data + wil::unique_cotaskmem_string uuid; + Assert::AreEqual(S_OK, StringFromCLSID(m_id, &uuid)); const GridLayoutInfo grid(GridLayoutInfo(JSONHelpers::GridLayoutInfo::Full{ .rows = 1, .columns = 3, .rowsPercents = { 10000 }, .columnsPercents = { 2500, 5000, 2500 }, .cellChildMap = { { 0, 1, 2 } } })); - CustomZoneSetJSON expected{ uuid, CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } }; + CustomZoneSetJSON expected{ uuid.get(), CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } }; json::to_file(m_path, CustomZoneSetJSON::ToJson(expected)); Assert::IsTrue(std::filesystem::exists(m_path)); + FancyZonesDataInstance().ParseCustomZoneSetFromTmpFile(m_path, L"default_device_id"); const int spacing = 10; const int zoneCount = grid.rows() * grid.columns(); @@ -1144,7 +1169,6 @@ namespace FancyZonesUnitTests checkZones(set, zoneCount, monitorInfo); } } -#endif TEST_METHOD(CustomZoneFromValidGridMinimalLayoutInfo) { From 6282002ed94ebab978e23db4410a3e47965abc2a Mon Sep 17 00:00:00 2001 From: Stefan S Date: Thu, 6 Feb 2020 17:50:39 +0100 Subject: [PATCH 24/32] Use single zone count information --- .../FancyZonesEditor/Models/LayoutModel.cs | 6 +---- src/modules/fancyzones/lib/JsonHelpers.cpp | 11 +--------- src/modules/fancyzones/lib/JsonHelpers.h | 1 - src/modules/fancyzones/lib/ZoneWindow.cpp | 2 +- .../tests/UnitTests/JsonHelpers.Tests.cpp | 22 +++++-------------- .../tests/UnitTests/ZoneWindow.Spec.cpp | 13 +++++------ 6 files changed, 15 insertions(+), 40 deletions(-) diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs index 1a31301b6830..ef9adeb1357f 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs @@ -271,11 +271,6 @@ public void Apply(System.Windows.Int32Rect[] zones) break; } - if (!custom) - { - writer.WriteNumber("zone-count", zoneCount); - } - writer.WriteEndObject(); Settings settings = ((App)Application.Current).ZoneSettings; @@ -283,6 +278,7 @@ public void Apply(System.Windows.Int32Rect[] zones) writer.WriteBoolean("editor-show-spacing", settings.ShowSpacing); writer.WriteNumber("editor-spacing", settings.Spacing); writer.WriteNumber("editor-zone-count", settings.ZoneCount); + writer.WriteEndObject(); writer.Flush(); outputStream.Close(); diff --git a/src/modules/fancyzones/lib/JsonHelpers.cpp b/src/modules/fancyzones/lib/JsonHelpers.cpp index 1846a0de803d..adb52e407e83 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.cpp +++ b/src/modules/fancyzones/lib/JsonHelpers.cpp @@ -152,7 +152,7 @@ namespace JSONHelpers if (!deviceInfoMap.contains(deviceId)) { // Creates default entry in map when ZoneWindow is created - deviceInfoMap[deviceId] = DeviceInfoData{ ZoneSetData{ L"null", ZoneSetLayoutType::Grid, 1 }, true, 16, 3 }; + deviceInfoMap[deviceId] = DeviceInfoData{ ZoneSetData{ L"null", ZoneSetLayoutType::Grid }, true, 16, 3 }; MigrateDeviceInfoFromRegistry(deviceId); } @@ -486,7 +486,6 @@ namespace JSONHelpers if (appliedZoneSetData.type != ZoneSetLayoutType::Custom) { appliedZoneSetData.uuid = std::wstring{ value }; - appliedZoneSetData.zoneCount = data.ZoneCount; } else { @@ -627,10 +626,6 @@ namespace JSONHelpers result.SetNamedValue(L"uuid", json::value(zoneSet.uuid)); result.SetNamedValue(L"type", json::value(TypeToString(zoneSet.type))); - if (zoneSet.type != ZoneSetLayoutType::Custom) - { - result.SetNamedValue(L"zone-count", json::value(*zoneSet.zoneCount)); - } return result; } @@ -643,10 +638,6 @@ namespace JSONHelpers zoneSetData.uuid = zoneSet.GetNamedString(L"uuid"); zoneSetData.type = TypeFromString(std::wstring{ zoneSet.GetNamedString(L"type") }); - if (zoneSetData.type != ZoneSetLayoutType::Custom) - { - zoneSetData.zoneCount = static_cast(zoneSet.GetNamedNumber(L"zone-count")); - } return zoneSetData; } diff --git a/src/modules/fancyzones/lib/JsonHelpers.h b/src/modules/fancyzones/lib/JsonHelpers.h index a3755ad57eca..c49c84272a41 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.h +++ b/src/modules/fancyzones/lib/JsonHelpers.h @@ -118,7 +118,6 @@ namespace JSONHelpers { std::wstring uuid; ZoneSetLayoutType type; - std::optional zoneCount; static json::JsonObject ToJson(const ZoneSetData& zoneSet); static std::optional FromJson(const json::JsonObject& zoneSet); diff --git a/src/modules/fancyzones/lib/ZoneWindow.cpp b/src/modules/fancyzones/lib/ZoneWindow.cpp index 9f7e53726073..4ab38ff270c4 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.cpp +++ b/src/modules/fancyzones/lib/ZoneWindow.cpp @@ -583,7 +583,7 @@ void ZoneWindow::CalculateZoneSet() noexcept { bool showSpacing = deviceInfoMap.at(m_uniqueId).showSpacing; int spacing = showSpacing ? deviceInfoMap.at(m_uniqueId).spacing : 0; - int zoneCount = activeZoneSet.zoneCount.has_value() ? activeZoneSet.zoneCount.value() : 0; + int zoneCount = deviceInfoMap.at(m_uniqueId).zoneCount; zoneSet->CalculateZones(monitorInfo, zoneCount, spacing); UpdateActiveZoneSet(zoneSet.get()); } diff --git a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp index ea6875777575..2803a2a71abf 100644 --- a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp +++ b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp @@ -530,8 +530,8 @@ namespace FancyZonesUnitTests TEST_METHOD(ToJsonGeneral) { - json::JsonObject expected = json::JsonObject::Parse(L"{\"uuid\": \"uuid\", \"type\": \"rows\", \"zone-count\": 47372}"); - ZoneSetData data{ L"uuid", ZoneSetLayoutType::Rows, 47372 }; + json::JsonObject expected = json::JsonObject::Parse(L"{\"uuid\": \"uuid\", \"type\": \"rows\"}"); + ZoneSetData data{ L"uuid", ZoneSetLayoutType::Rows }; const auto actual = ZoneSetData::ToJson(data); compareJsonObjects(expected, actual); } @@ -546,34 +546,30 @@ namespace FancyZonesUnitTests Assert::AreEqual(expected.uuid.c_str(), actual->uuid.c_str()); Assert::AreEqual((int)expected.type, (int)actual->type); - Assert::IsFalse(actual->zoneCount.has_value()); } TEST_METHOD(FromJsonCustomZoneAdded) { ZoneSetData expected{ L"uuid", ZoneSetLayoutType::Custom }; - json::JsonObject json = json::JsonObject::Parse(L"{\"uuid\": \"uuid\", \"type\": \"custom\", \"zone-count\": 47372}"); + json::JsonObject json = json::JsonObject::Parse(L"{\"uuid\": \"uuid\", \"type\": \"custom\"}"); auto actual = ZoneSetData::FromJson(json); Assert::IsTrue(actual.has_value()); Assert::AreEqual(expected.uuid.c_str(), actual->uuid.c_str()); Assert::AreEqual((int)expected.type, (int)actual->type); - Assert::IsFalse(actual->zoneCount.has_value()); } TEST_METHOD(FromJsonGeneral) { - ZoneSetData expected{ L"uuid", ZoneSetLayoutType::Columns, 47372 }; + ZoneSetData expected{ L"uuid", ZoneSetLayoutType::Columns }; - json::JsonObject json = json::JsonObject::Parse(L"{\"uuid\": \"uuid\", \"type\": \"columns\", \"zone-count\": 47372}"); + json::JsonObject json = json::JsonObject::Parse(L"{\"uuid\": \"uuid\", \"type\": \"columns\"}"); auto actual = ZoneSetData::FromJson(json); Assert::IsTrue(actual.has_value()); Assert::AreEqual(expected.uuid.c_str(), actual->uuid.c_str()); Assert::AreEqual((int)expected.type, (int)actual->type); - Assert::IsTrue(actual->zoneCount.has_value()); - Assert::AreEqual(*expected.zoneCount, *actual->zoneCount); } TEST_METHOD(FromJsonTypeInvalid) @@ -586,12 +582,11 @@ namespace FancyZonesUnitTests Assert::AreEqual(expected.uuid.c_str(), actual->uuid.c_str()); Assert::AreEqual((int)expected.type, (int)actual->type); - Assert::IsFalse(actual->zoneCount.has_value()); } TEST_METHOD(FromJsonMissingKeys) { - ZoneSetData data{ L"uuid", ZoneSetLayoutType::Columns, 47372 }; + ZoneSetData data{ L"uuid", ZoneSetLayoutType::Columns }; const auto json = ZoneSetData::ToJson(data); auto iter = json.First(); @@ -717,22 +712,18 @@ namespace FancyZonesUnitTests Assert::IsTrue(actual.has_value()); Assert::AreEqual((int)expected.data.activeZoneSet.type, (int)actual->data.activeZoneSet.type, L"zone set type"); - Assert::IsFalse(actual->data.activeZoneSet.zoneCount.has_value(), L"zone set count"); } TEST_METHOD(FromJsonZoneGeneral) { DeviceInfoJSON expected = m_defaultDeviceInfo; expected.data.activeZoneSet.type = ZoneSetLayoutType::PriorityGrid; - expected.data.activeZoneSet.zoneCount = 10; json::JsonObject json = DeviceInfoJSON::ToJson(expected); auto actual = DeviceInfoJSON::FromJson(json); Assert::IsTrue(actual.has_value()); Assert::AreEqual((int)expected.data.activeZoneSet.type, (int)actual->data.activeZoneSet.type, L"zone set type"); - Assert::IsTrue(actual->data.activeZoneSet.zoneCount.has_value(), L"zone set count"); - Assert::AreEqual(*expected.data.activeZoneSet.zoneCount, *actual->data.activeZoneSet.zoneCount); } TEST_METHOD(FromJsonMissingKeys) @@ -981,7 +972,6 @@ namespace FancyZonesUnitTests Assert::AreEqual(expected.data.zoneCount, actual.zoneCount); Assert::AreEqual((int)expected.data.activeZoneSet.type, (int)actual.activeZoneSet.type); Assert::AreEqual(expected.data.activeZoneSet.uuid.c_str(), actual.activeZoneSet.uuid.c_str()); - Assert::IsFalse(actual.activeZoneSet.zoneCount.has_value()); } TEST_METHOD(DeviceInfoReadTempUnexsisted) diff --git a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp index 70c792f9ce33..905855284fd0 100644 --- a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp @@ -108,7 +108,7 @@ namespace FancyZonesUnitTests Assert::IsFalse(std::filesystem::exists(activeZoneSetTempPath)); const auto type = JSONHelpers::ZoneSetLayoutType::Columns; - const auto expectedZoneSet = JSONHelpers::ZoneSetData{ CreateGuidString(), type, 5 }; + const auto expectedZoneSet = JSONHelpers::ZoneSetData{ CreateGuidString(), type}; const auto data = JSONHelpers::DeviceInfoData{ expectedZoneSet, true, 16, 3 }; const auto deviceInfo = JSONHelpers::DeviceInfoJSON{ m_uniqueId.str(), data }; const auto json = JSONHelpers::DeviceInfoJSON::ToJson(deviceInfo); @@ -208,7 +208,7 @@ namespace FancyZonesUnitTests for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) { - const auto expectedZoneSet = ZoneSetData{ CreateGuidString(), static_cast(type), 5 }; + const auto expectedZoneSet = ZoneSetData{ CreateGuidString(), static_cast(type) }; const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data }; const auto json = DeviceInfoJSON::ToJson(deviceInfo); @@ -223,7 +223,6 @@ namespace FancyZonesUnitTests Assert::IsNotNull(actual->ActiveZoneSet()); const auto actualZoneSet = actual->ActiveZoneSet()->GetZones(); - Assert::AreEqual((size_t)expectedZoneSet.zoneCount.value(), actualZoneSet.size()); } } @@ -234,7 +233,7 @@ namespace FancyZonesUnitTests const auto activeZoneSetTempPath = ZoneWindowUtils::GetActiveZoneSetTmpPath(); const ZoneSetLayoutType type = ZoneSetLayoutType::Custom; - const auto expectedZoneSet = ZoneSetData{ CreateGuidString(), type, 5 }; + const auto expectedZoneSet = ZoneSetData{ CreateGuidString(), type }; const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data }; const auto json = DeviceInfoJSON::ToJson(deviceInfo); @@ -263,7 +262,7 @@ namespace FancyZonesUnitTests const ZoneSetLayoutType type = ZoneSetLayoutType::Custom; const auto customSetGuid = CreateGuidString(); - const auto expectedZoneSet = ZoneSetData{ customSetGuid, type, 5 }; + const auto expectedZoneSet = ZoneSetData{ customSetGuid, type}; const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data }; const auto json = DeviceInfoJSON::ToJson(deviceInfo); @@ -299,7 +298,7 @@ namespace FancyZonesUnitTests const ZoneSetLayoutType type = ZoneSetLayoutType::Custom; const auto customSetGuid = CreateGuidString(); - const auto expectedZoneSet = ZoneSetData{ customSetGuid, type, 5 }; + const auto expectedZoneSet = ZoneSetData{ customSetGuid, type }; const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data }; const auto json = DeviceInfoJSON::ToJson(deviceInfo); @@ -344,7 +343,7 @@ namespace FancyZonesUnitTests const ZoneSetLayoutType type = ZoneSetLayoutType::Custom; const auto customSetGuid = CreateGuidString(); - const auto expectedZoneSet = ZoneSetData{ customSetGuid, type, 5 }; + const auto expectedZoneSet = ZoneSetData{ customSetGuid, type }; const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data }; const auto json = DeviceInfoJSON::ToJson(deviceInfo); From e501f60b6ea6838fd14ee9b12f05eb927de2d2e7 Mon Sep 17 00:00:00 2001 From: Stefan S Date: Thu, 6 Feb 2020 18:00:22 +0100 Subject: [PATCH 25/32] Remove uneeded tests --- .../tests/UnitTests/JsonHelpers.Tests.cpp | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp index 2803a2a71abf..5a592420019e 100644 --- a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp +++ b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp @@ -520,14 +520,6 @@ namespace FancyZonesUnitTests }; TEST_CLASS(ZoneSetDataUnitTest){ - TEST_METHOD(ToJsonCustom) - { - json::JsonObject expected = json::JsonObject::Parse(L"{\"uuid\": \"uuid\", \"type\": \"custom\"}"); - ZoneSetData data{ L"uuid", ZoneSetLayoutType::Custom }; - const auto actual = ZoneSetData::ToJson(data); - compareJsonObjects(expected, actual); - } - TEST_METHOD(ToJsonGeneral) { json::JsonObject expected = json::JsonObject::Parse(L"{\"uuid\": \"uuid\", \"type\": \"rows\"}"); @@ -536,30 +528,6 @@ namespace FancyZonesUnitTests compareJsonObjects(expected, actual); } - TEST_METHOD(FromJsonCustom) - { - ZoneSetData expected{ L"uuid", ZoneSetLayoutType::Custom }; - - json::JsonObject json = json::JsonObject::Parse(L"{\"uuid\": \"uuid\", \"type\": \"custom\"}"); - auto actual = ZoneSetData::FromJson(json); - Assert::IsTrue(actual.has_value()); - - Assert::AreEqual(expected.uuid.c_str(), actual->uuid.c_str()); - Assert::AreEqual((int)expected.type, (int)actual->type); - } - - TEST_METHOD(FromJsonCustomZoneAdded) - { - ZoneSetData expected{ L"uuid", ZoneSetLayoutType::Custom }; - - json::JsonObject json = json::JsonObject::Parse(L"{\"uuid\": \"uuid\", \"type\": \"custom\"}"); - auto actual = ZoneSetData::FromJson(json); - Assert::IsTrue(actual.has_value()); - - Assert::AreEqual(expected.uuid.c_str(), actual->uuid.c_str()); - Assert::AreEqual((int)expected.type, (int)actual->type); - } - TEST_METHOD(FromJsonGeneral) { ZoneSetData expected{ L"uuid", ZoneSetLayoutType::Columns }; From 8906bdc7378be2727e5cc0441bff383e40da98dd Mon Sep 17 00:00:00 2001 From: Stefan S Date: Thu, 6 Feb 2020 18:02:54 +0100 Subject: [PATCH 26/32] Remove one more test --- .../fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp index 5a592420019e..4ce59eb61967 100644 --- a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp +++ b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp @@ -670,18 +670,6 @@ namespace FancyZonesUnitTests Assert::AreEqual(expected.data.spacing, actual->data.spacing); } - TEST_METHOD(FromJsonZoneCustom) - { - DeviceInfoJSON expected = m_defaultDeviceInfo; - expected.data.activeZoneSet.type = ZoneSetLayoutType::Custom; - - json::JsonObject json = DeviceInfoJSON::ToJson(expected); - auto actual = DeviceInfoJSON::FromJson(json); - Assert::IsTrue(actual.has_value()); - - Assert::AreEqual((int)expected.data.activeZoneSet.type, (int)actual->data.activeZoneSet.type, L"zone set type"); - } - TEST_METHOD(FromJsonZoneGeneral) { DeviceInfoJSON expected = m_defaultDeviceInfo; From 720fff3fabb8fabb0dacc9586fc3a5deeb12b271 Mon Sep 17 00:00:00 2001 From: Stefan S Date: Thu, 6 Feb 2020 18:09:35 +0100 Subject: [PATCH 27/32] Remove uneeded line --- src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp index 905855284fd0..d7b464a12be4 100644 --- a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp @@ -222,7 +222,6 @@ namespace FancyZonesUnitTests testZoneWindow(actual); Assert::IsNotNull(actual->ActiveZoneSet()); - const auto actualZoneSet = actual->ActiveZoneSet()->GetZones(); } } From 3d5aea55fd4a36b171dc7e828fcd710211b059d1 Mon Sep 17 00:00:00 2001 From: Stefan S Date: Thu, 6 Feb 2020 18:41:15 +0100 Subject: [PATCH 28/32] Address PR comments - Missing whitespace --- src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp index d7b464a12be4..0aaa616b3b44 100644 --- a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp @@ -108,7 +108,7 @@ namespace FancyZonesUnitTests Assert::IsFalse(std::filesystem::exists(activeZoneSetTempPath)); const auto type = JSONHelpers::ZoneSetLayoutType::Columns; - const auto expectedZoneSet = JSONHelpers::ZoneSetData{ CreateGuidString(), type}; + const auto expectedZoneSet = JSONHelpers::ZoneSetData{ CreateGuidString(), type }; const auto data = JSONHelpers::DeviceInfoData{ expectedZoneSet, true, 16, 3 }; const auto deviceInfo = JSONHelpers::DeviceInfoJSON{ m_uniqueId.str(), data }; const auto json = JSONHelpers::DeviceInfoJSON::ToJson(deviceInfo); @@ -261,7 +261,7 @@ namespace FancyZonesUnitTests const ZoneSetLayoutType type = ZoneSetLayoutType::Custom; const auto customSetGuid = CreateGuidString(); - const auto expectedZoneSet = ZoneSetData{ customSetGuid, type}; + const auto expectedZoneSet = ZoneSetData{ customSetGuid, type }; const auto data = DeviceInfoData{ expectedZoneSet, true, 16, 3 }; const auto deviceInfo = DeviceInfoJSON{ m_uniqueId.str(), data }; const auto json = DeviceInfoJSON::ToJson(deviceInfo); From 485227c4004ee0517ae6a34c830512c6f564d865 Mon Sep 17 00:00:00 2001 From: Seraphima Date: Thu, 6 Feb 2020 20:46:37 +0300 Subject: [PATCH 29/32] Update zoneset data for new virtual desktops (#21) * update active zone set with actual data --- src/modules/fancyzones/lib/JsonHelpers.cpp | 7 ++-- src/modules/fancyzones/lib/JsonHelpers.h | 2 +- src/modules/fancyzones/lib/ZoneWindow.cpp | 6 +++- .../tests/UnitTests/JsonHelpers.Tests.cpp | 33 +++++++++++++------ 4 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/modules/fancyzones/lib/JsonHelpers.cpp b/src/modules/fancyzones/lib/JsonHelpers.cpp index adb52e407e83..5602c014b0c6 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.cpp +++ b/src/modules/fancyzones/lib/JsonHelpers.cpp @@ -209,11 +209,12 @@ namespace JSONHelpers return true; } - void FancyZonesData::SetActiveZoneSet(const std::wstring& deviceId, const std::wstring& uuid) + void FancyZonesData::SetActiveZoneSet(const std::wstring& deviceId, const ZoneSetData& data) { - if (!uuid.empty() && deviceInfoMap.find(deviceId) != deviceInfoMap.end()) + auto it = deviceInfoMap.find(deviceId); + if (it != deviceInfoMap.end()) { - deviceInfoMap[deviceId].activeZoneSet.uuid = uuid; + it->second.activeZoneSet = data; } } diff --git a/src/modules/fancyzones/lib/JsonHelpers.h b/src/modules/fancyzones/lib/JsonHelpers.h index c49c84272a41..3a816d261ef3 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.h +++ b/src/modules/fancyzones/lib/JsonHelpers.h @@ -200,7 +200,7 @@ namespace JSONHelpers bool RemoveAppLastZone(HWND window, const std::wstring_view& deviceId, const std::wstring_view& zoneSetId); bool SetAppLastZone(HWND window, const std::wstring& deviceId, const std::wstring& zoneSetId, int zoneIndex); - void SetActiveZoneSet(const std::wstring& deviceId, const std::wstring& uuid); + void SetActiveZoneSet(const std::wstring& deviceId, const ZoneSetData& zoneSet); void SerializeDeviceInfoToTmpFile(const DeviceInfoJSON& deviceInfo, std::wstring_view tmpFilePath) const; diff --git a/src/modules/fancyzones/lib/ZoneWindow.cpp b/src/modules/fancyzones/lib/ZoneWindow.cpp index 4ab38ff270c4..7d586dfee262 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.cpp +++ b/src/modules/fancyzones/lib/ZoneWindow.cpp @@ -599,7 +599,11 @@ void ZoneWindow::UpdateActiveZoneSet(_In_opt_ IZoneSet* zoneSet) noexcept wil::unique_cotaskmem_string zoneSetId; if (SUCCEEDED_LOG(StringFromCLSID(m_activeZoneSet->Id(), &zoneSetId))) { - JSONHelpers::FancyZonesDataInstance().SetActiveZoneSet(m_uniqueId, zoneSetId.get()); + JSONHelpers::ZoneSetData data{ + .uuid = zoneSetId.get(), + .type = m_activeZoneSet->LayoutType() + }; + JSONHelpers::FancyZonesDataInstance().SetActiveZoneSet(m_uniqueId, data); } } } diff --git a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp index 4ce59eb61967..94c57c83635b 100644 --- a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp +++ b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp @@ -1313,7 +1313,6 @@ namespace FancyZonesUnitTests TEST_METHOD(SetActiveZoneSet) { FancyZonesData data; - const std::wstring uuid = L"uuid"; const std::wstring uniqueId = L"default_device_id"; json::JsonArray devices; @@ -1322,16 +1321,21 @@ namespace FancyZonesUnitTests json.SetNamedValue(L"devices", devices); data.ParseDeviceInfos(json); - data.SetActiveZoneSet(uniqueId, uuid); + JSONHelpers::ZoneSetData expectedZoneSetData{ + .uuid = L"uuid", + .type = ZoneSetLayoutType::Focus + }; + + data.SetActiveZoneSet(uniqueId, expectedZoneSetData); auto actual = data.GetDeviceInfoMap().find(uniqueId)->second; - Assert::AreEqual(uuid, actual.activeZoneSet.uuid); + Assert::AreEqual(expectedZoneSetData.uuid.c_str(), actual.activeZoneSet.uuid.c_str()); + Assert::IsTrue(expectedZoneSetData.type == actual.activeZoneSet.type); } TEST_METHOD(SetActiveZoneSetUuidEmpty) { FancyZonesData data; - const std::wstring uuid = L""; const std::wstring expected = L"uuid"; const std::wstring uniqueId = L"default_device_id"; @@ -1341,16 +1345,21 @@ namespace FancyZonesUnitTests json.SetNamedValue(L"devices", devices); data.ParseDeviceInfos(json); - data.SetActiveZoneSet(uniqueId, uuid); + JSONHelpers::ZoneSetData expectedZoneSetData{ + .uuid = L"", + .type = ZoneSetLayoutType::Focus + }; + + data.SetActiveZoneSet(uniqueId, expectedZoneSetData); auto actual = data.GetDeviceInfoMap().find(uniqueId)->second; - Assert::AreEqual(expected, actual.activeZoneSet.uuid); + Assert::AreEqual(expectedZoneSetData.uuid.c_str(), actual.activeZoneSet.uuid.c_str()); + Assert::IsTrue(expectedZoneSetData.type == actual.activeZoneSet.type); } TEST_METHOD(SetActiveZoneSetUniqueIdInvalid) { FancyZonesData data; - const std::wstring uuid = L"new_uuid"; const std::wstring expected = L"uuid"; const std::wstring uniqueId = L"id_not_contained_by_device_info_map"; @@ -1361,12 +1370,16 @@ namespace FancyZonesUnitTests bool parseRes = data.ParseDeviceInfos(json); Assert::IsTrue(parseRes); - data.SetActiveZoneSet(uniqueId, uuid); + JSONHelpers::ZoneSetData zoneSetData{ + .uuid = L"new_uuid", + .type = ZoneSetLayoutType::Focus + }; + + data.SetActiveZoneSet(uniqueId, zoneSetData); const auto& deviceInfoMap = data.GetDeviceInfoMap(); auto actual = deviceInfoMap.find(L"default_device_id")->second; - Assert::AreEqual(expected, actual.activeZoneSet.uuid); - + Assert::AreEqual(expected.c_str(), actual.activeZoneSet.uuid.c_str()); Assert::IsTrue(deviceInfoMap.end() == deviceInfoMap.find(uniqueId), L"new device info should not be added"); } From c991263bf36ecd86e1cc1dbb028f1c4bbdefa240 Mon Sep 17 00:00:00 2001 From: vldmr11080 z Date: Thu, 6 Feb 2020 23:11:44 +0100 Subject: [PATCH 30/32] Introduce Blank zone set (used to indicate that no layout applied yet). Move parsing completely outside of ZoneWindow. --- .../FancyZonesEditor/Models/LayoutModel.cs | 4 +-- .../FancyZonesEditor/Models/Settings.cs | 13 ++++--- src/modules/fancyzones/lib/FancyZones.cpp | 7 ++-- src/modules/fancyzones/lib/FancyZones.h | 2 +- src/modules/fancyzones/lib/JsonHelpers.cpp | 36 +++++++++++++------ src/modules/fancyzones/lib/JsonHelpers.h | 6 ++-- src/modules/fancyzones/lib/ZoneWindow.cpp | 34 +++++++----------- 7 files changed, 55 insertions(+), 47 deletions(-) diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs index ef9adeb1357f..e8b22bf185e5 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs @@ -14,13 +14,13 @@ namespace FancyZonesEditor.Models { public enum LayoutType { + Blank = -1, Focus, Columns, Rows, Grid, PriorityGrid, Custom, - Blank, } // Base LayoutModel @@ -247,7 +247,6 @@ public void Apply(System.Windows.Int32Rect[] zones) writer.WriteStartObject("active-zoneset"); writer.WriteString("uuid", "{" + Guid.ToString().ToUpper() + "}"); - bool custom = false; switch (Type) { case LayoutType.Focus: @@ -267,7 +266,6 @@ public void Apply(System.Windows.Int32Rect[] zones) break; case LayoutType.Custom: writer.WriteString("type", "custom"); - custom = true; break; } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs index 6c8c8725ab7b..409792d2d924 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs @@ -355,10 +355,13 @@ private void ParseDeviceInfoData() ActiveZoneSetUUid = jsonObject.GetProperty("active-zoneset").GetProperty("uuid").GetString(); string layoutType = jsonObject.GetProperty("active-zoneset").GetProperty("type").GetString(); - if (ActiveZoneSetUUid == "null") + if (ActiveZoneSetUUid == "null" || layoutType == "blank") { // Default selection is Focus ActiveZoneSetLayoutType = LayoutType.Focus; + _showSpacing = true; + _spacing = 16; + _zoneCount = 3; } else { @@ -383,11 +386,11 @@ private void ParseDeviceInfoData() ActiveZoneSetLayoutType = LayoutType.Custom; break; } - } - _showSpacing = jsonObject.GetProperty("editor-show-spacing").GetBoolean(); - _spacing = jsonObject.GetProperty("editor-spacing").GetInt32(); - _zoneCount = jsonObject.GetProperty("editor-zone-count").GetInt32(); + _showSpacing = jsonObject.GetProperty("editor-show-spacing").GetBoolean(); + _spacing = jsonObject.GetProperty("editor-spacing").GetInt32(); + _zoneCount = jsonObject.GetProperty("editor-zone-count").GetInt32(); + } } private void ParseCommandLineArgs() diff --git a/src/modules/fancyzones/lib/FancyZones.cpp b/src/modules/fancyzones/lib/FancyZones.cpp index d0b5750e6675..1c8443fdb5f3 100644 --- a/src/modules/fancyzones/lib/FancyZones.cpp +++ b/src/modules/fancyzones/lib/FancyZones.cpp @@ -78,15 +78,14 @@ struct FancyZones : public winrt::implementssecond; - return zoneWindowPtr->ActiveZoneSet(); + return it->second.get(); } return nullptr; } @@ -994,6 +993,8 @@ void FancyZones::OnEditorExitEvent() noexcept // Colect information about changes in zone layout after editor exited. JSONHelpers::FancyZonesDataInstance().ParseDeviceInfoFromTmpFile(ZoneWindowUtils::GetActiveZoneSetTmpPath()); JSONHelpers::FancyZonesDataInstance().ParseDeletedCustomZoneSetsFromTmpFile(ZoneWindowUtils::GetCustomZoneSetsTmpPath()); + JSONHelpers::FancyZonesDataInstance().ParseCustomZoneSetFromTmpFile(ZoneWindowUtils::GetAppliedZoneSetTmpPath()); + JSONHelpers::FancyZonesDataInstance().SaveFancyZonesData(); } winrt::com_ptr MakeFancyZones(HINSTANCE hinstance, const winrt::com_ptr& settings) noexcept diff --git a/src/modules/fancyzones/lib/FancyZones.h b/src/modules/fancyzones/lib/FancyZones.h index d9a6e039abc6..47b5b43c85d7 100644 --- a/src/modules/fancyzones/lib/FancyZones.h +++ b/src/modules/fancyzones/lib/FancyZones.h @@ -27,7 +27,7 @@ interface __declspec(uuid("{5C8D99D6-34B2-4F4A-A8E5-7483F6869775}")) IZoneWindow { IFACEMETHOD_(void, MoveWindowsOnActiveZoneSetChange)() = 0; IFACEMETHOD_(COLORREF, GetZoneHighlightColor)() = 0; - IFACEMETHOD_(IZoneSet*, GetCurrentMonitorZoneSet) (HMONITOR monitor) = 0; + IFACEMETHOD_(IZoneWindow*, GetParentZoneWindow) (HMONITOR monitor) = 0; IFACEMETHOD_(int, GetZoneHighlightOpacity)() = 0; }; diff --git a/src/modules/fancyzones/lib/JsonHelpers.cpp b/src/modules/fancyzones/lib/JsonHelpers.cpp index 5602c014b0c6..05bcab097e94 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.cpp +++ b/src/modules/fancyzones/lib/JsonHelpers.cpp @@ -61,6 +61,8 @@ namespace JSONHelpers return ZoneSetLayoutType::Grid; case c_priorityGridModelId: return ZoneSetLayoutType::PriorityGrid; + case c_blankCustomModelId: + return ZoneSetLayoutType::Blank; default: return ZoneSetLayoutType::Custom; } @@ -70,6 +72,8 @@ namespace JSONHelpers { switch (type) { + case ZoneSetLayoutType::Blank: + return L"blank"; case ZoneSetLayoutType::Focus: return L"focus"; case ZoneSetLayoutType::Columns: @@ -109,10 +113,14 @@ namespace JSONHelpers { return JSONHelpers::ZoneSetLayoutType::PriorityGrid; } - else - { //Custom + else if (typeStr == L"custom") + { return JSONHelpers::ZoneSetLayoutType::Custom; } + else + { + return JSONHelpers::ZoneSetLayoutType::Blank; + } } FancyZonesData& FancyZonesDataInstance() @@ -152,12 +160,22 @@ namespace JSONHelpers if (!deviceInfoMap.contains(deviceId)) { // Creates default entry in map when ZoneWindow is created - deviceInfoMap[deviceId] = DeviceInfoData{ ZoneSetData{ L"null", ZoneSetLayoutType::Grid }, true, 16, 3 }; + deviceInfoMap[deviceId] = DeviceInfoData{ ZoneSetData{ L"null", ZoneSetLayoutType::Blank } }; MigrateDeviceInfoFromRegistry(deviceId); } } + void FancyZonesData::CloneDeviceInfo(const std::wstring& source, const std::wstring& destination) + { + // Clone information from source device if destination device is uninitialized (Blank). + auto& destInfo = deviceInfoMap[destination]; + if (destInfo.activeZoneSet.type == ZoneSetLayoutType::Blank) + { + destInfo = deviceInfoMap[source]; + } + } + int FancyZonesData::GetAppLastZoneIndex(HWND window, const std::wstring_view& deviceId, const std::wstring_view& zoneSetId) const { auto processPath = get_process_path(window); @@ -244,14 +262,8 @@ namespace JSONHelpers } } - bool FancyZonesData::ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath, const std::wstring& deviceId) + bool FancyZonesData::ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath) { - const auto device = deviceInfoMap.find(deviceId); - if (device == deviceInfoMap.end() || device->second.activeZoneSet.type != JSONHelpers::ZoneSetLayoutType::Custom) - { - return false; - } - bool res = true; if (std::filesystem::exists(tmpFilePath)) { @@ -372,7 +384,9 @@ namespace JSONHelpers for (const auto& [deviceID, deviceData] : deviceInfoMap) { - DeviceInfosJSON.Append(DeviceInfoJSON::DeviceInfoJSON::ToJson(DeviceInfoJSON{ deviceID, deviceData })); + if (deviceData.activeZoneSet.type != ZoneSetLayoutType::Blank) { + DeviceInfosJSON.Append(DeviceInfoJSON::DeviceInfoJSON::ToJson(DeviceInfoJSON{ deviceID, deviceData })); + } } return DeviceInfosJSON; diff --git a/src/modules/fancyzones/lib/JsonHelpers.h b/src/modules/fancyzones/lib/JsonHelpers.h index 3a816d261ef3..5ec6b461fbde 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.h +++ b/src/modules/fancyzones/lib/JsonHelpers.h @@ -17,7 +17,8 @@ namespace JSONHelpers enum class ZoneSetLayoutType : int { - Focus = 0, + Blank = -1, + Focus, Columns, Rows, Grid, @@ -195,6 +196,7 @@ namespace JSONHelpers } void AddDevice(const std::wstring& deviceId); + void CloneDeviceInfo(const std::wstring& source, const std::wstring& destination); int GetAppLastZoneIndex(HWND window, const std::wstring_view& deviceId, const std::wstring_view& zoneSetId) const; bool RemoveAppLastZone(HWND window, const std::wstring_view& deviceId, const std::wstring_view& zoneSetId); @@ -205,7 +207,7 @@ namespace JSONHelpers void SerializeDeviceInfoToTmpFile(const DeviceInfoJSON& deviceInfo, std::wstring_view tmpFilePath) const; void ParseDeviceInfoFromTmpFile(std::wstring_view tmpFilePath); - bool ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath, const std::wstring& deviceId); + bool ParseCustomZoneSetFromTmpFile(std::wstring_view tmpFilePath); bool ParseDeletedCustomZoneSetsFromTmpFile(std::wstring_view tmpFilePath); bool ParseAppZoneHistory(const json::JsonObject& fancyZonesDataJSON); diff --git a/src/modules/fancyzones/lib/ZoneWindow.cpp b/src/modules/fancyzones/lib/ZoneWindow.cpp index 7d586dfee262..958952ad9a14 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.cpp +++ b/src/modules/fancyzones/lib/ZoneWindow.cpp @@ -303,7 +303,6 @@ struct ZoneWindow : public winrt::implements void OnPaint(wil::unique_hdc& hdc) noexcept; void OnKeyUp(WPARAM wparam) noexcept; winrt::com_ptr ZoneFromPoint(POINT pt) noexcept; - void ChooseDefaultActiveZoneSet() noexcept; void CycleActiveZoneSetInternal(DWORD wparam, Trace::ZoneWindow::InputMode mode) noexcept; void FlashZones() noexcept; @@ -544,18 +543,17 @@ void ZoneWindow::HideZoneWindow() noexcept void ZoneWindow::LoadSettings() noexcept { JSONHelpers::FancyZonesDataInstance().AddDevice(m_uniqueId); - JSONHelpers::FancyZonesDataInstance().ParseCustomZoneSetFromTmpFile(ZoneWindowUtils::GetAppliedZoneSetTmpPath(), m_uniqueId); - JSONHelpers::FancyZonesDataInstance().SaveFancyZonesData(); } void ZoneWindow::InitializeZoneSets(MONITORINFO const& mi) noexcept { - CalculateZoneSet(); - - if (!m_activeZoneSet) + auto parent = m_host->GetParentZoneWindow(m_monitor); + if (parent) { - ChooseDefaultActiveZoneSet(); + // Update device info with device info from parent virtual desktop (if empty). + JSONHelpers::FancyZonesDataInstance().CloneDeviceInfo(parent->UniqueId(), m_uniqueId); } + CalculateZoneSet(); } void ZoneWindow::CalculateZoneSet() noexcept @@ -565,10 +563,16 @@ void ZoneWindow::CalculateZoneSet() noexcept const auto& activeDeviceId = fancyZonesData.GetActiveDeviceId(); const auto& activeZoneSet = deviceInfoMap.at(m_uniqueId).activeZoneSet; - if (!activeDeviceId.empty() && activeDeviceId.compare(m_uniqueId) != 0 && !activeZoneSet.uuid.empty()) + if (activeDeviceId != m_uniqueId) + { + return; + } + + if (activeZoneSet.uuid.empty() || activeZoneSet.type == JSONHelpers::ZoneSetLayoutType::Blank) { return; } + GUID zoneSetId; if (SUCCEEDED_LOG(CLSIDFromString(activeZoneSet.uuid.c_str(), &zoneSetId))) { @@ -689,20 +693,6 @@ winrt::com_ptr ZoneWindow::ZoneFromPoint(POINT pt) noexcept return nullptr; } -void ZoneWindow::ChooseDefaultActiveZoneSet() noexcept -{ - // Default zone set can be empty (no fancyzones layout), or it can be layout from virtual - // desktop from which this virtual desktop is created. - if (m_host) - { - auto* zoneSet = m_host->GetCurrentMonitorZoneSet(m_monitor); - if (zoneSet) - { - UpdateActiveZoneSet(zoneSet); - } - } -} - void ZoneWindow::CycleActiveZoneSetInternal(DWORD wparam, Trace::ZoneWindow::InputMode mode) noexcept { Trace::ZoneWindow::CycleActiveZoneSet(m_activeZoneSet, mode); From 44155828e182f160eea8b8d4253661068dbff584 Mon Sep 17 00:00:00 2001 From: vldmr11080 z Date: Thu, 6 Feb 2020 23:39:43 +0100 Subject: [PATCH 31/32] Fix unit tests to match modifications in implementation --- .../fancyzones/tests/UnitTests/FancyZones.Spec.cpp | 4 ++-- .../fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp | 12 +++++++----- .../fancyzones/tests/UnitTests/ZoneSet.Spec.cpp | 4 ++-- .../fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp | 11 +++++++---- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/modules/fancyzones/tests/UnitTests/FancyZones.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/FancyZones.Spec.cpp index d3595033222f..e2f5ff1d54b9 100644 --- a/src/modules/fancyzones/tests/UnitTests/FancyZones.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/FancyZones.Spec.cpp @@ -227,13 +227,13 @@ namespace FancyZonesUnitTests TEST_METHOD(GetCurrentMonitorZoneSetEmpty) { - const auto* actual = m_zoneWindowHost->GetCurrentMonitorZoneSet(Mocks::Monitor()); + const auto* actual = m_zoneWindowHost->GetParentZoneWindow(Mocks::Monitor()); Assert::IsNull(actual); } TEST_METHOD(GetCurrentMonitorZoneSetNullMonitor) { - const auto* actual = m_zoneWindowHost->GetCurrentMonitorZoneSet(nullptr); + const auto* actual = m_zoneWindowHost->GetParentZoneWindow(nullptr); Assert::IsNull(actual); } }; diff --git a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp index 94c57c83635b..ce161a2c586d 100644 --- a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp +++ b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp @@ -55,7 +55,8 @@ namespace FancyZonesUnitTests { TEST_METHOD(ZoneSetLayoutTypeToString){ std::map expectedMap = { - std::make_pair(-1, L"TypeToString_ERROR"), + std::make_pair(-2, L"TypeToString_ERROR"), + std::make_pair(-1, L"blank"), std::make_pair(0, L"focus"), std::make_pair(1, L"columns"), std::make_pair(2, L"rows"), @@ -98,8 +99,9 @@ namespace FancyZonesUnitTests std::make_pair(ZoneSetLayoutType::Rows, 0xFFFE), std::make_pair(ZoneSetLayoutType::Grid, 0xFFFC), std::make_pair(ZoneSetLayoutType::PriorityGrid, 0xFFFB), - std::make_pair(ZoneSetLayoutType::Custom, 0xFFFA), + std::make_pair(ZoneSetLayoutType::Blank, 0xFFFA), std::make_pair(ZoneSetLayoutType::Custom, 0), + std::make_pair(ZoneSetLayoutType::Custom, 1), std::make_pair(ZoneSetLayoutType::Custom, -1), }; @@ -542,7 +544,7 @@ namespace FancyZonesUnitTests TEST_METHOD(FromJsonTypeInvalid) { - ZoneSetData expected{ L"uuid", ZoneSetLayoutType::Custom }; + ZoneSetData expected{ L"uuid", ZoneSetLayoutType::Blank }; json::JsonObject json = json::JsonObject::Parse(L"{\"uuid\": \"uuid\", \"type\": \"invalid_type\"}"); auto actual = ZoneSetData::FromJson(json); @@ -1279,7 +1281,7 @@ namespace FancyZonesUnitTests FancyZonesData data; const std::wstring path = data.GetPersistFancyZonesJSONPath() + L".test_tmp"; json::to_file(path, CustomZoneSetJSON::ToJson(expected)); - m_fzData.ParseCustomZoneSetFromTmpFile(path, deviceId); + m_fzData.ParseCustomZoneSetFromTmpFile(path); bool actualFileExists = std::filesystem::exists(path); if (actualFileExists) @@ -1305,7 +1307,7 @@ namespace FancyZonesUnitTests const std::wstring path = m_fzData.GetPersistFancyZonesJSONPath() + L".test_tmp"; const std::wstring deviceId = L"default_device_id"; - m_fzData.ParseCustomZoneSetFromTmpFile(path, deviceId); + m_fzData.ParseCustomZoneSetFromTmpFile(path); auto devices = m_fzData.GetDeviceInfoMap(); Assert::AreEqual((size_t)0, devices.size()); } diff --git a/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp index 3a2e04968511..fd00c24ff8ae 100644 --- a/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp @@ -1112,7 +1112,7 @@ namespace FancyZonesUnitTests CustomZoneSetJSON expected{ uuid.get(), CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info } }; json::to_file(m_path, CustomZoneSetJSON::ToJson(expected)); Assert::IsTrue(std::filesystem::exists(m_path)); - FancyZonesDataInstance().ParseCustomZoneSetFromTmpFile(m_path, L"default_device_id"); + FancyZonesDataInstance().ParseCustomZoneSetFromTmpFile(m_path); //test const int spacing = 10; @@ -1154,7 +1154,7 @@ namespace FancyZonesUnitTests CustomZoneSetJSON expected{ uuid.get(), CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } }; json::to_file(m_path, CustomZoneSetJSON::ToJson(expected)); Assert::IsTrue(std::filesystem::exists(m_path)); - FancyZonesDataInstance().ParseCustomZoneSetFromTmpFile(m_path, L"default_device_id"); + FancyZonesDataInstance().ParseCustomZoneSetFromTmpFile(m_path); const int spacing = 10; const int zoneCount = grid.rows() * grid.columns(); diff --git a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp index 0aaa616b3b44..8a5418bedeea 100644 --- a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp @@ -22,10 +22,10 @@ namespace FancyZonesUnitTests { return RGB(0xFF, 0xFF, 0xFF); } - IFACEMETHODIMP_(IZoneSet*) - GetCurrentMonitorZoneSet(HMONITOR monitor) noexcept + IFACEMETHODIMP_(IZoneWindow*) + GetParentZoneWindow(HMONITOR monitor) noexcept { - return m_zoneSet; + return m_zoneWindow; } IFACEMETHODIMP_(int) GetZoneHighlightOpacity() noexcept @@ -33,7 +33,7 @@ namespace FancyZonesUnitTests return 100; } - IZoneSet* m_zoneSet; + IZoneWindow* m_zoneWindow; }; TEST_CLASS(ZoneWindowUnitTests) @@ -274,6 +274,7 @@ namespace FancyZonesUnitTests auto customZoneJson = CustomZoneSetJSON::ToJson(CustomZoneSetJSON{ customSetGuid, customZoneData }); json::to_file(appliedZoneSetTempPath, customZoneJson); m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath); + m_fancyZonesData.ParseCustomZoneSetFromTmpFile(appliedZoneSetTempPath); //temp file read on initialization auto actual = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); @@ -320,6 +321,7 @@ namespace FancyZonesUnitTests m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath); m_fancyZonesData.ParseDeletedCustomZoneSetsFromTmpFile(deletedZonesTempPath); + m_fancyZonesData.ParseCustomZoneSetFromTmpFile(appliedZoneSetTempPath); //temp file read on initialization auto actual = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); @@ -366,6 +368,7 @@ namespace FancyZonesUnitTests m_fancyZonesData.ParseDeviceInfoFromTmpFile(activeZoneSetTempPath); m_fancyZonesData.ParseDeletedCustomZoneSetsFromTmpFile(deletedZonesTempPath); + m_fancyZonesData.ParseCustomZoneSetFromTmpFile(appliedZoneSetTempPath); //temp file read on initialization auto actual = MakeZoneWindow(m_hostPtr, m_hInst, m_monitor, m_uniqueId.str(), false); From 5c70bdb427801621aef4720b19a28d96dc98ab7f Mon Sep 17 00:00:00 2001 From: Stefan S Date: Fri, 7 Feb 2020 18:15:00 +0100 Subject: [PATCH 32/32] Fix applying layouts on startup (second monitor) --- src/modules/fancyzones/lib/ZoneWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/fancyzones/lib/ZoneWindow.cpp b/src/modules/fancyzones/lib/ZoneWindow.cpp index 958952ad9a14..a70ffc5dfa1f 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.cpp +++ b/src/modules/fancyzones/lib/ZoneWindow.cpp @@ -563,7 +563,7 @@ void ZoneWindow::CalculateZoneSet() noexcept const auto& activeDeviceId = fancyZonesData.GetActiveDeviceId(); const auto& activeZoneSet = deviceInfoMap.at(m_uniqueId).activeZoneSet; - if (activeDeviceId != m_uniqueId) + if (!activeDeviceId.empty() && activeDeviceId != m_uniqueId) { return; }