From 08337a9578d3dd1c758484d62a6502d6ac6d602f Mon Sep 17 00:00:00 2001 From: Samuel Bronson Date: Mon, 21 Aug 2023 04:27:11 -0400 Subject: [PATCH 01/11] Update ui-architecture.md: Fix .xaml links (#28068) * Update ui-architecture.md: Fix .xaml links * Update doc/devdocs/settingsv2/ui-architecture.md --------- Co-authored-by: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com> --- doc/devdocs/settingsv2/ui-architecture.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/devdocs/settingsv2/ui-architecture.md b/doc/devdocs/settingsv2/ui-architecture.md index 929f81386e..a44dcd8d3f 100644 --- a/doc/devdocs/settingsv2/ui-architecture.md +++ b/doc/devdocs/settingsv2/ui-architecture.md @@ -1,7 +1,7 @@ # UI Architecture - The UI code is distributed between two projects: [`PowerToys.Settings`](/src/settings-ui/Settings.UI) and [`Settings.UI`](/src/settings-ui/Settings.UI.Library). [`PowerToys.Settings`](/src/settings-ui/Settings.UI) is a Windows App Sdk .net Unpackaged application. It contains the views for base navigation and modules. Parent display window and corresponding code is present in [`MainWindow.xaml.`](/src/settings-ui/Settings.UI/MainWindow.xaml). Fig 1 provides a description of the UI controls hierarchy and each of the controls have been summarized below : -- [`ShellPage.xaml`](/src/settings-ui/Settings.UI/Views/ShellPage.xaml) is a WinUI control, consisting of a side navigation panel with an icon for each module. Clicking on a module icon loads the corresponding `setting.json` file and displays the data in the UI. + The UI code is distributed between two projects: [`PowerToys.Settings`](/src/settings-ui/Settings.UI) and [`Settings.UI`](/src/settings-ui/Settings.UI.Library). [`PowerToys.Settings`](/src/settings-ui/Settings.UI) is a Windows App Sdk .net Unpackaged application. It contains the views for base navigation and modules. Parent display window and corresponding code is present in [`MainWindow.xaml`](/src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml). Fig 1 provides a description of the UI controls hierarchy and each of the controls have been summarized below : +- [`ShellPage.xaml`](/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml) is a WinUI control, consisting of a side navigation panel with an icon for each module. Clicking on a module icon loads the corresponding `setting.json` file and displays the data in the UI. ![Settings UI architecture](/doc/images/settingsv2/ui-architecture.png) **Fig 1: UI Architecture for settingsv2** From a8b7d4d627d2f60714195907039ada28caeb05a5 Mon Sep 17 00:00:00 2001 From: Deepak Sangle Date: Mon, 21 Aug 2023 14:28:50 +0530 Subject: [PATCH 02/11] Fix/peek/file size (#28051) * [Peek] displaying file size in correct grammatical format * Update Directory.Packages.props * removed unnecessary file --- Directory.Packages.props | 2 +- .../peek/Peek.Common/Helpers/ReadableStringHelper.cs | 4 +++- src/modules/peek/Peek.UI/Strings/en-us/Resources.resw | 6 +++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 5b81560e37..53faceb474 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -73,4 +73,4 @@ - \ No newline at end of file + diff --git a/src/modules/peek/Peek.Common/Helpers/ReadableStringHelper.cs b/src/modules/peek/Peek.Common/Helpers/ReadableStringHelper.cs index 6fe8997984..bc2cba4547 100644 --- a/src/modules/peek/Peek.Common/Helpers/ReadableStringHelper.cs +++ b/src/modules/peek/Peek.Common/Helpers/ReadableStringHelper.cs @@ -17,7 +17,9 @@ namespace Peek.Common.Helpers var resourceLoader = ResourceLoaderInstance.ResourceLoader; List format = new List { - resourceLoader.GetString("ReadableString_ByteAbbreviationFormat"), // "B" + (bytes == 1) ? + resourceLoader.GetString("ReadableString_ByteAbbreviationFormat") : // "byte" + resourceLoader.GetString("ReadableString_BytesAbbreviationFormat"), // "bytes" resourceLoader.GetString("ReadableString_KiloByteAbbreviationFormat"), // "KB" resourceLoader.GetString("ReadableString_MegaByteAbbreviationFormat"), // "MB" resourceLoader.GetString("ReadableString_GigaByteAbbreviationFormat"), // "GB" diff --git a/src/modules/peek/Peek.UI/Strings/en-us/Resources.resw b/src/modules/peek/Peek.UI/Strings/en-us/Resources.resw index 0d032785b1..756be0a1b4 100644 --- a/src/modules/peek/Peek.UI/Strings/en-us/Resources.resw +++ b/src/modules/peek/Peek.UI/Strings/en-us/Resources.resw @@ -154,7 +154,7 @@ Date Modified label for the unsupported files view. {0} is the date. - {0} bytes + {0} byte Abbreviation for the size unit byte. @@ -233,4 +233,8 @@ {0} (extracted {1}) {0} is the size of the archive, {1} is the extracted size + + {0} bytes + Abbreviation for the size bytes + \ No newline at end of file From d4ae13238ee686694e5c04f3ba492b767cbedf3a Mon Sep 17 00:00:00 2001 From: League of Poro <95635582+LeagueOfPoro@users.noreply.github.com> Date: Mon, 21 Aug 2023 12:05:08 +0200 Subject: [PATCH 03/11] Added base64 decode function to the Value Generator (#27835) --- .../InputParserTests.cs | 3 +- .../Base64/Base64DecodeRequest.cs | 58 +++++++++++++++++++ .../InputParser.cs | 6 ++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Base64/Base64DecodeRequest.cs diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests/InputParserTests.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests/InputParserTests.cs index e17437556d..afc2d0232d 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests/InputParserTests.cs +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests/InputParserTests.cs @@ -29,6 +29,7 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests [DataRow("base64 abc", typeof(Base64.Base64Request))] [DataRow("base99 abc", null)] [DataRow("base64s abc", null)] + [DataRow("base64d abc=", typeof(Base64.Base64DecodeRequest))] public void ParserTest(string input, Type? expectedRequestType) { var parser = new InputParser(); @@ -77,7 +78,7 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests private static bool CommandIsKnown(string command) { - string[] hashes = new string[] { "md5", "sha1", "sha256", "sha384", "sha512", "base64" }; + string[] hashes = new string[] { "md5", "sha1", "sha256", "sha384", "sha512", "base64", "base64d" }; if (hashes.Contains(command.ToLowerInvariant())) { diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Base64/Base64DecodeRequest.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Base64/Base64DecodeRequest.cs new file mode 100644 index 0000000000..ae6623bbc8 --- /dev/null +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Base64/Base64DecodeRequest.cs @@ -0,0 +1,58 @@ +// 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.Diagnostics; +using System.Text; +using Wox.Plugin.Logger; + +namespace Community.PowerToys.Run.Plugin.ValueGenerator.Base64 +{ + public class Base64DecodeRequest : IComputeRequest + { + public byte[] Result { get; set; } + + public string Description => "Base64 Decoding"; + + public bool IsSuccessful { get; set; } + + public string ErrorMessage { get; set; } + + private string DataToDecode { get; set; } + + public Base64DecodeRequest(string dataToDecode) + { + DataToDecode = dataToDecode ?? throw new ArgumentNullException(nameof(dataToDecode)); + } + + public bool Compute() + { + IsSuccessful = true; + try + { + Result = System.Convert.FromBase64String(DataToDecode); + } + catch (Exception e) + { + Log.Exception(e.Message, e, GetType()); + ErrorMessage = e.Message; + IsSuccessful = false; + } + + return IsSuccessful; + } + + public string ResultToString() + { + if (Result != null) + { + return Encoding.UTF8.GetString(Result); + } + else + { + return string.Empty; + } + } + } +} diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/InputParser.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/InputParser.cs index 394fbf8752..1581bab297 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/InputParser.cs +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/InputParser.cs @@ -121,6 +121,12 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator string content = query.RawUserQuery.Substring(commandIndex + command.Length).Trim(); request = new Base64Request(Encoding.UTF8.GetBytes(content)); } + else if (command.ToLower(null) == "base64d") + { + int commandIndex = query.RawUserQuery.IndexOf(command, StringComparison.InvariantCultureIgnoreCase); + string content = query.RawUserQuery.Substring(commandIndex + command.Length).Trim(); + request = new Base64DecodeRequest(content); + } else { throw new FormatException($"Invalid Query: {query.RawUserQuery}"); From 35285088b8d9c747317076a540026e3ccf812227 Mon Sep 17 00:00:00 2001 From: Aaron Junker Date: Mon, 21 Aug 2023 12:15:31 +0200 Subject: [PATCH 04/11] Add /helped command (#27175) * Update resourceManagement.yml * Update .github/policies/resourceManagement.yml Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com> --------- Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com> --- .github/policies/resourceManagement.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/policies/resourceManagement.yml b/.github/policies/resourceManagement.yml index d431904c38..ae72e1266f 100644 --- a/.github/policies/resourceManagement.yml +++ b/.github/policies/resourceManagement.yml @@ -192,6 +192,29 @@ configuration: - addLabel: label: Needs-Author-Feedback description: + - if: + - payloadType: Issue_Comment + - commentContains: + pattern: '\/helped' + isRegex: True + - or: + - activitySenderHasAssociation: + association: Owner + - activitySenderHasAssociation: + association: Member + - activitySenderHasAssociation: + association: Collaborator + then: + - removeLabel: + label: Needs-Triage + - removeLabel: + label: Needs-Team-Response + - addLabel: + label: Resolution-Helped User + - addReply: + reply: This issue is now marked as resolved. If you have any follow-up questions, please don't hesitate to ask. You can find out more about PowerToys functionalities in our [end-user documentation](https://aka.ms/powertoy-docs]. + - closeIssue + description: - if: - payloadType: Issue_Comment - commentContains: From 75ed88f82358c78ef72ba598fa5f65438e4347b0 Mon Sep 17 00:00:00 2001 From: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com> Date: Mon, 21 Aug 2023 13:30:25 +0200 Subject: [PATCH 05/11] Add Base64DecodeRequest description in community.valuegenerator.md (#28074) --- .../modules/launcher/plugins/community.valuegenerator.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/devdocs/modules/launcher/plugins/community.valuegenerator.md b/doc/devdocs/modules/launcher/plugins/community.valuegenerator.md index ddecae0bee..379e7d4894 100644 --- a/doc/devdocs/modules/launcher/plugins/community.valuegenerator.md +++ b/doc/devdocs/modules/launcher/plugins/community.valuegenerator.md @@ -24,6 +24,10 @@ The Value Generator plugin is used to generate hashes for strings, to calculate - Implements IComputeRequest - `Compute()` will populate `Result` with the base64 encoding of the byte array passed in the constructor +### [`Base64DecodeRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Base64/Base64DecodeRequest.cs) +- Implements IComputeRequest +- `Compute()` will populate `Result` with the decoded byte array of the base64 string passed in the constructor + ### [`GUIDRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/GUID/GUIDRequest.cs) - Implements IComputeRequest - Uses the [`GUIDGenerator`](#guidgenerator) class to generate or compute the requested GUID From a46b80c76f25c9cb739c026402214f6d4867ad99 Mon Sep 17 00:00:00 2001 From: Andrey Nekrasov Date: Mon, 21 Aug 2023 15:42:28 +0200 Subject: [PATCH 06/11] [PowerRename] Enable "Enumerate items" by default (#28075) --- .../powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml index cecbeea866..5b2d4d1832 100644 --- a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml +++ b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml @@ -503,6 +503,7 @@ From 7c7f6cabf7844b37b098ce03a5060efa0c1fc324 Mon Sep 17 00:00:00 2001 From: Davide Giacometti Date: Mon, 21 Aug 2023 15:51:29 +0200 Subject: [PATCH 07/11] [Peek][Monaco Preview] Open Monaco URI in default browser (#27774) * open Monaco URI in default browser * added dialog when URI is opened --- .../Controls/BrowserControl.xaml | 14 ++++-- .../Controls/BrowserControl.xaml.cs | 43 +++++++++++++++++-- .../peek/Peek.UI/Strings/en-us/Resources.resw | 16 +++++++ .../MonacoPreviewHandlerControl.cs | 11 +++++ 4 files changed, 77 insertions(+), 7 deletions(-) diff --git a/src/modules/peek/Peek.FilePreviewer/Controls/BrowserControl.xaml b/src/modules/peek/Peek.FilePreviewer/Controls/BrowserControl.xaml index f462aeaef6..1ddfee1c6f 100644 --- a/src/modules/peek/Peek.FilePreviewer/Controls/BrowserControl.xaml +++ b/src/modules/peek/Peek.FilePreviewer/Controls/BrowserControl.xaml @@ -12,9 +12,15 @@ mc:Ignorable="d"> - + + diff --git a/src/modules/peek/Peek.FilePreviewer/Controls/BrowserControl.xaml.cs b/src/modules/peek/Peek.FilePreviewer/Controls/BrowserControl.xaml.cs index 5030f2f69f..39f94af55e 100644 --- a/src/modules/peek/Peek.FilePreviewer/Controls/BrowserControl.xaml.cs +++ b/src/modules/peek/Peek.FilePreviewer/Controls/BrowserControl.xaml.cs @@ -3,11 +3,13 @@ // See the LICENSE file in the project root for more information. using System; +using System.Threading.Tasks; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.Web.WebView2.Core; using Peek.Common.Constants; using Peek.Common.Helpers; +using Windows.ApplicationModel.DataTransfer; using Windows.System; using Windows.UI; @@ -96,6 +98,7 @@ namespace Peek.FilePreviewer.Controls private void SourcePropertyChanged() { + OpenUriDialog.Hide(); Navigate(); } @@ -135,6 +138,7 @@ namespace Peek.FilePreviewer.Controls } PreviewBrowser.CoreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded; + PreviewBrowser.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested; } catch (Exception ex) { @@ -149,6 +153,16 @@ namespace Peek.FilePreviewer.Controls DOMContentLoaded?.Invoke(sender, args); } + private async void CoreWebView2_NewWindowRequested(CoreWebView2 sender, CoreWebView2NewWindowRequestedEventArgs args) + { + // Monaco opens URI in a new window. We open the URI in the default web browser. + if (args.Uri != null && args.IsUserInitiated) + { + args.Handled = true; + await ShowOpenUriDialogAsync(new Uri(args.Uri)); + } + } + private async void PreviewBrowser_NavigationStarting(WebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs args) { if (_navigatedUri == null) @@ -157,10 +171,11 @@ namespace Peek.FilePreviewer.Controls } // In case user starts or tries to navigate from within the HTML file we launch default web browser for navigation. - if (args.Uri != null && args.Uri != _navigatedUri?.ToString() && args.IsUserInitiated) + // TODO: && args.IsUserInitiated - always false for PDF files, revert the workaround when fixed in WebView2: https://github.com/microsoft/PowerToys/issues/27403 + if (args.Uri != null && args.Uri != _navigatedUri?.ToString()) { args.Cancel = true; - await Launcher.LaunchUriAsync(new Uri(args.Uri)); + await ShowOpenUriDialogAsync(new Uri(args.Uri)); } } @@ -171,7 +186,29 @@ namespace Peek.FilePreviewer.Controls _navigatedUri = Source; } - NavigationCompleted?.Invoke(sender, args); + // Don't raise NavigationCompleted event if NavigationStarting has been cancelled + if (args.WebErrorStatus != CoreWebView2WebErrorStatus.OperationCanceled) + { + NavigationCompleted?.Invoke(sender, args); + } + } + + private async Task ShowOpenUriDialogAsync(Uri uri) + { + OpenUriDialog.Content = uri.ToString(); + var result = await OpenUriDialog.ShowAsync(); + + if (result == ContentDialogResult.Primary) + { + await Launcher.LaunchUriAsync(uri); + } + } + + private void OpenUriDialog_SecondaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) + { + var dataPackage = new DataPackage(); + dataPackage.SetText(sender.Content.ToString()); + Clipboard.SetContent(dataPackage); } } } diff --git a/src/modules/peek/Peek.UI/Strings/en-us/Resources.resw b/src/modules/peek/Peek.UI/Strings/en-us/Resources.resw index 756be0a1b4..d8df22b84a 100644 --- a/src/modules/peek/Peek.UI/Strings/en-us/Resources.resw +++ b/src/modules/peek/Peek.UI/Strings/en-us/Resources.resw @@ -237,4 +237,20 @@ {0} bytes Abbreviation for the size bytes + + Cancel + Dialog showed when an URI is clicked. Button to close the dialog. + + + Open + Dialog showed when an URI is clicked. Button to open the URI. + + + Copy + Dialog showed when an URI is clicked. Button to copy the URI. + + + Do you want Peek to open the external application? + Title of the dialog showed when an URI is clicked,"Peek" is the name of the utility. + \ No newline at end of file diff --git a/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandlerControl.cs b/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandlerControl.cs index f137d67883..ca16bcc835 100644 --- a/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandlerControl.cs +++ b/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandlerControl.cs @@ -157,6 +157,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco _webView.NavigationCompleted += WebView2Init; _webView.Height = this.Height; _webView.Width = this.Width; + _webView.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested; Controls.Add(_webView); _webView.SendToBack(); _loadingBar.Value = 100; @@ -218,6 +219,16 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco } } + private async void CoreWebView2_NewWindowRequested(object sender, CoreWebView2NewWindowRequestedEventArgs e) + { + // Monaco opens URI in a new window. We open the URI in the default web browser. + if (e.Uri != null && e.IsUserInitiated) + { + e.Handled = true; + await Launcher.LaunchUriAsync(new Uri(e.Uri)); + } + } + /// /// This event sets the height and width of the webview to the size of the form /// From 6acae53e2ccea1efbabcce5e7c3ec1e5acf5c1f4 Mon Sep 17 00:00:00 2001 From: Seraphima Zykova Date: Tue, 22 Aug 2023 15:57:45 +0300 Subject: [PATCH 08/11] [FancyZones] Improve code quality (part 6) (#28034) --- .../fancyzones/FancyZonesLib/FancyZones.cpp | 368 +---- .../FancyZonesData/AppZoneHistory.cpp | 131 +- .../FancyZonesData/AppZoneHistory.h | 6 +- .../FancyZonesLib/FancyZonesDataTypes.h | 6 +- .../FancyZonesLib/FancyZonesLib.vcxproj | 6 +- .../FancyZonesLib.vcxproj.filters | 10 +- .../FancyZonesWindowProperties.cpp | 6 +- .../FancyZonesWindowProperties.h | 2 +- .../fancyzones/FancyZonesLib/JsonHelpers.cpp | 7 +- .../FancyZonesLib/LayoutAssignedWindows.cpp | 34 +- .../FancyZonesLib/LayoutAssignedWindows.h | 13 +- .../fancyzones/FancyZonesLib/Settings.h | 7 + .../FancyZonesLib/WindowKeyboardSnap.cpp | 489 ++++++ .../FancyZonesLib/WindowKeyboardSnap.h | 56 + .../FancyZonesLib/WindowMouseSnap.cpp | 244 +++ .../{WindowDrag.h => WindowMouseSnap.h} | 8 +- .../fancyzones/FancyZonesLib/WindowUtils.cpp | 25 +- .../fancyzones/FancyZonesLib/WorkArea.cpp | 379 +---- .../fancyzones/FancyZonesLib/WorkArea.h | 27 +- .../fancyzones/FancyZonesLib/trace.cpp | 16 +- src/modules/fancyzones/FancyZonesLib/trace.h | 10 +- src/modules/fancyzones/FancyZonesLib/util.cpp | 9 + src/modules/fancyzones/FancyZonesLib/util.h | 13 +- .../UnitTests/AppZoneHistoryTests.Spec.cpp | 64 +- .../FancyZonesTests/UnitTests/Layout.Spec.cpp | 14 +- .../UnitTests/UnitTests.vcxproj | 1 + .../UnitTests/UnitTests.vcxproj.filters | 3 + .../UnitTests/WindowKeyboardSnap.Spec.cpp | 1452 +++++++++++++++++ .../UnitTests/WorkArea.Spec.cpp | 502 +----- 29 files changed, 2669 insertions(+), 1239 deletions(-) create mode 100644 src/modules/fancyzones/FancyZonesLib/WindowKeyboardSnap.cpp create mode 100644 src/modules/fancyzones/FancyZonesLib/WindowKeyboardSnap.h create mode 100644 src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.cpp rename src/modules/fancyzones/FancyZonesLib/{WindowDrag.h => WindowMouseSnap.h} (82%) create mode 100644 src/modules/fancyzones/FancyZonesTests/UnitTests/WindowKeyboardSnap.Spec.cpp diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp index caa77429de..d62c1d0cc6 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp @@ -26,7 +26,8 @@ #include #include #include -#include +#include +#include #include enum class DisplayChangeType @@ -142,10 +143,6 @@ protected: private: void UpdateWorkAreas(bool updateWindowPositions) noexcept; void CycleWindows(bool reverse) noexcept; - bool OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexcept; - bool OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept; - bool OnSnapHotkey(DWORD vkCode) noexcept; - bool ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle, WorkArea* const workArea) noexcept; void SyncVirtualDesktops() noexcept; @@ -153,13 +150,11 @@ private: bool MoveToAppLastZone(HWND window, HMONITOR monitor) noexcept; - void UpdateActiveLayouts() noexcept; + void RefreshLayouts() noexcept; bool ShouldProcessSnapHotkey(DWORD vkCode) noexcept; void ApplyQuickLayout(int key) noexcept; void FlashZones() noexcept; - std::vector> GetRawMonitorData() noexcept; - std::vector GetMonitorsSorted() noexcept; HMONITOR WorkAreaKeyFromWindow(HWND window) noexcept; virtual void SettingsUpdate(SettingId type) override; @@ -167,7 +162,8 @@ private: const HINSTANCE m_hinstance{}; HWND m_window{}; - std::unique_ptr m_windowDrag{}; + std::unique_ptr m_windowMouseSnapper{}; + WindowKeyboardSnap m_windowKeyboardSnapper{}; MonitorWorkAreaMap m_workAreaHandler; DraggingState m_draggingState; @@ -288,8 +284,8 @@ FancyZones::VirtualDesktopChanged() noexcept void FancyZones::MoveSizeStart(HWND window, HMONITOR monitor) { - m_windowDrag = WindowDrag::Create(window, m_workAreaHandler.GetAllWorkAreas()); - if (m_windowDrag) + m_windowMouseSnapper = WindowMouseSnap::Create(window, m_workAreaHandler.GetAllWorkAreas()); + if (m_windowMouseSnapper) { if (FancyZonesSettings::settings().spanZonesAcrossMonitors) { @@ -298,13 +294,13 @@ void FancyZones::MoveSizeStart(HWND window, HMONITOR monitor) m_draggingState.Enable(); m_draggingState.UpdateDraggingState(); - m_windowDrag->MoveSizeStart(monitor, m_draggingState.IsDragging()); + m_windowMouseSnapper->MoveSizeStart(monitor, m_draggingState.IsDragging()); } } void FancyZones::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen) { - if (m_windowDrag) + if (m_windowMouseSnapper) { if (FancyZonesSettings::settings().spanZonesAcrossMonitors) { @@ -312,17 +308,17 @@ void FancyZones::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen) } m_draggingState.UpdateDraggingState(); - m_windowDrag->MoveSizeUpdate(monitor, ptScreen, m_draggingState.IsDragging(), m_draggingState.IsSelectManyZonesState()); + m_windowMouseSnapper->MoveSizeUpdate(monitor, ptScreen, m_draggingState.IsDragging(), m_draggingState.IsSelectManyZonesState()); } } void FancyZones::MoveSizeEnd() { - if (m_windowDrag) + if (m_windowMouseSnapper) { - m_windowDrag->MoveSizeEnd(); + m_windowMouseSnapper->MoveSizeEnd(); m_draggingState.Disable(); - m_windowDrag = nullptr; + m_windowMouseSnapper = nullptr; } } @@ -339,7 +335,7 @@ bool FancyZones::MoveToAppLastZone(HWND window, HMONITOR monitor) noexcept workArea = workAreas.at(monitor).get(); if (workArea) { - indexes = workArea->GetWindowZoneIndexes(window); + indexes = AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId()); } } else @@ -353,7 +349,7 @@ bool FancyZones::MoveToAppLastZone(HWND window, HMONITOR monitor) noexcept { if (secondaryWorkArea) { - indexes = secondaryWorkArea->GetWindowZoneIndexes(window); + indexes = AppZoneHistory::instance().GetAppLastZoneIndexSet(window, secondaryWorkArea->UniqueId(), secondaryWorkArea->GetLayoutId()); workArea = secondaryWorkArea.get(); if (!indexes.empty()) { @@ -365,8 +361,8 @@ bool FancyZones::MoveToAppLastZone(HWND window, HMONITOR monitor) noexcept if (!indexes.empty() && workArea) { - Trace::FancyZones::SnapNewWindowIntoZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); - workArea->MoveWindowIntoZoneByIndexSet(window, indexes); + Trace::FancyZones::SnapNewWindowIntoZone(workArea->GetLayout().get(), workArea->GetLayoutWindows()); + workArea->Snap(window, indexes); return true; } @@ -604,7 +600,40 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa if (message == WM_PRIV_SNAP_HOTKEY) { - OnSnapHotkey(static_cast(lparam)); + // We already checked in ShouldProcessSnapHotkey whether the foreground window is a candidate for zoning + auto foregroundWindow = GetForegroundWindow(); + + HMONITOR monitor{ nullptr }; + if (!FancyZonesSettings::settings().spanZonesAcrossMonitors) + { + monitor = MonitorFromWindow(foregroundWindow, MONITOR_DEFAULTTONULL); + } + + if (FancyZonesSettings::settings().moveWindowsBasedOnPosition) + { + auto monitors = FancyZonesUtils::GetAllMonitorRects<&MONITORINFOEX::rcWork>(); + RECT windowRect; + if (GetWindowRect(foregroundWindow, &windowRect)) + { + // Check whether Alt is used in the shortcut key combination + if (GetAsyncKeyState(VK_MENU) & 0x8000) + { + m_windowKeyboardSnapper.Extend(foregroundWindow, windowRect, monitor, static_cast(lparam), m_workAreaHandler.GetAllWorkAreas()); + } + else + { + m_windowKeyboardSnapper.Snap(foregroundWindow, windowRect, monitor, static_cast(lparam), m_workAreaHandler.GetAllWorkAreas(), monitors); + } + } + else + { + Logger::error("Error snapping window by keyboard shortcut: failed to get window rect"); + } + } + else + { + m_windowKeyboardSnapper.Snap(foregroundWindow, monitor, static_cast(lparam), m_workAreaHandler.GetAllWorkAreas(), FancyZonesUtils::GetMonitorsOrdered()); + } } else if (message == WM_PRIV_INIT) { @@ -661,7 +690,7 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa else if (message == WM_PRIV_APPLIED_LAYOUTS_FILE_UPDATE) { AppliedLayouts::instance().LoadData(); - UpdateActiveLayouts(); + RefreshLayouts(); } else if (message == WM_PRIV_DEFAULT_LAYOUTS_FILE_UPDATE) { @@ -800,7 +829,7 @@ void FancyZones::UpdateWorkAreas(bool updateWindowPositions) noexcept { for (const auto& [window, zones] : windowsToSnap) { - workArea->SnapWindow(window, zones, false); + workArea->Snap(window, zones, false); } } } @@ -813,9 +842,9 @@ void FancyZones::UpdateWorkAreas(bool updateWindowPositions) noexcept const auto zones = iter->second; const auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL); const auto workAreaForMonitor = m_workAreaHandler.GetWorkArea(monitor); - if (workAreaForMonitor && workAreaForMonitor->GetWindowZoneIndexes(window) == zones) + if (workAreaForMonitor && AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaForMonitor->UniqueId(), workAreaForMonitor->GetLayoutId()) == zones) { - workAreaForMonitor->SnapWindow(window, zones, false); + workAreaForMonitor->Snap(window, zones, false); iter = windowsToSnap.erase(iter); } else @@ -829,10 +858,10 @@ void FancyZones::UpdateWorkAreas(bool updateWindowPositions) noexcept { for (const auto& [_, workArea] : m_workAreaHandler.GetAllWorkAreas()) { - const auto savedIndexes = workArea->GetWindowZoneIndexes(window); + const auto savedIndexes = AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId()); if (savedIndexes == zones) { - workArea->SnapWindow(window, zones, false); + workArea->Snap(window, zones, false); } } } @@ -862,257 +891,6 @@ void FancyZones::CycleWindows(bool reverse) noexcept } } -bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexcept -{ - HMONITOR current = WorkAreaKeyFromWindow(window); - - std::vector monitorInfo = GetMonitorsSorted(); - if (current && monitorInfo.size() > 1 && FancyZonesSettings::settings().moveWindowAcrossMonitors) - { - // Multi monitor environment. - auto currMonitorInfo = std::find(std::begin(monitorInfo), std::end(monitorInfo), current); - do - { - auto workArea = m_workAreaHandler.GetWorkArea(*currMonitorInfo); - if (workArea && workArea->MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */)) - { - // unassign from previous work area - for (auto& [_, prevWorkArea] : m_workAreaHandler.GetAllWorkAreas()) - { - if (prevWorkArea && workArea != prevWorkArea.get()) - { - prevWorkArea->UnsnapWindow(window); - } - } - - Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); - return true; - } - // We iterated through all zones in current monitor zone layout, move on to next one (or previous depending on direction). - if (vkCode == VK_RIGHT) - { - currMonitorInfo = std::next(currMonitorInfo); - if (currMonitorInfo == std::end(monitorInfo)) - { - currMonitorInfo = std::begin(monitorInfo); - } - } - else if (vkCode == VK_LEFT) - { - if (currMonitorInfo == std::begin(monitorInfo)) - { - currMonitorInfo = std::end(monitorInfo); - } - currMonitorInfo = std::prev(currMonitorInfo); - } - } while (*currMonitorInfo != current); - } - else - { - auto workArea = m_workAreaHandler.GetWorkArea(current); - // Single monitor environment, or combined multi-monitor environment. - if (FancyZonesSettings::settings().restoreSize) - { - bool moved = workArea && workArea->MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */); - if (!moved) - { - FancyZonesWindowUtils::RestoreWindowOrigin(window); - FancyZonesWindowUtils::RestoreWindowSize(window); - } - else if (workArea) - { - Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); - } - return moved; - } - else - { - bool moved = workArea && workArea->MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, true /* cycle through zones */); - - if (moved) - { - Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); - } - - return moved; - } - } - - return false; -} - -bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept -{ - HMONITOR current = WorkAreaKeyFromWindow(window); - - auto allMonitors = FancyZonesUtils::GetAllMonitorRects<&MONITORINFOEX::rcWork>(); - - if (current && allMonitors.size() > 1 && FancyZonesSettings::settings().moveWindowAcrossMonitors) - { - // Multi monitor environment. - // First, try to stay on the same monitor - bool success = ProcessDirectedSnapHotkey(window, vkCode, false, m_workAreaHandler.GetWorkArea(current)); - if (success) - { - return true; - } - - // If that didn't work, extract zones from all other monitors and target one of them - std::vector zoneRects; - std::vector> zoneRectsInfo; - RECT currentMonitorRect{ .top = 0, .bottom = -1 }; - - for (const auto& [monitor, monitorRect] : allMonitors) - { - if (monitor == current) - { - currentMonitorRect = monitorRect; - } - else - { - auto workArea = m_workAreaHandler.GetWorkArea(monitor); - if (workArea) - { - const auto& layout = workArea->GetLayout(); - if (layout) - { - const auto& zones = layout->Zones(); - for (const auto& [zoneId, zone] : zones) - { - RECT zoneRect = zone.GetZoneRect(); - - zoneRect.left += monitorRect.left; - zoneRect.right += monitorRect.left; - zoneRect.top += monitorRect.top; - zoneRect.bottom += monitorRect.top; - - zoneRects.emplace_back(zoneRect); - zoneRectsInfo.emplace_back(zoneId, workArea); - } - } - } - } - } - - // Ensure we can get the windowRect, if not, just quit - RECT windowRect; - if (!GetWindowRect(window, &windowRect)) - { - return false; - } - - auto chosenIdx = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects); - - if (chosenIdx < zoneRects.size()) - { - // Moving to another monitor succeeded - const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx]; - if (workArea) - { - workArea->MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }); - Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); - } - - return true; - } - - // We reached the end of all monitors. - // Try again, cycling on all monitors. - // First, add zones from the origin monitor to zoneRects - // Sanity check: the current monitor is valid - if (currentMonitorRect.top <= currentMonitorRect.bottom) - { - auto workArea = m_workAreaHandler.GetWorkArea(current); - if (workArea) - { - const auto& layout = workArea->GetLayout(); - if (layout) - { - const auto& zones = layout->Zones(); - for (const auto& [zoneId, zone] : zones) - { - RECT zoneRect = zone.GetZoneRect(); - - zoneRect.left += currentMonitorRect.left; - zoneRect.right += currentMonitorRect.left; - zoneRect.top += currentMonitorRect.top; - zoneRect.bottom += currentMonitorRect.top; - - zoneRects.emplace_back(zoneRect); - zoneRectsInfo.emplace_back(zoneId, workArea); - } - } - } - } - else - { - return false; - } - - RECT combinedRect = FancyZonesUtils::GetAllMonitorsCombinedRect<&MONITORINFOEX::rcWork>(); - windowRect = FancyZonesUtils::PrepareRectForCycling(windowRect, combinedRect, vkCode); - chosenIdx = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects); - if (chosenIdx < zoneRects.size()) - { - // Moving to another monitor succeeded - const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx]; - - if (workArea) - { - workArea->MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }); - Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); - } - - return true; - } - else - { - // Giving up - return false; - } - } - else - { - // Single monitor environment, or combined multi-monitor environment. - return ProcessDirectedSnapHotkey(window, vkCode, true, m_workAreaHandler.GetWorkArea(current)); - } -} - -bool FancyZones::OnSnapHotkey(DWORD vkCode) noexcept -{ - // We already checked in ShouldProcessSnapHotkey whether the foreground window is a candidate for zoning - auto window = GetForegroundWindow(); - if (FancyZonesSettings::settings().moveWindowsBasedOnPosition) - { - return OnSnapHotkeyBasedOnPosition(window, vkCode); - } - - return (vkCode == VK_LEFT || vkCode == VK_RIGHT) && OnSnapHotkeyBasedOnZoneNumber(window, vkCode); -} - -bool FancyZones::ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle, WorkArea* const workArea) noexcept -{ - // Check whether Alt is used in the shortcut key combination - if (GetAsyncKeyState(VK_MENU) & 0x8000) - { - bool result = workArea && workArea->ExtendWindowByDirectionAndPosition(window, vkCode); - if (result) - { - Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); - } - return result; - } - else - { - bool result = workArea && workArea->MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle); - if (result) - { - Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get()); - } - return result; - } -} - void FancyZones::SyncVirtualDesktops() noexcept { auto guids = VirtualDesktop::instance().GetVirtualDesktopIdsFromRegistry(); @@ -1186,13 +964,13 @@ void FancyZones::SettingsUpdate(SettingId id) } } -void FancyZones::UpdateActiveLayouts() noexcept +void FancyZones::RefreshLayouts() noexcept { for (const auto& [_, workArea] : m_workAreaHandler.GetAllWorkAreas()) { if (workArea) { - workArea->UpdateActiveZoneSet(); + workArea->InitLayout(); if (FancyZonesSettings::settings().zoneSetChange_moveWindows) { @@ -1265,7 +1043,7 @@ void FancyZones::ApplyQuickLayout(int key) noexcept { AppliedLayouts::instance().ApplyLayout(workArea->UniqueId(), layout.value()); AppliedLayouts::instance().SaveData(); - UpdateActiveLayouts(); + RefreshLayouts(); FlashZones(); } } @@ -1284,32 +1062,6 @@ void FancyZones::FlashZones() noexcept } } -std::vector FancyZones::GetMonitorsSorted() noexcept -{ - auto monitorInfo = GetRawMonitorData(); - FancyZonesUtils::OrderMonitors(monitorInfo); - std::vector output; - std::transform(std::begin(monitorInfo), std::end(monitorInfo), std::back_inserter(output), [](const auto& info) { return info.first; }); - return output; -} - -std::vector> FancyZones::GetRawMonitorData() noexcept -{ - std::vector> monitorInfo; - const auto& activeWorkAreaMap = m_workAreaHandler.GetAllWorkAreas(); - for (const auto& [monitor, workArea] : activeWorkAreaMap) - { - if (workArea && workArea->GetLayout() != nullptr) - { - MONITORINFOEX mi; - mi.cbSize = sizeof(mi); - GetMonitorInfo(monitor, &mi); - monitorInfo.push_back({ monitor, mi.rcMonitor }); - } - } - return monitorInfo; -} - HMONITOR FancyZones::WorkAreaKeyFromWindow(HWND window) noexcept { if (FancyZonesSettings::settings().spanZonesAcrossMonitors) diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.cpp index 6eaf81d90d..886dec5a84 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.cpp @@ -103,13 +103,14 @@ namespace JsonUtils } data.workAreaId = deviceIdOpt.value(); - data.zoneSetUuid = json.GetNamedString(NonLocalizable::AppZoneHistoryIds::LayoutIdID); - - if (!FancyZonesUtils::IsValidGuid(data.zoneSetUuid)) + std::wstring layoutIdStr = json.GetNamedString(NonLocalizable::AppZoneHistoryIds::LayoutIdID).c_str(); + auto layoutIdOpt = FancyZonesUtils::GuidFromString(layoutIdStr); + if (!layoutIdOpt.has_value()) { return std::nullopt; } + data.layoutId = layoutIdOpt.value(); return data; } @@ -187,7 +188,11 @@ namespace JsonUtils desktopData.SetNamedValue(NonLocalizable::AppZoneHistoryIds::LayoutIndexesID, jsonIndexSet); desktopData.SetNamedValue(NonLocalizable::AppZoneHistoryIds::DeviceID, device); - desktopData.SetNamedValue(NonLocalizable::AppZoneHistoryIds::LayoutIdID, json::value(data.zoneSetUuid)); + auto layoutIdStr = FancyZonesUtils::GuidToString(data.layoutId); + if (layoutIdStr) + { + desktopData.SetNamedValue(NonLocalizable::AppZoneHistoryIds::LayoutIdID, json::value(layoutIdStr.value())); + } appHistoryArray.Append(desktopData); } @@ -334,7 +339,7 @@ void AppZoneHistory::AdjustWorkAreaIds(const std::vector processIdToHandleMap{}; processIdToHandleMap[processId] = window; FancyZonesDataTypes::AppZoneHistoryData data{ .processIdToHandleMap = processIdToHandleMap, - .zoneSetUuid = zoneSetId, + .layoutId = layoutId, .workAreaId = workAreaId, .zoneIndexSet = zoneIndexSet }; @@ -392,56 +401,66 @@ bool AppZoneHistory::SetAppLastZones(HWND window, const FancyZonesDataTypes::Wor return true; } -bool AppZoneHistory::RemoveAppLastZone(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring_view& zoneSetId) +bool AppZoneHistory::RemoveAppLastZone(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId) { - Logger::info(L"Remove app zone history, device: {}, layout: {}", workAreaId.toString(), zoneSetId); - auto processPath = get_process_path_waiting_uwp(window); - if (!processPath.empty()) + if (processPath.empty()) { - auto history = m_history.find(processPath); - if (history != std::end(m_history)) - { - auto& perDesktopData = history->second; - for (auto data = std::begin(perDesktopData); data != std::end(perDesktopData);) - { - if (data->workAreaId == workAreaId && data->zoneSetUuid == zoneSetId) - { - if (!IsAnotherWindowOfApplicationInstanceZoned(window, workAreaId)) - { - DWORD processId = 0; - GetWindowThreadProcessId(window, &processId); - - data->processIdToHandleMap.erase(processId); - } - - // if there is another instance of same application placed in the same zone don't erase history - auto windowZoneStamps = FancyZonesWindowProperties::RetrieveZoneIndexProperty(window); - for (auto placedWindow : data->processIdToHandleMap) - { - auto placedWindowZoneStamps = FancyZonesWindowProperties::RetrieveZoneIndexProperty(placedWindow.second); - if (IsWindow(placedWindow.second) && (windowZoneStamps == placedWindowZoneStamps)) - { - return false; - } - } - - data = perDesktopData.erase(data); - if (perDesktopData.empty()) - { - m_history.erase(processPath); - } - SaveData(); - return true; - } - else - { - ++data; - } - } - } + return false; } + auto history = m_history.find(processPath); + if (history == std::end(m_history)) + { + return false; + } + + auto layoutIdStrOpt = FancyZonesUtils::GuidToString(layoutId); + if (!layoutIdStrOpt) + { + Logger::error("Invalid layout id"); + return false; + } + + Logger::info(L"Remove app zone history, device: {}, layout: {}", workAreaId.toString(), layoutIdStrOpt.value()); + + auto& perDesktopData = history->second; + for (auto data = std::begin(perDesktopData); data != std::end(perDesktopData);) + { + if (data->workAreaId == workAreaId && data->layoutId == layoutId) + { + if (!IsAnotherWindowOfApplicationInstanceZoned(window, workAreaId)) + { + DWORD processId = 0; + GetWindowThreadProcessId(window, &processId); + + data->processIdToHandleMap.erase(processId); + } + + // if there is another instance of same application placed in the same zone don't erase history + auto windowZoneStamps = FancyZonesWindowProperties::RetrieveZoneIndexProperty(window); + for (auto placedWindow : data->processIdToHandleMap) + { + auto placedWindowZoneStamps = FancyZonesWindowProperties::RetrieveZoneIndexProperty(placedWindow.second); + if (IsWindow(placedWindow.second) && (windowZoneStamps == placedWindowZoneStamps)) + { + return false; + } + } + + data = perDesktopData.erase(data); + if (perDesktopData.empty()) + { + m_history.erase(processPath); + } + SaveData(); + return true; + } + else + { + ++data; + } + } return false; } @@ -532,7 +551,7 @@ bool AppZoneHistory::IsAnotherWindowOfApplicationInstanceZoned(HWND window, cons return false; } -ZoneIndexSet AppZoneHistory::GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring& zoneSetId) const +ZoneIndexSet AppZoneHistory::GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId) const { auto processPath = get_process_path_waiting_uwp(window); if (processPath.empty()) @@ -559,7 +578,7 @@ ZoneIndexSet AppZoneHistory::GetAppLastZoneIndexSet(HWND window, const FancyZone const auto& perDesktopData = history->second; for (const auto& data : perDesktopData) { - if (data.zoneSetUuid == zoneSetId && data.workAreaId == workAreaId) + if (data.layoutId == layoutId && data.workAreaId == workAreaId) { if (data.workAreaId.virtualDesktopId == workAreaId.virtualDesktopId || data.workAreaId.virtualDesktopId == GUID_NULL) { diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.h index 8c69cc5b5a..18ec84ac93 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.h +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.h @@ -45,8 +45,8 @@ public: void SaveData(); void AdjustWorkAreaIds(const std::vector& ids); - bool SetAppLastZones(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring& zoneSetId, const ZoneIndexSet& zoneIndexSet); - bool RemoveAppLastZone(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring_view& zoneSetId); + bool SetAppLastZones(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId, const ZoneIndexSet& zoneIndexSet); + bool RemoveAppLastZone(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId); void RemoveApp(const std::wstring& appPath); @@ -54,7 +54,7 @@ public: std::optional GetZoneHistory(const std::wstring& appPath, const FancyZonesDataTypes::WorkAreaId& workAreaId) const noexcept; bool IsAnotherWindowOfApplicationInstanceZoned(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId) const noexcept; - ZoneIndexSet GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring& zoneSetId) const; + ZoneIndexSet GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId) const; void SyncVirtualDesktops(); void RemoveDeletedVirtualDesktops(const std::vector& activeDesktops); diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.h index 1770f0f646..6f61b3e125 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.h +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.h @@ -144,9 +144,9 @@ namespace FancyZonesDataTypes { std::unordered_map processIdToHandleMap; // Maps process id(DWORD) of application to zoned window handle(HWND) - std::wstring zoneSetUuid; - WorkAreaId workAreaId; - ZoneIndexSet zoneIndexSet; + GUID layoutId = {}; + WorkAreaId workAreaId = {}; + ZoneIndexSet zoneIndexSet = {}; }; struct DeviceInfoData diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj index ad923bfcb9..bd79059fb7 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj @@ -71,7 +71,8 @@ - + + @@ -123,7 +124,8 @@ - + + diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters index f34893d20d..e8012868f2 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters @@ -162,7 +162,10 @@ Header Files - + + Header Files + + Header Files @@ -263,7 +266,10 @@ Source Files - + + Source Files + + Source Files diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.cpp index dd3298cd8a..306b4ccab0 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.cpp @@ -15,7 +15,7 @@ namespace ZonedWindowProperties const wchar_t PropertySortKeyWithinZone[] = L"FancyZones_TabSortKeyWithinZone"; } -void FancyZonesWindowProperties::StampZoneIndexProperty(HWND window, const ZoneIndexSet& zoneSet) +bool FancyZonesWindowProperties::StampZoneIndexProperty(HWND window, const ZoneIndexSet& zoneSet) { RemoveZoneIndexProperty(window); ZoneIndexSetBitmask bitmask = ZoneIndexSetBitmask::FromIndexSet(zoneSet); @@ -33,6 +33,7 @@ void FancyZonesWindowProperties::StampZoneIndexProperty(HWND window, const ZoneI if (!SetProp(window, ZonedWindowProperties::PropertyMultipleZone64ID, rawData)) { Logger::error(L"Failed to stamp window {}", get_last_error_or_default(GetLastError())); + return false; } } @@ -49,8 +50,11 @@ void FancyZonesWindowProperties::StampZoneIndexProperty(HWND window, const ZoneI if (!SetProp(window, ZonedWindowProperties::PropertyMultipleZone128ID, rawData)) { Logger::error(L"Failed to stamp window {}", get_last_error_or_default(GetLastError())); + return false; } } + + return true; } void FancyZonesWindowProperties::RemoveZoneIndexProperty(HWND window) diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h index 8b3bae38e1..0bd8ff16bf 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h @@ -18,7 +18,7 @@ namespace ZonedWindowProperties namespace FancyZonesWindowProperties { - void StampZoneIndexProperty(HWND window, const ZoneIndexSet& zoneSet); + bool StampZoneIndexProperty(HWND window, const ZoneIndexSet& zoneSet); void RemoveZoneIndexProperty(HWND window); ZoneIndexSet RetrieveZoneIndexProperty(HWND window); diff --git a/src/modules/fancyzones/FancyZonesLib/JsonHelpers.cpp b/src/modules/fancyzones/FancyZonesLib/JsonHelpers.cpp index c557bcf1a3..44bfb5fec9 100644 --- a/src/modules/fancyzones/FancyZonesLib/JsonHelpers.cpp +++ b/src/modules/fancyzones/FancyZonesLib/JsonHelpers.cpp @@ -277,13 +277,16 @@ namespace .monitorId = { .deviceId = MonitorUtils::Display::ConvertObsoleteDeviceId(deviceId->deviceName) }, .virtualDesktopId = deviceId->virtualDesktopId }; - data.zoneSetUuid = json.GetNamedString(NonLocalizable::ZoneSetUuidStr); - if (!FancyZonesUtils::IsValidGuid(data.zoneSetUuid)) + std::wstring layoutIdStr = json.GetNamedString(NonLocalizable::ZoneSetUuidStr).c_str(); + auto layoutIdOpt = FancyZonesUtils::GuidFromString(layoutIdStr); + if (!layoutIdOpt.has_value()) { return std::nullopt; } + data.layoutId = layoutIdOpt.value(); + return data; } diff --git a/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.cpp b/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.cpp index 75cc6b5fa4..f2089cf176 100644 --- a/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.cpp +++ b/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.cpp @@ -6,37 +6,10 @@ #include #include -LayoutAssignedWindows::LayoutAssignedWindows() -{ - m_extendData = std::make_unique(); -} - void LayoutAssignedWindows::Assign(HWND window, const ZoneIndexSet& zones) { Dismiss(window); - - // clear info about extension - std::erase_if(m_extendData->windowInitialIndexSet, [window](const auto& item) { return item.first == window; }); - std::erase_if(m_extendData->windowFinalIndex, [window](const auto& item) { return item.first == window; }); - - for (const auto& index : zones) - { - m_windowIndexSet[window].push_back(index); - } - - if (FancyZonesSettings::settings().disableRoundCorners) - { - FancyZonesWindowUtils::DisableRoundCorners(window); - } - - auto tabSortKeyWithinZone = FancyZonesWindowProperties::GetTabSortKeyWithinZone(window); - InsertWindowIntoZone(window, tabSortKeyWithinZone, zones); -} - -void LayoutAssignedWindows::Extend(HWND window, const ZoneIndexSet& zones) -{ - Dismiss(window); - + for (const auto& index : zones) { m_windowIndexSet[window].push_back(index); @@ -133,11 +106,6 @@ void LayoutAssignedWindows::CycleWindows(HWND window, bool reverse) } } -const std::unique_ptr& LayoutAssignedWindows::ExtendWindowData() -{ - return m_extendData; -} - void LayoutAssignedWindows::InsertWindowIntoZone(HWND window, std::optional tabSortKeyWithinZone, const ZoneIndexSet& indexSet) { if (tabSortKeyWithinZone.has_value()) diff --git a/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.h b/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.h index 64377e36b1..b7754b057d 100644 --- a/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.h +++ b/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.h @@ -4,19 +4,11 @@ class LayoutAssignedWindows { -public: - struct ExtendWindowModeData - { - std::map windowInitialIndexSet; - std::map windowFinalIndex; - }; - public : - LayoutAssignedWindows(); + LayoutAssignedWindows() = default; ~LayoutAssignedWindows() = default; void Assign(HWND window, const ZoneIndexSet& zones); - void Extend(HWND window, const ZoneIndexSet& zones); void Dismiss(HWND window); std::map SnappedWindows() const noexcept; @@ -25,12 +17,9 @@ public : void CycleWindows(HWND window, bool reverse); - const std::unique_ptr& ExtendWindowData(); - private: std::map m_windowIndexSet{}; std::map> m_windowsByIndexSets{}; - std::unique_ptr m_extendData{}; // Needed for ExtendWindowByDirectionAndPosition void InsertWindowIntoZone(HWND window, std::optional tabSortKeyWithinZone, const ZoneIndexSet& indexSet); HWND GetNextZoneWindow(ZoneIndexSet indexSet, HWND current, bool reverse) noexcept; diff --git a/src/modules/fancyzones/FancyZonesLib/Settings.h b/src/modules/fancyzones/FancyZonesLib/Settings.h index 8c1789a6b9..632c6a3a78 100644 --- a/src/modules/fancyzones/FancyZonesLib/Settings.h +++ b/src/modules/fancyzones/FancyZonesLib/Settings.h @@ -80,6 +80,13 @@ public: #endif } +#if defined(UNIT_TESTS) + inline void SetSettings(const Settings& settings) + { + m_settings = settings; + } +#endif + void AddObserver(SettingsObserver& observer); void RemoveObserver(SettingsObserver& observer); diff --git a/src/modules/fancyzones/FancyZonesLib/WindowKeyboardSnap.cpp b/src/modules/fancyzones/FancyZonesLib/WindowKeyboardSnap.cpp new file mode 100644 index 0000000000..7274c6cda9 --- /dev/null +++ b/src/modules/fancyzones/FancyZonesLib/WindowKeyboardSnap.cpp @@ -0,0 +1,489 @@ +#include "pch.h" +#include "WindowKeyboardSnap.h" + +#include +#include +#include +#include +#include + +#include +#include + +bool WindowKeyboardSnap::Snap(HWND window, HMONITOR monitor, DWORD vkCode, const std::unordered_map>& activeWorkAreas, const std::vector& monitors) +{ + return (vkCode == VK_LEFT || vkCode == VK_RIGHT) && SnapHotkeyBasedOnZoneNumber(window, vkCode, monitor, activeWorkAreas, monitors); +} + +bool WindowKeyboardSnap::Snap(HWND window, RECT windowRect, HMONITOR monitor, DWORD vkCode, const std::unordered_map>& activeWorkAreas, const std::vector>& monitors) +{ + if (!activeWorkAreas.contains(monitor)) + { + return false; + } + + // clean previous extension data + m_extendData.Reset(); + + const auto& currentWorkArea = activeWorkAreas.at(monitor); + if (monitors.size() > 1 && FancyZonesSettings::settings().moveWindowAcrossMonitors) + { + // Multi monitor environment. + // First, try to stay on the same monitor + bool success = MoveByDirectionAndPosition(window, windowRect, vkCode, false, currentWorkArea.get()); + if (success) + { + return true; + } + + // Try to snap on another monitor + success = SnapBasedOnPositionOnAnotherMonitor(window, windowRect, vkCode, monitor, activeWorkAreas, monitors); + if (success) + { + // Unsnap from previous work area + currentWorkArea->Unsnap(window); + } + + return success; + } + else + { + // Single monitor environment, or combined multi-monitor environment. + return MoveByDirectionAndPosition(window, windowRect, vkCode, true, currentWorkArea.get()); + } +} + +bool WindowKeyboardSnap::Extend(HWND window, RECT windowRect, HMONITOR monitor, DWORD vkCode, const std::unordered_map>& activeWorkAreas) +{ + if (!activeWorkAreas.contains(monitor)) + { + return false; + } + + // continue extension process + const auto& workArea = activeWorkAreas.at(monitor); + return Extend(window, windowRect, vkCode, workArea.get()); +} + +bool WindowKeyboardSnap::SnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode, HMONITOR current, const std::unordered_map>& activeWorkAreas, const std::vector& monitors) +{ + // clean previous extension data + m_extendData.Reset(); + + if (current && monitors.size() > 1 && FancyZonesSettings::settings().moveWindowAcrossMonitors) + { + // Multi monitor environment. + auto currMonitor = std::find(std::begin(monitors), std::end(monitors), current); + do + { + if (activeWorkAreas.contains(*currMonitor)) + { + const auto& workArea = activeWorkAreas.at(*currMonitor); + + if (MoveByDirectionAndIndex(window, vkCode, false /* cycle through zones */, workArea.get())) + { + // unassign from previous work area + for (auto& [_, prevWorkArea] : activeWorkAreas) + { + if (prevWorkArea && workArea != prevWorkArea) + { + prevWorkArea->Unsnap(window); + } + } + + return true; + } + // We iterated through all zones in current monitor zone layout, move on to next one (or previous depending on direction). + if (vkCode == VK_RIGHT) + { + currMonitor = std::next(currMonitor); + if (currMonitor == std::end(monitors)) + { + currMonitor = std::begin(monitors); + } + } + else if (vkCode == VK_LEFT) + { + if (currMonitor == std::begin(monitors)) + { + currMonitor = std::end(monitors); + } + currMonitor = std::prev(currMonitor); + } + } + } while (*currMonitor != current); + } + else + { + if (activeWorkAreas.contains(current)) + { + const auto& workArea = activeWorkAreas.at(current); + bool moved = MoveByDirectionAndIndex(window, vkCode, FancyZonesSettings::settings().moveWindowAcrossMonitors /* cycle through zones */, workArea.get()); + + if (FancyZonesSettings::settings().restoreSize && !moved) + { + FancyZonesWindowUtils::RestoreWindowOrigin(window); + FancyZonesWindowUtils::RestoreWindowSize(window); + } + + return moved; + } + } + + return false; +} + +bool WindowKeyboardSnap::SnapBasedOnPositionOnAnotherMonitor(HWND window, RECT windowRect, DWORD vkCode, HMONITOR current, const std::unordered_map>& activeWorkAreas, const std::vector>& monitors) +{ + // Extract zones from all other monitors and target one of them + std::vector zoneRects; + std::vector> zoneRectsInfo; + RECT currentMonitorRect{ .top = 0, .bottom = -1 }; + + for (const auto& [monitor, monitorRect] : monitors) + { + if (monitor == current) + { + currentMonitorRect = monitorRect; + } + else + { + if (activeWorkAreas.contains(monitor)) + { + const auto& workArea = activeWorkAreas.at(monitor); + const auto& layout = workArea->GetLayout(); + if (layout) + { + const auto& zones = layout->Zones(); + for (const auto& [zoneId, zone] : zones) + { + RECT zoneRect = zone.GetZoneRect(); + + zoneRect.left += monitorRect.left; + zoneRect.right += monitorRect.left; + zoneRect.top += monitorRect.top; + zoneRect.bottom += monitorRect.top; + + zoneRects.emplace_back(zoneRect); + zoneRectsInfo.emplace_back(zoneId, workArea.get()); + } + } + } + } + } + + auto chosenIdx = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects); + + if (chosenIdx < zoneRects.size()) + { + // Moving to another monitor succeeded + const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx]; + bool snapped = false; + if (workArea) + { + snapped = workArea->Snap(window, { trueZoneIdx }); + } + + if (snapped) + { + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows()); + } + + return snapped; + } + + // We reached the end of all monitors. + // Try again, cycling on all monitors. + // First, add zones from the origin monitor to zoneRects + // Sanity check: the current monitor is valid + if (currentMonitorRect.top <= currentMonitorRect.bottom) + { + const auto& currentWorkArea = activeWorkAreas.at(current); + if (currentWorkArea) + { + const auto& layout = currentWorkArea->GetLayout(); + if (layout) + { + const auto& zones = layout->Zones(); + for (const auto& [zoneId, zone] : zones) + { + RECT zoneRect = zone.GetZoneRect(); + + zoneRect.left += currentMonitorRect.left; + zoneRect.right += currentMonitorRect.left; + zoneRect.top += currentMonitorRect.top; + zoneRect.bottom += currentMonitorRect.top; + + zoneRects.emplace_back(zoneRect); + zoneRectsInfo.emplace_back(zoneId, currentWorkArea.get()); + } + } + } + } + else + { + return false; + } + + RECT combinedRect = FancyZonesUtils::GetMonitorsCombinedRect<&MONITORINFOEX::rcWork>(monitors); + windowRect = FancyZonesUtils::PrepareRectForCycling(windowRect, combinedRect, vkCode); + chosenIdx = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects); + if (chosenIdx < zoneRects.size()) + { + // Moving to another monitor succeeded + const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx]; + + bool snapped = false; + if (workArea) + { + snapped = workArea->Snap(window, { trueZoneIdx }); + } + + if (snapped) + { + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows()); + } + + return snapped; + } + else + { + // Giving up + return false; + } +} + +bool WindowKeyboardSnap::MoveByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, WorkArea* const workArea) +{ + if (!workArea) + { + return false; + } + + const auto& layout = workArea->GetLayout(); + const auto& zones = layout->Zones(); + const auto& layoutWindows = workArea->GetLayoutWindows(); + if (!layout || zones.empty()) + { + return false; + } + + auto zoneIndexes = layoutWindows.GetZoneIndexSetFromWindow(window); + const auto numZones = zones.size(); + bool snapped = false; + + // The window was not assigned to any zone here + if (zoneIndexes.size() == 0) + { + const ZoneIndex zone = vkCode == VK_LEFT ? numZones - 1 : 0; + snapped = workArea->Snap(window, { zone }); + } + else + { + const ZoneIndex oldId = zoneIndexes[0]; + + // We reached the edge + if ((vkCode == VK_LEFT && oldId == 0) || (vkCode == VK_RIGHT && oldId == static_cast(numZones) - 1)) + { + if (!cycle) + { + return false; + } + + const ZoneIndex zone = vkCode == VK_LEFT ? numZones - 1 : 0; + snapped = workArea->Snap(window, { zone }); + } + else + { + // We didn't reach the edge + if (vkCode == VK_LEFT) + { + snapped = workArea->Snap(window, { oldId - 1 }); + } + else + { + snapped = workArea->Snap(window, { oldId + 1 }); + } + } + } + + if (snapped) + { + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows()); + } + + return snapped; +} + +bool WindowKeyboardSnap::MoveByDirectionAndPosition(HWND window, RECT windowRect, DWORD vkCode, bool cycle, WorkArea* const workArea) +{ + if (!workArea) + { + return false; + } + + const auto& layout = workArea->GetLayout(); + const auto& zones = layout->Zones(); + const auto& layoutWindows = workArea->GetLayoutWindows(); + if (!layout || zones.empty()) + { + return false; + } + + std::vector usedZoneIndices(zones.size(), false); + auto windowZones = layoutWindows.GetZoneIndexSetFromWindow(window); + + for (const ZoneIndex id : windowZones) + { + usedZoneIndices[id] = true; + } + + std::vector zoneRects; + ZoneIndexSet freeZoneIndices; + + for (const auto& [zoneId, zone] : zones) + { + if (!usedZoneIndices[zoneId]) + { + zoneRects.emplace_back(zones.at(zoneId).GetZoneRect()); + freeZoneIndices.emplace_back(zoneId); + } + } + + // Move to coordinates relative to windowZone + const auto& workAreaRect = workArea->GetWorkAreaRect(); + windowRect.top -= workAreaRect.top(); + windowRect.bottom -= workAreaRect.top(); + windowRect.left -= workAreaRect.left(); + windowRect.right -= workAreaRect.left(); + + ZoneIndex result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects); + if (static_cast(result) < zoneRects.size()) + { + bool success = workArea->Snap(window, { freeZoneIndices[result] }); + if (success) + { + Trace::FancyZones::KeyboardSnapWindowToZone(layout.get(), layoutWindows); + } + return success; + } + else if (cycle) + { + // Try again from the position off the screen in the opposite direction to vkCode + // Consider all zones as available + zoneRects.resize(zones.size()); + std::transform(zones.begin(), zones.end(), zoneRects.begin(), [](auto zone) { return zone.second.GetZoneRect(); }); + windowRect = FancyZonesUtils::PrepareRectForCycling(windowRect, RECT(workAreaRect.left(), workAreaRect.top(), workAreaRect.right(), workAreaRect.bottom()), vkCode); + result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects); + + if (static_cast(result) < zoneRects.size()) + { + bool success = workArea->Snap(window, { result }); + + if (success) + { + Trace::FancyZones::KeyboardSnapWindowToZone(layout.get(), layoutWindows); + } + + return success; + } + } + + return false; +} + +bool WindowKeyboardSnap::Extend(HWND window, RECT windowRect, DWORD vkCode, WorkArea* const workArea) +{ + if (!workArea) + { + return false; + } + + const auto& layout = workArea->GetLayout(); + const auto& layoutWindows = workArea->GetLayoutWindows(); + if (!layout || layout->Zones().empty()) + { + return false; + } + + const auto& zones = layout->Zones(); + auto appliedZones = layoutWindows.GetZoneIndexSetFromWindow(window); + + std::vector usedZoneIndices(zones.size(), false); + std::vector zoneRects; + ZoneIndexSet freeZoneIndices; + + // If selectManyZones = true for the second time, use the last zone into which we moved + // instead of the window rect and enable moving to all zones except the old one + if (m_extendData.IsExtended(window)) + { + usedZoneIndices[m_extendData.windowFinalIndex] = true; + windowRect = zones.at(m_extendData.windowFinalIndex).GetZoneRect(); + } + else + { + for (const ZoneIndex idx : appliedZones) + { + usedZoneIndices[idx] = true; + } + + // Move to coordinates relative to windowZone + const auto& workAreaRect = workArea->GetWorkAreaRect(); + windowRect.top -= workAreaRect.top(); + windowRect.bottom -= workAreaRect.top(); + windowRect.left -= workAreaRect.left(); + windowRect.right -= workAreaRect.left(); + + m_extendData.Set(window); + } + + for (size_t i = 0; i < zones.size(); i++) + { + if (!usedZoneIndices[i]) + { + zoneRects.emplace_back(zones.at(i).GetZoneRect()); + freeZoneIndices.emplace_back(i); + } + } + + const auto result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects); + if (result >= zoneRects.size()) + { + return false; + } + + ZoneIndex targetZone = freeZoneIndices[result]; + ZoneIndexSet resultIndexSet; + + // First time with selectManyZones = true for this window? + if (m_extendData.windowFinalIndex == -1) + { + // Already zoned? + if (appliedZones.size()) + { + m_extendData.windowInitialIndexSet = appliedZones; + m_extendData.windowFinalIndex = targetZone; + resultIndexSet = layout->GetCombinedZoneRange(appliedZones, { targetZone }); + } + else + { + m_extendData.windowInitialIndexSet = { targetZone }; + m_extendData.windowFinalIndex = targetZone; + resultIndexSet = { targetZone }; + } + } + else + { + auto deletethis = m_extendData.windowInitialIndexSet; + m_extendData.windowFinalIndex = targetZone; + resultIndexSet = layout->GetCombinedZoneRange(m_extendData.windowInitialIndexSet, { targetZone }); + } + + bool success = workArea->Snap(window, resultIndexSet); + if (success) + { + Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows()); + } + + return success; +} diff --git a/src/modules/fancyzones/FancyZonesLib/WindowKeyboardSnap.h b/src/modules/fancyzones/FancyZonesLib/WindowKeyboardSnap.h new file mode 100644 index 0000000000..086b4047ba --- /dev/null +++ b/src/modules/fancyzones/FancyZonesLib/WindowKeyboardSnap.h @@ -0,0 +1,56 @@ +#pragma once + +#include + +class WorkArea; + +class WindowKeyboardSnap +{ + struct ExtendWindowModeData + { + HWND window{ nullptr }; + ZoneIndexSet windowInitialIndexSet{}; + ZoneIndex windowFinalIndex{ -1 }; + + bool IsExtended(HWND wnd) const + { + return window == wnd && windowFinalIndex != -1; + } + + void Set(HWND w) + { + window = w; + windowFinalIndex = -1; + windowInitialIndexSet.clear(); + } + + void Reset() + { + window = nullptr; + windowFinalIndex = -1; + windowInitialIndexSet.clear(); + } + }; + +public: + WindowKeyboardSnap() = default; + ~WindowKeyboardSnap() = default; + + bool Snap(HWND window, HMONITOR activeMonitor, DWORD vkCode, + const std::unordered_map>& activeWorkAreas, + const std::vector& monitors); + bool Snap(HWND window, RECT windowRect, HMONITOR activeMonitor, DWORD vkCode, + const std::unordered_map>& activeWorkAreas, + const std::vector>& monitors); + bool Extend(HWND window, RECT windowRect, HMONITOR monitor, DWORD vkCode, const std::unordered_map>& activeWorkAreas); + +private: + bool SnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode, HMONITOR monitor, const std::unordered_map>& activeWorkAreas, const std::vector& monitors); + bool SnapBasedOnPositionOnAnotherMonitor(HWND window, RECT windowRect, DWORD vkCode, HMONITOR monitor, const std::unordered_map>& activeWorkAreas, const std::vector>& monitors); + + bool MoveByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, WorkArea* const workArea); + bool MoveByDirectionAndPosition(HWND window, RECT windowRect, DWORD vkCode, bool cycle, WorkArea* const workArea); + bool Extend(HWND window, RECT windowRect, DWORD vkCode, WorkArea* const workArea); + + ExtendWindowModeData m_extendData{}; // Needed for ExtendWindowByDirectionAndPosition +}; diff --git a/src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.cpp b/src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.cpp new file mode 100644 index 0000000000..c6b8f8cedd --- /dev/null +++ b/src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.cpp @@ -0,0 +1,244 @@ +#include "pch.h" +#include "WindowMouseSnap.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +WindowMouseSnap::WindowMouseSnap(HWND window, const std::unordered_map>& activeWorkAreas) : + m_window(window), + m_activeWorkAreas(activeWorkAreas), + m_currentWorkArea(nullptr), + m_snappingMode(false) +{ + m_windowProperties.hasNoVisibleOwner = !FancyZonesWindowUtils::HasVisibleOwner(m_window); + m_windowProperties.isStandardWindow = FancyZonesWindowUtils::IsStandardWindow(m_window) && + (!FancyZonesWindowUtils::IsPopupWindow(m_window) || FancyZonesSettings::settings().allowSnapPopupWindows); +} + +WindowMouseSnap::~WindowMouseSnap() +{ + ResetWindowTransparency(); +} + +std::unique_ptr WindowMouseSnap::Create(HWND window, const std::unordered_map>& activeWorkAreas) +{ + if (!FancyZonesWindowProcessing::IsProcessable(window) || + !FancyZonesWindowUtils::IsCandidateForZoning(window) || + FancyZonesWindowUtils::IsCursorTypeIndicatingSizeEvent()) + { + return nullptr; + } + + if (!is_process_elevated() && FancyZonesWindowUtils::IsProcessOfWindowElevated(window)) + { + // Notifies user if unable to drag elevated window + FancyZonesNotifications::WarnIfElevationIsRequired(); + return nullptr; + } + + return std::unique_ptr(new WindowMouseSnap(window, activeWorkAreas)); +} + +bool WindowMouseSnap::MoveSizeStart(HMONITOR monitor, bool isSnapping) +{ + auto iter = m_activeWorkAreas.find(monitor); + if (iter == end(m_activeWorkAreas)) + { + return false; + } + + m_currentWorkArea = iter->second.get(); + + SwitchSnappingMode(isSnapping); + + if (m_currentWorkArea) + { + m_currentWorkArea->Unsnap(m_window); + } + + return true; +} + +void WindowMouseSnap::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, bool isSnapping, bool isSelectManyZonesState) +{ + auto iter = m_activeWorkAreas.find(monitor); + if (isSnapping && iter != m_activeWorkAreas.end()) + { + // The drag has moved to a different monitor. + // Change work area + if (iter->second.get() != m_currentWorkArea) + { + m_highlightedZones.Reset(); + + if (m_currentWorkArea) + { + if (!FancyZonesSettings::settings().showZonesOnAllMonitors) + { + m_currentWorkArea->HideZones(); + } + else + { + m_currentWorkArea->ShowZones({}, m_window); + } + } + + m_currentWorkArea = iter->second.get(); + } + + if (m_currentWorkArea) + { + POINT ptClient = ptScreen; + MapWindowPoints(nullptr, m_currentWorkArea->GetWorkAreaWindow(), &ptClient, 1); + const bool redraw = m_highlightedZones.Update(m_currentWorkArea->GetLayout().get(), ptClient, isSelectManyZonesState); + if (redraw) + { + m_currentWorkArea->ShowZones(m_highlightedZones.Zones(), m_window); + } + } + } + + SwitchSnappingMode(isSnapping); +} + +void WindowMouseSnap::MoveSizeEnd() +{ + if (m_snappingMode) + { + const bool hasNoVisibleOwner = !FancyZonesWindowUtils::HasVisibleOwner(m_window); + const bool isStandardWindow = FancyZonesWindowUtils::IsStandardWindow(m_window); + + if ((isStandardWindow == false && hasNoVisibleOwner == true && + m_windowProperties.isStandardWindow == true && m_windowProperties.hasNoVisibleOwner == true) || + FancyZonesWindowUtils::IsWindowMaximized(m_window)) + { + // Abort the zoning, this is a Chromium based tab that is merged back with an existing window + // or if the window is maximized by Windows when the cursor hits the screen top border + } + else if (m_currentWorkArea) + { + m_currentWorkArea->Snap(m_window, m_highlightedZones.Zones()); + } + } + else + { + FancyZonesWindowUtils::ResetRoundCornersPreference(m_window); + if (FancyZonesSettings::settings().restoreSize) + { + if (FancyZonesWindowUtils::IsCursorTypeIndicatingSizeEvent()) + { + ::RemoveProp(m_window, ZonedWindowProperties::PropertyRestoreSizeID); + } + else if (!FancyZonesWindowUtils::IsWindowMaximized(m_window)) + { + FancyZonesWindowUtils::RestoreWindowSize(m_window); + } + } + } + + SwitchSnappingMode(false); +} + +void WindowMouseSnap::SwitchSnappingMode(bool isSnapping) +{ + if (!m_snappingMode && isSnapping) // turn on + { + m_highlightedZones.Reset(); + SetWindowTransparency(); + + if (FancyZonesSettings::settings().showZonesOnAllMonitors) + { + for (const auto& [_, workArea] : m_activeWorkAreas) + { + if (workArea) + { + workArea->ShowZones({}, m_window); + } + } + } + else if (m_currentWorkArea) + { + m_currentWorkArea->ShowZones({}, m_window); + } + + if (m_currentWorkArea) + { + m_currentWorkArea->Unsnap(m_window); + Trace::WorkArea::MoveOrResizeStarted(m_currentWorkArea->GetLayout().get(), m_currentWorkArea->GetLayoutWindows()); + } + } + else if (m_snappingMode && !isSnapping) // turn off + { + ResetWindowTransparency(); + m_highlightedZones.Reset(); + + // Hide all layouts (regardless of settings) + for (auto& [_, workArea] : m_activeWorkAreas) + { + if (workArea) + { + workArea->HideZones(); + } + } + + if (m_currentWorkArea) + { + Trace::WorkArea::MoveOrResizeEnd(m_currentWorkArea->GetLayout().get(), m_currentWorkArea->GetLayoutWindows()); + } + } + + m_snappingMode = isSnapping; +} + +void WindowMouseSnap::SetWindowTransparency() +{ + if (FancyZonesSettings::settings().makeDraggedWindowTransparent) + { + m_windowProperties.exstyle = GetWindowLong(m_window, GWL_EXSTYLE); + + SetWindowLong(m_window, GWL_EXSTYLE, m_windowProperties.exstyle | WS_EX_LAYERED); + + if (!GetLayeredWindowAttributes(m_window, &m_windowProperties.crKey, &m_windowProperties.alpha, &m_windowProperties.dwFlags)) + { + Logger::error(L"Window transparency: GetLayeredWindowAttributes failed, {}", get_last_error_or_default(GetLastError())); + return; + } + + if (!SetLayeredWindowAttributes(m_window, 0, (255 * 50) / 100, LWA_ALPHA)) + { + Logger::error(L"Window transparency: SetLayeredWindowAttributes failed, {}", get_last_error_or_default(GetLastError())); + return; + } + + m_windowProperties.transparencySet = true; + } +} + +void WindowMouseSnap::ResetWindowTransparency() +{ + if (FancyZonesSettings::settings().makeDraggedWindowTransparent && m_windowProperties.transparencySet) + { + bool reset = true; + if (!SetLayeredWindowAttributes(m_window, m_windowProperties.crKey, m_windowProperties.alpha, m_windowProperties.dwFlags)) + { + Logger::error(L"Window transparency: SetLayeredWindowAttributes failed, {}", get_last_error_or_default(GetLastError())); + reset = false; + } + + if (SetWindowLong(m_window, GWL_EXSTYLE, m_windowProperties.exstyle) == 0) + { + Logger::error(L"Window transparency: SetWindowLong failed, {}", get_last_error_or_default(GetLastError())); + reset = false; + } + + m_windowProperties.transparencySet = !reset; + } +} diff --git a/src/modules/fancyzones/FancyZonesLib/WindowDrag.h b/src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.h similarity index 82% rename from src/modules/fancyzones/FancyZonesLib/WindowDrag.h rename to src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.h index 0956af9841..5891771e14 100644 --- a/src/modules/fancyzones/FancyZonesLib/WindowDrag.h +++ b/src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.h @@ -4,13 +4,13 @@ class WorkArea; -class WindowDrag +class WindowMouseSnap { - WindowDrag(HWND window, const std::unordered_map>& activeWorkAreas); + WindowMouseSnap(HWND window, const std::unordered_map>& activeWorkAreas); public: - static std::unique_ptr Create(HWND window, const std::unordered_map>& activeWorkAreas); - ~WindowDrag(); + static std::unique_ptr Create(HWND window, const std::unordered_map>& activeWorkAreas); + ~WindowMouseSnap(); bool MoveSizeStart(HMONITOR monitor, bool isSnapping); void MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, bool isSnapping, bool isSelectManyZonesState); diff --git a/src/modules/fancyzones/FancyZonesLib/WindowUtils.cpp b/src/modules/fancyzones/FancyZonesLib/WindowUtils.cpp index 40f22ea802..dbad77732c 100644 --- a/src/modules/fancyzones/FancyZonesLib/WindowUtils.cpp +++ b/src/modules/fancyzones/FancyZonesLib/WindowUtils.cpp @@ -456,15 +456,19 @@ RECT FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(HWND window, RECT rect ::GetWindowRect(window, &windowRect); // Take care of borders - RECT frameRect{}; - if (SUCCEEDED(DwmGetWindowAttribute(window, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRect, sizeof(frameRect)))) + // Skip when windowOfRect is not initialized (in unit tests) + if (windowOfRect) { - LONG leftMargin = frameRect.left - windowRect.left; - LONG rightMargin = frameRect.right - windowRect.right; - LONG bottomMargin = frameRect.bottom - windowRect.bottom; - newWindowRect.left -= leftMargin; - newWindowRect.right -= rightMargin; - newWindowRect.bottom -= bottomMargin; + RECT frameRect{}; + if (SUCCEEDED(DwmGetWindowAttribute(window, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRect, sizeof(frameRect)))) + { + LONG leftMargin = frameRect.left - windowRect.left; + LONG rightMargin = frameRect.right - windowRect.right; + LONG bottomMargin = frameRect.bottom - windowRect.bottom; + newWindowRect.left -= leftMargin; + newWindowRect.right -= rightMargin; + newWindowRect.bottom -= bottomMargin; + } } // Take care of windows that cannot be resized @@ -475,7 +479,10 @@ RECT FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(HWND window, RECT rect } // Convert to screen coordinates - MapWindowRect(windowOfRect, nullptr, &newWindowRect); + if (windowOfRect) + { + MapWindowRect(windowOfRect, nullptr, &newWindowRect); + } return newWindowRect; } diff --git a/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp b/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp index afed03881f..002bcdc63b 100644 --- a/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp +++ b/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp @@ -1,26 +1,16 @@ #include "pch.h" #include "WorkArea.h" -#include #include -#include #include "FancyZonesData/AppliedLayouts.h" #include "FancyZonesData/AppZoneHistory.h" -#include "FancyZonesDataTypes.h" -#include "SettingsObserver.h" #include "ZonesOverlay.h" -#include "trace.h" -#include "on_thread_executor.h" #include "Settings.h" #include #include #include -#include -#include -#include - // disabling warning 4458 - declaration of 'identifier' hides class member // to avoid warnings from GDI files - can't add winRT directory to external code // in the Cpp.Build.props @@ -127,284 +117,41 @@ WorkArea::~WorkArea() windowPool.FreeZonesOverlayWindow(m_window); } -void WorkArea::MoveWindowIntoZoneByIndex(HWND window, ZoneIndex index) +bool WorkArea::Snap(HWND window, const ZoneIndexSet& zones, bool updatePosition) { - MoveWindowIntoZoneByIndexSet(window, { index }); -} - -void WorkArea::MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, bool updatePosition /* = true*/) -{ - if (!m_layout || !m_layoutWindows || m_layout->Zones().empty() || indexSet.empty()) + if (!m_layout || zones.empty()) { - return; + return false; } - FancyZonesWindowUtils::SaveWindowSizeAndOrigin(window); + m_layoutWindows.Assign(window, zones); + AppZoneHistory::instance().SetAppLastZones(window, m_uniqueId, m_layout->Id(), zones); if (updatePosition) { - const auto rect = m_layout->GetCombinedZonesRect(indexSet); - if (rect.bottom - rect.top > 0 && rect.right - rect.left > 0) - { - const auto adjustedRect = FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(window, rect, m_window); - FancyZonesWindowUtils::SizeWindowToRect(window, adjustedRect); - } + const auto rect = m_layout->GetCombinedZonesRect(zones); + const auto adjustedRect = FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(window, rect, m_window); + FancyZonesWindowUtils::SaveWindowSizeAndOrigin(window); + FancyZonesWindowUtils::SizeWindowToRect(window, adjustedRect); } - SnapWindow(window, indexSet); + return FancyZonesWindowProperties::StampZoneIndexProperty(window, zones); } -bool WorkArea::MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle) +bool WorkArea::Unsnap(HWND window) { - if (!m_layout || !m_layoutWindows || m_layout->Zones().empty()) + if (!m_layout) { return false; } - - auto zoneIndexes = m_layoutWindows->GetZoneIndexSetFromWindow(window); - const auto numZones = m_layout->Zones().size(); - - // The window was not assigned to any zone here - if (zoneIndexes.size() == 0) - { - MoveWindowIntoZoneByIndex(window, vkCode == VK_LEFT ? numZones - 1 : 0); - } - else - { - const ZoneIndex oldId = zoneIndexes[0]; - - // We reached the edge - if ((vkCode == VK_LEFT && oldId == 0) || (vkCode == VK_RIGHT && oldId == static_cast(numZones) - 1)) - { - if (!cycle) - { - return false; - } - - MoveWindowIntoZoneByIndex(window, vkCode == VK_LEFT ? numZones - 1 : 0); - } - else - { - // We didn't reach the edge - if (vkCode == VK_LEFT) - { - MoveWindowIntoZoneByIndex(window, oldId - 1); - } - else - { - MoveWindowIntoZoneByIndex(window, oldId + 1); - } - } - } + + m_layoutWindows.Dismiss(window); + AppZoneHistory::instance().RemoveAppLastZone(window, m_uniqueId, m_layout->Id()); + FancyZonesWindowProperties::RemoveZoneIndexProperty(window); return true; } -bool WorkArea::MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle) -{ - if (!m_layout || !m_layoutWindows || m_layout->Zones().empty()) - { - return false; - } - - const auto& zones = m_layout->Zones(); - std::vector usedZoneIndices(zones.size(), false); - auto windowZones = m_layoutWindows->GetZoneIndexSetFromWindow(window); - - for (const ZoneIndex id : windowZones) - { - usedZoneIndices[id] = true; - } - - std::vector zoneRects; - ZoneIndexSet freeZoneIndices; - - for (const auto& [zoneId, zone] : zones) - { - if (!usedZoneIndices[zoneId]) - { - zoneRects.emplace_back(zones.at(zoneId).GetZoneRect()); - freeZoneIndices.emplace_back(zoneId); - } - } - - RECT windowRect; - if (!GetWindowRect(window, &windowRect)) - { - Logger::error(L"GetWindowRect failed, {}", get_last_error_or_default(GetLastError())); - return false; - } - - // Move to coordinates relative to windowZone - windowRect.top -= m_workAreaRect.top(); - windowRect.bottom -= m_workAreaRect.top(); - windowRect.left -= m_workAreaRect.left(); - windowRect.right -= m_workAreaRect.left(); - - auto result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects); - if (result < zoneRects.size()) - { - MoveWindowIntoZoneByIndex(window, freeZoneIndices[result]); - Trace::FancyZones::KeyboardSnapWindowToZone(m_layout.get(), m_layoutWindows.get()); - return true; - } - else if (cycle) - { - // Try again from the position off the screen in the opposite direction to vkCode - // Consider all zones as available - zoneRects.resize(zones.size()); - std::transform(zones.begin(), zones.end(), zoneRects.begin(), [](auto zone) { return zone.second.GetZoneRect(); }); - windowRect = FancyZonesUtils::PrepareRectForCycling(windowRect, RECT(m_workAreaRect.left(), m_workAreaRect.top(), m_workAreaRect.right(), m_workAreaRect.bottom()), vkCode); - result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects); - - if (result < zoneRects.size()) - { - MoveWindowIntoZoneByIndex(window, result); - Trace::FancyZones::KeyboardSnapWindowToZone(m_layout.get(), m_layoutWindows.get()); - return true; - } - } - - return false; -} - -bool WorkArea::ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode) -{ - if (!m_layout || !m_layoutWindows || m_layout->Zones().empty()) - { - return false; - } - - RECT windowRect; - if (!GetWindowRect(window, &windowRect)) - { - Logger::error(L"GetWindowRect failed, {}", get_last_error_or_default(GetLastError())); - return false; - } - - const auto& zones = m_layout->Zones(); - auto appliedZones = m_layoutWindows->GetZoneIndexSetFromWindow(window); - const auto& extendModeData = m_layoutWindows->ExtendWindowData(); - - std::vector usedZoneIndices(zones.size(), false); - std::vector zoneRects; - ZoneIndexSet freeZoneIndices; - - // If selectManyZones = true for the second time, use the last zone into which we moved - // instead of the window rect and enable moving to all zones except the old one - auto finalIndexIt = extendModeData->windowFinalIndex.find(window); - if (finalIndexIt != extendModeData->windowFinalIndex.end()) - { - usedZoneIndices[finalIndexIt->second] = true; - windowRect = zones.at(finalIndexIt->second).GetZoneRect(); - } - else - { - for (const ZoneIndex idx : appliedZones) - { - usedZoneIndices[idx] = true; - } - // Move to coordinates relative to windowZone - windowRect.top -= m_workAreaRect.top(); - windowRect.bottom -= m_workAreaRect.top(); - windowRect.left -= m_workAreaRect.left(); - windowRect.right -= m_workAreaRect.left(); - } - - for (size_t i = 0; i < zones.size(); i++) - { - if (!usedZoneIndices[i]) - { - zoneRects.emplace_back(zones.at(i).GetZoneRect()); - freeZoneIndices.emplace_back(i); - } - } - - const auto result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects); - if (result < zoneRects.size()) - { - ZoneIndex targetZone = freeZoneIndices[result]; - ZoneIndexSet resultIndexSet; - - // First time with selectManyZones = true for this window? - if (finalIndexIt == extendModeData->windowFinalIndex.end()) - { - // Already zoned? - if (appliedZones.size()) - { - extendModeData->windowInitialIndexSet[window] = appliedZones; - extendModeData->windowFinalIndex[window] = targetZone; - resultIndexSet = m_layout->GetCombinedZoneRange(appliedZones, { targetZone }); - } - else - { - extendModeData->windowInitialIndexSet[window] = { targetZone }; - extendModeData->windowFinalIndex[window] = targetZone; - resultIndexSet = { targetZone }; - } - } - else - { - auto deletethis = extendModeData->windowInitialIndexSet[window]; - extendModeData->windowFinalIndex[window] = targetZone; - resultIndexSet = m_layout->GetCombinedZoneRange(extendModeData->windowInitialIndexSet[window], { targetZone }); - } - - const auto rect = m_layout->GetCombinedZonesRect(resultIndexSet); - const auto adjustedRect = FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(window, rect, m_window); - FancyZonesWindowUtils::SizeWindowToRect(window, adjustedRect); - - SnapWindow(window, resultIndexSet, true); - - return true; - } - - return false; -} - -void WorkArea::SnapWindow(HWND window, const ZoneIndexSet& zones, bool extend) -{ - if (!m_layoutWindows || !m_layout) - { - return; - } - - if (extend) - { - m_layoutWindows->Extend(window, zones); - } - else - { - m_layoutWindows->Assign(window, zones); - } - - auto guidStr = FancyZonesUtils::GuidToString(m_layout->Id()); - if (guidStr.has_value()) - { - AppZoneHistory::instance().SetAppLastZones(window, m_uniqueId, guidStr.value(), zones); - } - - FancyZonesWindowProperties::StampZoneIndexProperty(window, zones); -} - -void WorkArea::UnsnapWindow(HWND window) -{ - if (!m_layoutWindows || !m_layout) - { - return; - } - - m_layoutWindows->Dismiss(window); - - auto guidStr = FancyZonesUtils::GuidToString(m_layout->Id()); - if (guidStr.has_value()) - { - AppZoneHistory::instance().RemoveAppLastZone(window, m_uniqueId, guidStr.value()); - } - - FancyZonesWindowProperties::RemoveZoneIndexProperty(window); -} - const GUID WorkArea::GetLayoutId() const noexcept { if (m_layout) @@ -415,29 +162,7 @@ const GUID WorkArea::GetLayoutId() const noexcept return GUID{}; } -ZoneIndexSet WorkArea::GetWindowZoneIndexes(HWND window) const -{ - if (m_layout) - { - auto guidStr = FancyZonesUtils::GuidToString(m_layout->Id()); - if (guidStr.has_value()) - { - return AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_uniqueId, guidStr.value()); - } - else - { - Logger::error(L"Failed to convert to string layout GUID on the requested work area"); - } - } - else - { - Logger::error(L"No layout initialized on the requested work area"); - } - - return {}; -} - -void WorkArea::ShowZonesOverlay(const ZoneIndexSet& highlight, HWND draggedWindow/* = nullptr*/) +void WorkArea::ShowZones(const ZoneIndexSet& highlight, HWND draggedWindow/* = nullptr*/) { if (m_layout && m_zonesOverlay) { @@ -447,7 +172,7 @@ void WorkArea::ShowZonesOverlay(const ZoneIndexSet& highlight, HWND draggedWindo } } -void WorkArea::HideZonesOverlay() +void WorkArea::HideZones() { if (m_zonesOverlay) { @@ -465,15 +190,10 @@ void WorkArea::FlashZones() } } -void WorkArea::UpdateActiveZoneSet() +void WorkArea::InitLayout() { - const bool isLayoutAlreadyApplied = AppliedLayouts::instance().IsLayoutApplied(m_uniqueId); - if (!isLayoutAlreadyApplied) - { - AppliedLayouts::instance().ApplyDefaultLayout(m_uniqueId); - } + InitLayout({}); - CalculateZoneSet(); if (m_window && m_layout) { m_zonesOverlay->DrawActiveZoneSet(m_layout->Zones(), {}, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber); @@ -482,24 +202,16 @@ void WorkArea::UpdateActiveZoneSet() void WorkArea::UpdateWindowPositions() { - if (!m_layoutWindows) - { - return; - } - - const auto& snappedWindows = m_layoutWindows->SnappedWindows(); + const auto& snappedWindows = m_layoutWindows.SnappedWindows(); for (const auto& [window, zones] : snappedWindows) { - MoveWindowIntoZoneByIndexSet(window, zones, true); + Snap(window, zones, true); } } void WorkArea::CycleWindows(HWND window, bool reverse) { - if (m_layoutWindows) - { - m_layoutWindows->CycleWindows(window, reverse); - } + m_layoutWindows.CycleWindows(window, reverse); } #pragma region private @@ -537,6 +249,46 @@ void WorkArea::InitLayout(const FancyZonesDataTypes::WorkAreaId& parentUniqueId) CalculateZoneSet(); } +void WorkArea::InitSnappedWindows() +{ + static bool updatePositionOnceOnStartFlag = true; + Logger::info(L"Init work area {} windows, update positions = {}", m_uniqueId.toString(), updatePositionOnceOnStartFlag); + + for (const auto& window : VirtualDesktop::instance().GetWindowsFromCurrentDesktop()) + { + auto indexes = FancyZonesWindowProperties::RetrieveZoneIndexProperty(window); + if (indexes.size() == 0) + { + continue; + } + + if (!m_uniqueId.monitorId.monitor) // one work area across monitors + { + Snap(window, indexes, updatePositionOnceOnStartFlag); + } + else + { + const auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL); + if (monitor && m_uniqueId.monitorId.monitor == monitor) + { + // prioritize snapping on the current monitor if the window was snapped to several work areas + Snap(window, indexes, updatePositionOnceOnStartFlag); + } + else + { + // if the window is not snapped on the current monitor, then check the others + auto savedIndexes = AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_uniqueId, GetLayoutId()); + if (savedIndexes == indexes) + { + Snap(window, indexes, updatePositionOnceOnStartFlag); + } + } + } + } + + updatePositionOnceOnStartFlag = false; +} + void WorkArea::CalculateZoneSet() { const auto appliedLayout = AppliedLayouts::instance().GetDeviceLayout(m_uniqueId); @@ -548,11 +300,6 @@ void WorkArea::CalculateZoneSet() m_layout = std::make_unique(appliedLayout.value()); m_layout->Init(m_workAreaRect, m_uniqueId.monitorId.monitor); - - if (!m_layoutWindows) - { - m_layoutWindows = std::make_unique(); - } } LRESULT WorkArea::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept diff --git a/src/modules/fancyzones/FancyZonesLib/WorkArea.h b/src/modules/fancyzones/FancyZonesLib/WorkArea.h index 5e04c87aee..ecd97fd849 100644 --- a/src/modules/fancyzones/FancyZonesLib/WorkArea.h +++ b/src/modules/fancyzones/FancyZonesLib/WorkArea.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -39,26 +38,20 @@ public: FancyZonesDataTypes::WorkAreaId UniqueId() const noexcept { return { m_uniqueId }; } const std::unique_ptr& GetLayout() const noexcept { return m_layout; } - const std::unique_ptr& GetLayoutWindows() const noexcept { return m_layoutWindows; } + const LayoutAssignedWindows& GetLayoutWindows() const noexcept { return m_layoutWindows; } const HWND GetWorkAreaWindow() const noexcept { return m_window; } const GUID GetLayoutId() const noexcept; + const FancyZonesUtils::Rect& GetWorkAreaRect() const noexcept { return m_workAreaRect; } - ZoneIndexSet GetWindowZoneIndexes(HWND window) const; - - void MoveWindowIntoZoneByIndex(HWND window, ZoneIndex index); - void MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, bool updatePosition = true); - bool MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle); - bool MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle); - bool ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode); - - void SnapWindow(HWND window, const ZoneIndexSet& zones, bool extend = false); - void UnsnapWindow(HWND window); - - void UpdateActiveZoneSet(); + void InitLayout(); + void InitSnappedWindows(); void UpdateWindowPositions(); - void ShowZonesOverlay(const ZoneIndexSet& highlight, HWND draggedWindow = nullptr); - void HideZonesOverlay(); + bool Snap(HWND window, const ZoneIndexSet& zones, bool updatePosition = true); + bool Unsnap(HWND window); + + void ShowZones(const ZoneIndexSet& highlight, HWND draggedWindow = nullptr); + void HideZones(); void FlashZones(); void CycleWindows(HWND window, bool reverse); @@ -79,6 +72,6 @@ private: const FancyZonesDataTypes::WorkAreaId m_uniqueId; HWND m_window{}; // Hidden tool window used to represent current monitor desktop work area. std::unique_ptr m_layout; - std::unique_ptr m_layoutWindows; + LayoutAssignedWindows m_layoutWindows{}; std::unique_ptr m_zonesOverlay; }; diff --git a/src/modules/fancyzones/FancyZonesLib/trace.cpp b/src/modules/fancyzones/FancyZonesLib/trace.cpp index 00cbfbd064..0fce61584f 100644 --- a/src/modules/fancyzones/FancyZonesLib/trace.cpp +++ b/src/modules/fancyzones/FancyZonesLib/trace.cpp @@ -90,17 +90,17 @@ struct ZoneSetInfo }; -ZoneSetInfo GetZoneSetInfo(_In_opt_ Layout* layout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept +ZoneSetInfo GetZoneSetInfo(_In_opt_ Layout* layout, const LayoutAssignedWindows& layoutWindows) noexcept { ZoneSetInfo info; - if (layout && layoutWindows) + if (layout) { auto zones = layout->Zones(); info.NumberOfZones = zones.size(); info.NumberOfWindows = 0; for (int i = 0; i < static_cast(zones.size()); i++) { - if (!layoutWindows->IsZoneEmpty(i)) + if (!layoutWindows.IsZoneEmpty(i)) { info.NumberOfWindows++; } @@ -258,7 +258,7 @@ void Trace::FancyZones::QuickLayoutSwitched(bool shortcutUsed) noexcept TraceLoggingBoolean(shortcutUsed, QuickLayoutSwitchedWithShortcutUsed)); } -void Trace::FancyZones::SnapNewWindowIntoZone(Layout* activeLayout, LayoutAssignedWindows* layoutWindows) noexcept +void Trace::FancyZones::SnapNewWindowIntoZone(Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept { auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows); TraceLoggingWrite( @@ -271,7 +271,7 @@ void Trace::FancyZones::SnapNewWindowIntoZone(Layout* activeLayout, LayoutAssign TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey)); } -void Trace::FancyZones::KeyboardSnapWindowToZone(Layout* activeLayout, LayoutAssignedWindows* layoutWindows) noexcept +void Trace::FancyZones::KeyboardSnapWindowToZone(Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept { auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows); TraceLoggingWrite( @@ -356,7 +356,7 @@ void Trace::WorkArea::KeyUp(WPARAM wParam) noexcept TraceLoggingValue(wParam, KeyboardValueKey)); } -void Trace::WorkArea::MoveOrResizeStarted(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept +void Trace::WorkArea::MoveOrResizeStarted(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept { auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows); TraceLoggingWrite( @@ -369,7 +369,7 @@ void Trace::WorkArea::MoveOrResizeStarted(_In_opt_ Layout* activeLayout, _In_opt TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey)); } -void Trace::WorkArea::MoveOrResizeEnd(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept +void Trace::WorkArea::MoveOrResizeEnd(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept { auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows); TraceLoggingWrite( @@ -382,7 +382,7 @@ void Trace::WorkArea::MoveOrResizeEnd(_In_opt_ Layout* activeLayout, _In_opt_ La TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey)); } -void Trace::WorkArea::CycleActiveZoneSet(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows, InputMode mode) noexcept +void Trace::WorkArea::CycleActiveZoneSet(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows, InputMode mode) noexcept { auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows); TraceLoggingWrite( diff --git a/src/modules/fancyzones/FancyZonesLib/trace.h b/src/modules/fancyzones/FancyZonesLib/trace.h index 9555f5563d..3bef5cc8c1 100644 --- a/src/modules/fancyzones/FancyZonesLib/trace.h +++ b/src/modules/fancyzones/FancyZonesLib/trace.h @@ -19,8 +19,8 @@ public: static void EditorLaunched(int value) noexcept; static void Error(const DWORD errorCode, std::wstring errorMessage, std::wstring methodName) noexcept; static void QuickLayoutSwitched(bool shortcutUsed) noexcept; - static void SnapNewWindowIntoZone(Layout* activeLayout, LayoutAssignedWindows* layoutWindows) noexcept; - static void KeyboardSnapWindowToZone(Layout* activeLayout, LayoutAssignedWindows* layoutWindows) noexcept; + static void SnapNewWindowIntoZone(Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept; + static void KeyboardSnapWindowToZone(Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept; }; static void SettingsTelemetry(const Settings& settings) noexcept; @@ -36,8 +36,8 @@ public: }; static void KeyUp(WPARAM wparam) noexcept; - static void MoveOrResizeStarted(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept; - static void MoveOrResizeEnd(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept; - static void CycleActiveZoneSet(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows, InputMode mode) noexcept; + static void MoveOrResizeStarted(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept; + static void MoveOrResizeEnd(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept; + static void CycleActiveZoneSet(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows, InputMode mode) noexcept; }; }; diff --git a/src/modules/fancyzones/FancyZonesLib/util.cpp b/src/modules/fancyzones/FancyZonesLib/util.cpp index 4b4ccc40cb..6bc1a2aee2 100644 --- a/src/modules/fancyzones/FancyZonesLib/util.cpp +++ b/src/modules/fancyzones/FancyZonesLib/util.cpp @@ -123,6 +123,15 @@ namespace FancyZonesUtils monitorInfo = std::move(sortedMonitorInfo); } + std::vector GetMonitorsOrdered() + { + auto monitors = FancyZonesUtils::GetAllMonitorRects<&MONITORINFOEX::rcWork>(); + FancyZonesUtils::OrderMonitors(monitors); + std::vector output; + std::transform(std::begin(monitors), std::end(monitors), std::back_inserter(output), [](const auto& info) { return info.first; }); + return output; + } + bool IsValidGuid(const std::wstring& str) { GUID id; diff --git a/src/modules/fancyzones/FancyZonesLib/util.h b/src/modules/fancyzones/FancyZonesLib/util.h index 41d1a9b4d8..98105a1af0 100644 --- a/src/modules/fancyzones/FancyZonesLib/util.h +++ b/src/modules/fancyzones/FancyZonesLib/util.h @@ -143,13 +143,12 @@ namespace FancyZonesUtils } template - RECT GetAllMonitorsCombinedRect() + RECT GetMonitorsCombinedRect(const std::vector>& monitorRects) { - auto allMonitors = GetAllMonitorRects(); bool empty = true; RECT result{ 0, 0, 0, 0 }; - for (auto& [monitor, rect] : allMonitors) + for (auto& [monitor, rect] : monitorRects) { if (empty) { @@ -168,6 +167,13 @@ namespace FancyZonesUtils return result; } + template + RECT GetAllMonitorsCombinedRect() + { + auto allMonitors = GetAllMonitorRects(); + return GetMonitorsCombinedRect(allMonitors); + } + constexpr RECT PrepareRectForCycling(RECT windowRect, RECT workAreaRect, DWORD vkCode) noexcept { LONG deltaX = 0, deltaY = 0; @@ -196,6 +202,7 @@ namespace FancyZonesUtils UINT GetDpiForMonitor(HMONITOR monitor) noexcept; void OrderMonitors(std::vector>& monitorInfo); + std::vector GetMonitorsOrdered(); bool IsValidGuid(const std::wstring& str); std::optional GuidFromString(const std::wstring& str) noexcept; diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/AppZoneHistoryTests.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/AppZoneHistoryTests.Spec.cpp index f1365f2079..c3e954fb00 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/AppZoneHistoryTests.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/AppZoneHistoryTests.Spec.cpp @@ -207,22 +207,22 @@ namespace FancyZonesUnitTests TEST_METHOD (AppLastZoneInvalidWindow) { - const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}"; + const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value(); const FancyZonesDataTypes::WorkAreaId workAreaId{ .monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } }, .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value() }; const auto window = Mocks::Window(); - Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetId)); + Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutId)); const int expectedZoneIndex = 1; - Assert::IsFalse(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, zoneSetId, { expectedZoneIndex })); + Assert::IsFalse(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, layoutId, { expectedZoneIndex })); } TEST_METHOD (AppLastZoneNullWindow) { - const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}"; + const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value(); const FancyZonesDataTypes::WorkAreaId workAreaId{ .monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } }, .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value() @@ -230,12 +230,12 @@ namespace FancyZonesUnitTests const auto window = nullptr; const int expectedZoneIndex = 1; - Assert::IsFalse(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, zoneSetId, { expectedZoneIndex })); + Assert::IsFalse(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, layoutId, { expectedZoneIndex })); } TEST_METHOD (AppLastdeviceIdTest) { - const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}"; + const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value(); const FancyZonesDataTypes::WorkAreaId workAreaId1{ .monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } }, .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value() @@ -247,15 +247,15 @@ namespace FancyZonesUnitTests const auto window = Mocks::WindowCreate(m_hInst); const int expectedZoneIndex = 10; - Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId1, zoneSetId, { expectedZoneIndex })); - Assert::IsTrue(std::vector{ expectedZoneIndex } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId1, zoneSetId)); - Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId2, zoneSetId)); + Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId1, layoutId, { expectedZoneIndex })); + Assert::IsTrue(std::vector{ expectedZoneIndex } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId1, layoutId)); + Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId2, layoutId)); } TEST_METHOD (AppLastZoneSetIdTest) { - const std::wstring zoneSetId1 = L"{B7A1F5A9-9DC2-4505-84AB-993253839093}"; - const std::wstring zoneSetId2 = L"{B7A1F5A9-9DC2-4505-84AB-993253839094}"; + const auto layoutId1 = FancyZonesUtils::GuidFromString(L"{B7A1F5A9-9DC2-4505-84AB-993253839093}").value(); + const auto layoutId2 = FancyZonesUtils::GuidFromString(L"{B7A1F5A9-9DC2-4505-84AB-993253839094}").value(); const FancyZonesDataTypes::WorkAreaId workAreaId{ .monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } }, .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value() @@ -263,56 +263,56 @@ namespace FancyZonesUnitTests const auto window = Mocks::WindowCreate(m_hInst); const int expectedZoneIndex = 10; - Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, zoneSetId1, { expectedZoneIndex })); - Assert::IsTrue(std::vector{ expectedZoneIndex } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetId1)); - Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetId2)); + Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, layoutId1, { expectedZoneIndex })); + Assert::IsTrue(std::vector{ expectedZoneIndex } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutId1)); + Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutId2)); } TEST_METHOD (AppLastZoneRemoveWindow) { - const std::wstring zoneSetId = L"{B7A1F5A9-9DC2-4505-84AB-993253839093}"; + const auto layoutId = FancyZonesUtils::GuidFromString(L"{B7A1F5A9-9DC2-4505-84AB-993253839093}").value(); const FancyZonesDataTypes::WorkAreaId workAreaId{ .monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } }, .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value() }; const auto window = Mocks::WindowCreate(m_hInst); - Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, zoneSetId, { 1 })); - Assert::IsTrue(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, zoneSetId)); - Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetId)); + Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, layoutId, { 1 })); + Assert::IsTrue(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, layoutId)); + Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutId)); } TEST_METHOD (AppLastZoneRemoveUnknownWindow) { - const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}"; + const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value(); const FancyZonesDataTypes::WorkAreaId workAreaId{ .monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } }, .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value() }; const auto window = Mocks::WindowCreate(m_hInst); - Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, zoneSetId)); - Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetId)); + Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, layoutId)); + Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutId)); } TEST_METHOD (AppLastZoneRemoveUnknownZoneSetId) { - const std::wstring zoneSetIdToInsert = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}"; - const std::wstring zoneSetIdToRemove = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F1}"; + const auto layoutIdToInsert = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value(); + const auto layoutIdToRemove = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F1}").value(); const FancyZonesDataTypes::WorkAreaId workAreaId{ .monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } }, .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value() }; const auto window = Mocks::WindowCreate(m_hInst); - Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, zoneSetIdToInsert, { 1 })); - Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, zoneSetIdToRemove)); - Assert::IsTrue(std::vector{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetIdToInsert)); + Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, layoutIdToInsert, { 1 })); + Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, layoutIdToRemove)); + Assert::IsTrue(std::vector{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutIdToInsert)); } TEST_METHOD (AppLastZoneRemoveUnknownWindowId) { - const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}"; + const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value(); const FancyZonesDataTypes::WorkAreaId workAreaIdToInsert{ .monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } }, .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value() @@ -323,20 +323,20 @@ namespace FancyZonesUnitTests }; const auto window = Mocks::WindowCreate(m_hInst); - Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaIdToInsert, zoneSetId, { 1 })); - Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaIdToRemove, zoneSetId)); - Assert::IsTrue(std::vector{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaIdToInsert, zoneSetId)); + Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaIdToInsert, layoutId, { 1 })); + Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaIdToRemove, layoutId)); + Assert::IsTrue(std::vector{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaIdToInsert, layoutId)); } TEST_METHOD (AppLastZoneRemoveNullWindow) { - const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}"; + const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value(); const FancyZonesDataTypes::WorkAreaId workAreaId{ .monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } }, .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value() }; - Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(nullptr, workAreaId, zoneSetId)); + Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(nullptr, workAreaId, layoutId)); } }; } diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/Layout.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/Layout.Spec.cpp index fe3c262415..dff898bd7b 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/Layout.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/Layout.Spec.cpp @@ -140,10 +140,9 @@ namespace FancyZonesUnitTests data.zoneCount = 4; // prepare settings - PowerToysSettings::PowerToyValues values(NonLocalizable::ModuleKey, NonLocalizable::ModuleKey); - values.add_property(L"fancyzones_overlappingZonesAlgorithm", json::value(static_cast(OverlappingZonesAlgorithm::Smallest))); - json::to_file(FancyZonesSettings::GetSettingsFileName(), values.get_raw_json()); - FancyZonesSettings::instance().LoadSettings(); + auto settings = FancyZonesSettings::settings(); + settings.overlappingZonesAlgorithm = OverlappingZonesAlgorithm::Smallest; + FancyZonesSettings::instance().SetSettings(settings); auto layout = std::make_unique(data); layout->Init(RECT{ 0, 0, 1920, 1080 }, Mocks::Monitor()); @@ -168,10 +167,9 @@ namespace FancyZonesUnitTests data.zoneCount = 4; // prepare settings - PowerToysSettings::PowerToyValues values(NonLocalizable::ModuleKey, NonLocalizable::ModuleKey); - values.add_property(L"fancyzones_overlappingZonesAlgorithm", json::value(static_cast(OverlappingZonesAlgorithm::Smallest))); - json::to_file(FancyZonesSettings::GetSettingsFileName(), values.get_raw_json()); - FancyZonesSettings::instance().LoadSettings(); + auto settings = FancyZonesSettings::settings(); + settings.overlappingZonesAlgorithm = OverlappingZonesAlgorithm::Smallest; + FancyZonesSettings::instance().SetSettings(settings); auto layout = std::make_unique(data); layout->Init(RECT{ 0, 0, 1920, 1080 }, Mocks::Monitor()); diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj index f00a1e8c5d..baa9cfef4b 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj @@ -55,6 +55,7 @@ + diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters index 799ea74a4a..c8679dd8dc 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters @@ -63,6 +63,9 @@ Source Files + + Source Files + diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/WindowKeyboardSnap.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/WindowKeyboardSnap.Spec.cpp new file mode 100644 index 0000000000..2b18523ea6 --- /dev/null +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/WindowKeyboardSnap.Spec.cpp @@ -0,0 +1,1452 @@ +#include "pch.h" + +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#include "Util.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +namespace FancyZonesUnitTests +{ + TEST_CLASS (WindowKeyboardSnap_ByIndex_UnitTests) + { + const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}"; + const std::wstring m_layoutIdStr = L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}"; + constexpr GUID layoutId() + { + return FancyZonesUtils::GuidFromString(m_layoutIdStr).value(); + } + + const HMONITOR m_monitor = Mocks::Monitor(); + + const FancyZonesDataTypes::WorkAreaId m_workAreaId = { + .monitorId = { + .monitor = m_monitor, + .deviceId = { + .id = L"device-id-1", + .instanceId = L"5&10a58c63&0&UID16777488", + .number = 1, + }, + .serialNumber = L"serial-number-1" }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() }; + + std::unordered_map> m_workAreaMap; + HINSTANCE m_hInst{}; + + json::JsonObject WorkAreaLayoutObject(const FancyZonesDataTypes::WorkAreaId& workAreaId) + { + json::JsonObject layout{}; + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(m_layoutIdStr)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid))); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20)); + + json::JsonObject workAreaIdObj{}; + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(workAreaId.monitorId.deviceId.id)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(workAreaId.monitorId.deviceId.instanceId)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(workAreaId.monitorId.serialNumber)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(workAreaId.monitorId.deviceId.number)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr)); + + json::JsonObject obj{}; + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaIdObj); + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout); + + return obj; + } + + void PrepareGridLayout() + { + json::JsonObject root{}; + json::JsonArray layoutsArray{}; + + layoutsArray.Append(WorkAreaLayoutObject(m_workAreaId)); + + root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray); + json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root); + + AppliedLayouts::instance().LoadData(); + } + + TEST_METHOD_INITIALIZE (Init) + { + AppZoneHistory::instance().LoadData(); + PrepareGridLayout(); + FancyZonesSettings::instance().SetSettings({ .overrideSnapHotkeys = true, .moveWindowsBasedOnPosition = false }); + + auto workArea = WorkArea::Create(m_hInst, m_workAreaId, {}, FancyZonesUtils::Rect{ RECT(0, 0, 1920, 1080) }); + m_workAreaMap.insert({ m_monitor, std::move(workArea) }); + } + + TEST_METHOD_CLEANUP(CleanUp) + { + std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName()); + std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName()); + } + + TEST_METHOD (Snap_Left) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_LEFT, m_workAreaMap, { m_monitor })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 3 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (Snap_Right) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_RIGHT, m_workAreaMap, { m_monitor })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (MoveToNextZone) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 1 }, true); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_RIGHT, m_workAreaMap, { m_monitor })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (MoveToPrevZone) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 1 }, true); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_LEFT, m_workAreaMap, { m_monitor })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (MoveNext_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 3 }, true); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = true; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_RIGHT, m_workAreaMap, { m_monitor })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (MovePrev_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = true; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_LEFT, m_workAreaMap, { m_monitor })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 3 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (MoveNext_NoCycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 3 }, true); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = false; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsFalse(windowKeyboardSnap.Snap(window, m_monitor, VK_RIGHT, m_workAreaMap, { m_monitor })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 3 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (MovePrev_NoCycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = false; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsFalse(windowKeyboardSnap.Snap(window, m_monitor, VK_LEFT, m_workAreaMap, { m_monitor })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + }; + + TEST_CLASS (WindowKeyboardSnap_MoveAcrossMonitors_ByIndex_UnitTests) + { + const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}"; + const std::vector m_monitors = { Mocks::Monitor(), Mocks::Monitor() }; + + const std::vector m_workAreaIds = { + FancyZonesDataTypes::WorkAreaId{ + .monitorId = { + .monitor = m_monitors[0], + .deviceId = { + .id = L"device-id-1", + .instanceId = L"5&10a58c63&0&UID16777488", + .number = 1, + }, + .serialNumber = L"serial-number-1" }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() }, + FancyZonesDataTypes::WorkAreaId{ + .monitorId = { + .monitor = m_monitors[1], + .deviceId = { + .id = L"device-id-2", + .instanceId = L"5&10a58c63&0&UID16777489", + .number = 2, + }, + .serialNumber = L"serial-number-2" }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() } + }; + + std::unordered_map> m_workAreaMap; + HINSTANCE m_hInst{}; + + json::JsonObject WorkAreaLayoutObject(const FancyZonesDataTypes::WorkAreaId& workAreaId) + { + json::JsonObject layout{}; + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}")); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid))); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20)); + + json::JsonObject workAreaIdObj{}; + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(workAreaId.monitorId.deviceId.id)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(workAreaId.monitorId.deviceId.instanceId)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(workAreaId.monitorId.serialNumber)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(workAreaId.monitorId.deviceId.number)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr)); + + json::JsonObject obj{}; + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaIdObj); + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout); + + return obj; + } + + void PrepareGridLayout() + { + json::JsonObject root{}; + json::JsonArray layoutsArray{}; + + for (const auto& workAreaId : m_workAreaIds) + { + layoutsArray.Append(WorkAreaLayoutObject(workAreaId)); + } + + root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray); + json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root); + + AppliedLayouts::instance().LoadData(); + } + + TEST_METHOD_INITIALIZE(Init) + { + AppZoneHistory::instance().LoadData(); + PrepareGridLayout(); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = true; + FancyZonesSettings::instance().SetSettings(settings); + + auto workArea1 = WorkArea::Create(m_hInst, m_workAreaIds[0], {}, FancyZonesUtils::Rect{ RECT(0, 0, 1920, 1080) }); + m_workAreaMap.insert({ m_monitors[0], std::move(workArea1) }); + + auto workArea2 = WorkArea::Create(m_hInst, m_workAreaIds[1], {}, FancyZonesUtils::Rect{ RECT(1920, 0, 3840, 1080) }); + m_workAreaMap.insert({ m_monitors[1], std::move(workArea2) }); + } + + TEST_METHOD_CLEANUP(CleanUp) + { + std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName()); + std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName()); + } + + TEST_METHOD (Snap_Left) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitors[0], VK_LEFT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindows = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Right) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitors[0], VK_RIGHT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindows = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Left_NextWorkArea) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap[m_monitors[1]]->Snap(window, { 0 }); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitors[1], VK_LEFT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[1])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Right_NextWorkArea) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitors[0])->Snap(window, { 3 }); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitors[0], VK_RIGHT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[1])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Left_NextWorkArea_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap[m_monitors[0]]->Snap(window, { 0 }); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitors[0], VK_LEFT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[1])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Right_NextWorkArea_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitors[1])->Snap(window, { 3 }); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitors[1], VK_RIGHT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[1])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Left_NoCycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap[m_monitors[0]]->Snap(window, { 0 }); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = false; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsFalse(windowKeyboardSnap.Snap(window, m_monitors[0], VK_LEFT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[1])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Right_NoCycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitors[1])->Snap(window, { 3 }); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = false; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsFalse(windowKeyboardSnap.Snap(window, m_monitors[1], VK_RIGHT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[1])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + }; + + TEST_CLASS (WindowKeyboardSnap_ByPosition_UnitTests) + { + const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}"; + const std::wstring m_layoutIdStr = L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}"; + constexpr GUID layoutId() + { + return FancyZonesUtils::GuidFromString(m_layoutIdStr).value(); + } + + const HMONITOR m_monitor = Mocks::Monitor(); + const RECT m_rect = RECT{ 0, 0, 1920, 1080 }; + + const FancyZonesDataTypes::WorkAreaId m_workAreaId = { + .monitorId = { + .monitor = m_monitor, + .deviceId = { + .id = L"device-id-1", + .instanceId = L"5&10a58c63&0&UID16777488", + .number = 1, + }, + .serialNumber = L"serial-number-1" }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() + }; + + std::unordered_map> m_workAreaMap; + HINSTANCE m_hInst{}; + + json::JsonObject WorkAreaLayoutObject(const FancyZonesDataTypes::WorkAreaId& workAreaId) + { + json::JsonObject layout{}; + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(m_layoutIdStr)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid))); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20)); + + json::JsonObject workAreaIdObj{}; + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(workAreaId.monitorId.deviceId.id)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(workAreaId.monitorId.deviceId.instanceId)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(workAreaId.monitorId.serialNumber)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(workAreaId.monitorId.deviceId.number)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr)); + + json::JsonObject obj{}; + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaIdObj); + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout); + + return obj; + } + + void PrepareGridLayout() + { + json::JsonObject root{}; + json::JsonArray layoutsArray{}; + + layoutsArray.Append(WorkAreaLayoutObject(m_workAreaId)); + + root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray); + json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root); + + AppliedLayouts::instance().LoadData(); + } + + // using zone rects instead of the actual window rect + // otherwise we'll need to wait after snapping, for the window to be resized + RECT GetZoneRect(const WorkArea* workArea, ZoneIndex index) + { + auto rect = workArea->GetLayout()->Zones().at(index).GetZoneRect(); + auto workAreaRect = workArea->GetWorkAreaRect(); + return rect; + } + + TEST_METHOD_INITIALIZE(Init) + { + AppZoneHistory::instance().LoadData(); + PrepareGridLayout(); + + auto workArea = WorkArea::Create(m_hInst, m_workAreaId, {}, FancyZonesUtils::Rect{ m_rect }); + m_workAreaMap.insert({ m_monitor, std::move(workArea) }); + } + + TEST_METHOD_CLEANUP(CleanUp) + { + std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName()); + std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName()); + } + + TEST_METHOD (Snap_Left) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + RECT windowRect; + Assert::IsTrue(GetWindowRect(window, &windowRect)); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_LEFT, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::AreEqual((size_t)1, layoutWindows.GetZoneIndexSetFromWindow(window).size()); + Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()).size()); + } + + TEST_METHOD (Snap_Right) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + RECT windowRect; + Assert::IsTrue(GetWindowRect(window, &windowRect)); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap, { { m_monitor, m_rect } })); + + Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()).size()); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::AreEqual((size_t)1, layoutWindows.GetZoneIndexSetFromWindow(window).size()); + } + + TEST_METHOD (Snap_Up) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + RECT windowRect; + Assert::IsTrue(GetWindowRect(window, &windowRect)); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_UP, m_workAreaMap, { { m_monitor, m_rect } })); + + Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()).size()); + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::AreEqual((size_t)1, layoutWindows.GetZoneIndexSetFromWindow(window).size()); + } + + TEST_METHOD (Snap_Down) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + RECT windowRect; + Assert::IsTrue(GetWindowRect(window, &windowRect)); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_DOWN, m_workAreaMap, { { m_monitor, m_rect } })); + + Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()).size()); + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::AreEqual((size_t)1, layoutWindows.GetZoneIndexSetFromWindow(window).size()); + } + + TEST_METHOD (Move_Left) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 1 }, true); + RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 1); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_LEFT, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (Move_Right) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true); + RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 0); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (Move_Up) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 2 }, true); + RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 2); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_UP, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (Move_Down) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true); + RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 0); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_DOWN, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (MoveLeft_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true); + RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 0); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = true; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_LEFT, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (MoveRight_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 1 }, true); + RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 1); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = true; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (MoveUp_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true); + RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 0); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = true; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_UP, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (MoveDown_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 2 }, true); + RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 2); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = true; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_DOWN, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + }; + + TEST_CLASS (WindowKeyboardSnap_MoveAcrossMonitors_ByPosition_UnitTests) + { + const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}"; + const std::vector> m_monitors = { + { Mocks::Monitor(), RECT{ 0, 0, 1920, 1080 } }, // left + { Mocks::Monitor(), RECT{ 1920, 0, 3840, 1080 } }, // right + { Mocks::Monitor(), RECT{ 0, -1080, 1920, 0 } } // top + }; + + const std::vector m_workAreaIds = { + FancyZonesDataTypes::WorkAreaId{ + .monitorId = { + .monitor = m_monitors[0].first, + .deviceId = { + .id = L"device-id-left", + .instanceId = L"5&10a58c63&0&UID16777488", + .number = 1, + }, + .serialNumber = L"serial-number-left" }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() }, + FancyZonesDataTypes::WorkAreaId{ + .monitorId = { + .monitor = m_monitors[1].first, + .deviceId = { + .id = L"device-id-right", + .instanceId = L"5&10a58c63&0&UID16777489", + .number = 2, + }, + .serialNumber = L"serial-number-right" }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() }, + FancyZonesDataTypes::WorkAreaId{ + .monitorId = { + .monitor = m_monitors[2].first, + .deviceId = { + .id = L"device-id-top", + .instanceId = L"5&10a58c63&0&UID16777487", + .number = 3, + }, + .serialNumber = L"serial-number-top" }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() } + }; + + std::unordered_map> m_workAreaMap; + HINSTANCE m_hInst{}; + + json::JsonObject WorkAreaLayoutObject(const FancyZonesDataTypes::WorkAreaId& workAreaId) + { + json::JsonObject layout{}; + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}")); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid))); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20)); + + json::JsonObject workAreaIdObj{}; + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(workAreaId.monitorId.deviceId.id)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(workAreaId.monitorId.deviceId.instanceId)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(workAreaId.monitorId.serialNumber)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(workAreaId.monitorId.deviceId.number)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr)); + + json::JsonObject obj{}; + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaIdObj); + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout); + + return obj; + } + + void PrepareGridLayout() + { + json::JsonObject root{}; + json::JsonArray layoutsArray{}; + + for (const auto& workAreaId : m_workAreaIds) + { + layoutsArray.Append(WorkAreaLayoutObject(workAreaId)); + } + + root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray); + json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root); + + AppliedLayouts::instance().LoadData(); + } + + RECT GetAdjustedZoneRect(const WorkArea* workArea, ZoneIndex index) + { + auto rect = workArea->GetLayout()->Zones().at(index).GetZoneRect(); + auto workAreaRect = workArea->GetWorkAreaRect(); + rect.left += workAreaRect.left(); + rect.right += workAreaRect.left(); + rect.top += workAreaRect.top(); + rect.bottom += workAreaRect.top(); + return rect; + } + + TEST_METHOD_INITIALIZE(Init) + { + AppZoneHistory::instance().LoadData(); + PrepareGridLayout(); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = true; + FancyZonesSettings::instance().SetSettings(settings); + + auto workArea1 = WorkArea::Create(m_hInst, m_workAreaIds[0], {}, FancyZonesUtils::Rect{ m_monitors[0].second }); + m_workAreaMap.insert({ m_monitors[0].first, std::move(workArea1) }); + + auto workArea2 = WorkArea::Create(m_hInst, m_workAreaIds[1], {}, FancyZonesUtils::Rect{ m_monitors[1].second }); + m_workAreaMap.insert({ m_monitors[1].first, std::move(workArea2) }); + + auto workArea3 = WorkArea::Create(m_hInst, m_workAreaIds[2], {}, FancyZonesUtils::Rect{ m_monitors[2].second }); + m_workAreaMap.insert({ m_monitors[2].first, std::move(workArea3) }); + } + + TEST_METHOD_CLEANUP(CleanUp) + { + std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName()); + std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName()); + } + + TEST_METHOD (Snap_Left) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + RECT windowRect; + Assert::IsTrue(GetWindowRect(window, &windowRect)); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_LEFT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + } + + TEST_METHOD (Snap_Right) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + RECT windowRect; + Assert::IsTrue(GetWindowRect(window, &windowRect)); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_RIGHT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + } + + TEST_METHOD (Snap_Up) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + RECT windowRect; + Assert::IsTrue(GetWindowRect(window, &windowRect)); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_UP, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + } + + TEST_METHOD (Snap_Down) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + RECT windowRect; + Assert::IsTrue(GetWindowRect(window, &windowRect)); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_DOWN, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindows = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows(); + Assert::AreEqual((size_t)1, layoutWindows.GetZoneIndexSetFromWindow(window).size()); + } + + TEST_METHOD (Snap_Left_NextWorkArea) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const ZoneIndex initialZoneIndex = 0; + m_workAreaMap[m_monitors[1].first]->Snap(window, { initialZoneIndex }); + RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[1].first].get(), initialZoneIndex); // use zone rect instead of the actual position + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[1].first, VK_LEFT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[1].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Right_NextWorkArea) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const ZoneIndex initialZoneIndex = 3; + m_workAreaMap[m_monitors[0].first]->Snap(window, { initialZoneIndex }); + RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[0].first].get(), initialZoneIndex); // use zone rect instead of the actual position + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_RIGHT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[1].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Up_NextWorkArea) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const ZoneIndex initialZoneIndex = 0; + m_workAreaMap[m_monitors[0].first]->Snap(window, { initialZoneIndex }); + RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[0].first].get(), initialZoneIndex); // use zone rect instead of the actual position + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_UP, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[2].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Down_NextWorkArea) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const ZoneIndex initialZoneIndex = 2; + m_workAreaMap[m_monitors[2].first]->Snap(window, { initialZoneIndex }); + RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[2].first].get(), initialZoneIndex); // use zone rect instead of the actual position + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[2].first, VK_DOWN, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[2].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Left_NextWorkArea_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const ZoneIndex initialZoneIndex = 0; + m_workAreaMap[m_monitors[0].first]->Snap(window, { initialZoneIndex }); + RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[0].first].get(), initialZoneIndex); // use zone rect instead of the actual position + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_LEFT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[1].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Right_NextWorkArea_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const ZoneIndex initialZoneIndex = 3; + m_workAreaMap[m_monitors[1].first]->Snap(window, { initialZoneIndex }); + RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[1].first].get(), initialZoneIndex); // use zone rect instead of the actual position + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[1].first, VK_RIGHT, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[1].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Up_NextWorkArea_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const ZoneIndex initialZoneIndex = 0; + m_workAreaMap[m_monitors[2].first]->Snap(window, { initialZoneIndex }); + RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[2].first].get(), initialZoneIndex); // use zone rect instead of the actual position + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[2].first, VK_UP, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[2].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (Snap_Down_NextWorkArea_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const ZoneIndex initialZoneIndex = 2; + m_workAreaMap[m_monitors[0].first]->Snap(window, { initialZoneIndex }); + RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[0].first].get(), initialZoneIndex); // use zone rect instead of the actual position + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_DOWN, m_workAreaMap, m_monitors)); + + const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); + Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); + + const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window)); + + const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[2].first)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window)); + } + }; + + TEST_CLASS (WindowKeyboardSnap_Extend_UnitTests) + { + const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}"; + const std::wstring m_layoutIdStr = L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}"; + constexpr GUID layoutId() + { + return FancyZonesUtils::GuidFromString(m_layoutIdStr).value(); + } + + const HMONITOR m_monitor = Mocks::Monitor(); + const FancyZonesDataTypes::WorkAreaId m_workAreaId = { + .monitorId = { + .monitor = m_monitor, + .deviceId = { + .id = L"device-id-1", + .instanceId = L"5&10a58c63&0&UID16777488", + .number = 1, + }, + .serialNumber = L"serial-number-1" }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() + }; + std::unordered_map> m_workAreaMap = {}; + HINSTANCE m_hInst{}; + + json::JsonObject WorkAreaLayoutObject(const FancyZonesDataTypes::WorkAreaId& workAreaId) + { + json::JsonObject layout{}; + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(m_layoutIdStr)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid))); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20)); + + json::JsonObject workAreaIdObj{}; + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(workAreaId.monitorId.deviceId.id)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(workAreaId.monitorId.deviceId.instanceId)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(workAreaId.monitorId.serialNumber)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(workAreaId.monitorId.deviceId.number)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr)); + + json::JsonObject obj{}; + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaIdObj); + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout); + + return obj; + } + + void PrepareGridLayout() + { + json::JsonObject root{}; + json::JsonArray layoutsArray{}; + + layoutsArray.Append(WorkAreaLayoutObject(m_workAreaId)); + + root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray); + json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root); + + AppliedLayouts::instance().LoadData(); + } + + RECT GetAdjustedZoneRect(const WorkArea* workArea, ZoneIndex index) + { + auto rect = workArea->GetLayout()->Zones().at(index).GetZoneRect(); + auto workAreaRect = workArea->GetWorkAreaRect(); + rect.left += workAreaRect.left(); + rect.right += workAreaRect.left(); + rect.top += workAreaRect.top(); + rect.bottom += workAreaRect.top(); + return rect; + } + + TEST_METHOD_INITIALIZE(Init) + { + AppZoneHistory::instance().LoadData(); + PrepareGridLayout(); + + auto workArea = WorkArea::Create(m_hInst, m_workAreaId, {}, FancyZonesUtils::Rect{ RECT(0, 0, 1920, 1080) }); + m_workAreaMap.insert({ m_monitor, std::move(workArea) }); + } + + TEST_METHOD_CLEANUP(CleanUp) + { + std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName()); + std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName()); + } + + TEST_METHOD(ExtendNonSnappedWindow) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + RECT windowRect = {10,10,150,150}; + Assert::IsTrue(SetWindowPos(window, nullptr, windowRect.left, windowRect.top, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, 0)); + + Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap)); + + const auto& layoutWindows = m_workAreaMap[m_monitor]->GetLayoutWindows(); + Assert::AreEqual((size_t)1, layoutWindows.GetZoneIndexSetFromWindow(window).size()); + Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaMap[m_monitor]->UniqueId(), m_workAreaMap[m_monitor]->GetLayoutId()).size()); + } + + TEST_METHOD (ExtendSnappedWindow) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const auto& workArea = m_workAreaMap[m_monitor]; + workArea->Snap(window, { 0 }); + RECT windowRect = GetAdjustedZoneRect(workArea.get(), 0); + + Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap)); + + const auto& layoutWindows = workArea->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0, 1 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0, 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId())); + } + + TEST_METHOD (ExtendLeft) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const auto& workArea = m_workAreaMap[m_monitor]; + workArea->Snap(window, { 1 }); + RECT windowRect = GetAdjustedZoneRect(workArea.get(), 1); + + Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_LEFT, m_workAreaMap)); + + const auto& layoutWindows = workArea->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0, 1 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0, 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId())); + } + + TEST_METHOD (ExtendRight) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const auto& workArea = m_workAreaMap[m_monitor]; + workArea->Snap(window, { 0 }); + RECT windowRect = GetAdjustedZoneRect(workArea.get(), 0); + + Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap)); + + const auto& layoutWindows = workArea->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0, 1 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0, 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId())); + } + + TEST_METHOD (ExtendUp) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const auto& workArea = m_workAreaMap[m_monitor]; + workArea->Snap(window, { 2 }); + RECT windowRect = GetAdjustedZoneRect(workArea.get(), 2); + + Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_UP, m_workAreaMap)); + + const auto& layoutWindows = workArea->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0, 2 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0, 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId())); + } + + TEST_METHOD (ExtendDown) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const auto& workArea = m_workAreaMap[m_monitor]; + workArea->Snap(window, { 0 }); + RECT windowRect = GetAdjustedZoneRect(workArea.get(), 0); + + Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_DOWN, m_workAreaMap)); + + const auto& layoutWindows = workArea->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0, 2 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0, 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId())); + } + + TEST_METHOD (ExtendAndRevert) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const auto& workArea = m_workAreaMap[m_monitor]; + workArea->Snap(window, { 0 }); + RECT windowRect = GetAdjustedZoneRect(workArea.get(), 0); + + Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap)); + Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_DOWN, m_workAreaMap)); + Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_LEFT, m_workAreaMap)); + Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_UP, m_workAreaMap)); + + const auto& layoutWindows = workArea->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId())); + } + }; + + TEST_CLASS(WindowKeyboardSnap_MultiMonitorMode_UnitTests) + { + const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}"; + const std::wstring m_layoutIdStr = L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}"; + constexpr GUID layoutId() + { + return FancyZonesUtils::GuidFromString(m_layoutIdStr).value(); + } + + HINSTANCE m_hInst{}; + const RECT m_rect = RECT{ 0, 0, 1920, 1080 }; + const HMONITOR m_monitor = nullptr; + const FancyZonesDataTypes::WorkAreaId m_workAreaId = { + .monitorId = { + .monitor = m_monitor, + .deviceId = { + .id = L"FancyZones", + .instanceId = L"MultiMonitorDevice", + .number = 0, + }, + .serialNumber = L"" }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() + }; + std::unordered_map> m_workAreaMap = {}; + + json::JsonObject WorkAreaLayoutObject(const FancyZonesDataTypes::WorkAreaId& workAreaId) + { + json::JsonObject layout{}; + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(m_layoutIdStr)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid))); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4)); + layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20)); + + json::JsonObject workAreaIdObj{}; + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(workAreaId.monitorId.deviceId.id)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(workAreaId.monitorId.deviceId.instanceId)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(workAreaId.monitorId.serialNumber)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(workAreaId.monitorId.deviceId.number)); + workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr)); + + json::JsonObject obj{}; + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaIdObj); + obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout); + + return obj; + } + + void PrepareGridLayout() + { + json::JsonObject root{}; + json::JsonArray layoutsArray{}; + + layoutsArray.Append(WorkAreaLayoutObject(m_workAreaId)); + + root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray); + json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root); + + AppliedLayouts::instance().LoadData(); + } + + // use zone rects instead of the actual window rect + // otherwise we'll need to wait after snapping, for the window to be resized + // important when snapping/extending by position + RECT GetZoneRect(const WorkArea* workArea, ZoneIndex index) + { + auto rect = workArea->GetLayout()->Zones().at(index).GetZoneRect(); + auto workAreaRect = workArea->GetWorkAreaRect(); + return rect; + } + + TEST_METHOD_INITIALIZE(Init) + { + AppZoneHistory::instance().LoadData(); + PrepareGridLayout(); + + auto workArea = WorkArea::Create(m_hInst, m_workAreaId, {}, FancyZonesUtils::Rect{ RECT(0, 0, 1920, 1080) }); + m_workAreaMap.insert({ m_monitor, std::move(workArea) }); + } + + TEST_METHOD_CLEANUP(CleanUp) + { + std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName()); + std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName()); + } + + TEST_METHOD(Snap_ByIndex) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_LEFT, m_workAreaMap, { m_monitor })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 3 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD(Move_ByIndex) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 1 }, true); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_RIGHT, m_workAreaMap, { m_monitor })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (Move_ByIndex_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 3 }, true); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = true; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_RIGHT, m_workAreaMap, { m_monitor })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (Snap_ByPosition) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + RECT windowRect; + Assert::IsTrue(GetWindowRect(window, &windowRect)); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_LEFT, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::AreEqual((size_t)1, layoutWindows.GetZoneIndexSetFromWindow(window).size()); + Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()).size()); + } + + TEST_METHOD (Move_ByPosition) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true); + RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 0); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (Move_ByPosition_Cycle) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true); + RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 0); + + auto settings = FancyZonesSettings::settings(); + settings.moveWindowAcrossMonitors = true; + FancyZonesSettings::instance().SetSettings(settings); + + Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_UP, m_workAreaMap, { { m_monitor, m_rect } })); + + const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId())); + } + + TEST_METHOD (Extend) + { + WindowKeyboardSnap windowKeyboardSnap; + const auto window = Mocks::WindowCreate(m_hInst); + const auto& workArea = m_workAreaMap[m_monitor]; + workArea->Snap(window, { 0 }); + RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 0); + + Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_DOWN, m_workAreaMap)); + + const auto& layoutWindows = workArea->GetLayoutWindows(); + Assert::IsTrue(ZoneIndexSet{ 0, 2 } == layoutWindows.GetZoneIndexSetFromWindow(window)); + Assert::IsTrue(ZoneIndexSet{ 0, 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId())); + } + }; +} \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkArea.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkArea.Spec.cpp index 99d5119cb6..c365a06c03 100644 --- a/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkArea.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkArea.Spec.cpp @@ -44,7 +44,6 @@ namespace FancyZonesUnitTests { std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName()); std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName()); - std::filesystem::remove(DefaultLayouts::DefaultLayoutsFileName()); } @@ -58,7 +57,6 @@ namespace FancyZonesUnitTests const auto& layout = workArea->GetLayout(); Assert::IsNotNull(layout.get()); - Assert::IsNotNull(workArea->GetLayoutWindows().get()); Assert::AreEqual(static_cast(defaultLayout.type), static_cast(layout->Type())); Assert::AreEqual(defaultLayout.zoneCount, static_cast(layout->Zones().size())); } @@ -73,7 +71,6 @@ namespace FancyZonesUnitTests const auto& layout = workArea->GetLayout(); Assert::IsNotNull(layout.get()); - Assert::IsNotNull(workArea->GetLayoutWindows().get()); Assert::AreEqual(static_cast(defaultLayout.type), static_cast(layout->Type())); Assert::AreEqual(defaultLayout.zoneCount, static_cast(layout->Zones().size())); } @@ -102,7 +99,6 @@ namespace FancyZonesUnitTests auto actualWorkArea = WorkArea::Create(m_hInst, m_workAreaId, parentUniqueId, m_workAreaRect); Assert::IsNotNull(actualWorkArea->GetLayout().get()); - Assert::IsNotNull(actualWorkArea->GetLayoutWindows().get()); Assert::IsTrue(AppliedLayouts::instance().GetAppliedLayoutMap().contains(m_workAreaId)); const auto& actualLayout = AppliedLayouts::instance().GetAppliedLayoutMap().at(m_workAreaId); @@ -179,428 +175,46 @@ namespace FancyZonesUnitTests } }; - TEST_CLASS (WorkAreaMoveWindowUnitTests) + TEST_CLASS (WorkAreaSnapUnitTests) { - const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}"; - const FancyZonesDataTypes::WorkAreaId m_workAreaId{ + HINSTANCE m_hInst{}; + const HMONITOR m_monitor = Mocks::Monitor(); + const FancyZonesUtils::Rect m_workAreaRect{ RECT(0, 0, 1920, 1080) }; + const FancyZonesDataTypes::WorkAreaId m_parentUniqueId = {}; + const FancyZonesDataTypes::WorkAreaId m_workAreaId = { .monitorId = { - .monitor = Mocks::Monitor(), - .deviceId = { - .id = L"DELA026", + .monitor = m_monitor, + .deviceId = { + .id = L"device-id-1", .instanceId = L"5&10a58c63&0&UID16777488", .number = 1, }, - .serialNumber = L"serial-number" - }, - .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() + .serialNumber = L"serial-number-1" }, + .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{310F2924-B587-4D87-97C2-90031BDBE3F1}").value() }; - FancyZonesDataTypes::WorkAreaId m_parentUniqueId; // default empty - - HINSTANCE m_hInst{}; - FancyZonesUtils::Rect m_workAreaRect{ RECT(0, 0, 1920, 1080) }; - - void PrepareEmptyLayout() - { - json::JsonObject root{}; - json::JsonArray layoutsArray{}; - - { - json::JsonObject layout{}; - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}")); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Blank))); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false)); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0)); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(0)); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(0)); - - json::JsonObject workAreaId{}; - workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(m_workAreaId.monitorId.deviceId.id)); - workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(m_workAreaId.monitorId.deviceId.instanceId)); - workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(m_workAreaId.monitorId.serialNumber)); - workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(m_workAreaId.monitorId.deviceId.number)); - workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr)); - - json::JsonObject obj{}; - obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaId); - obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout); - - layoutsArray.Append(obj); - } - - root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray); - json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root); - - AppliedLayouts::instance().LoadData(); - } - - void PrepareGridLayout() - { - json::JsonObject root{}; - json::JsonArray layoutsArray{}; - - { - json::JsonObject layout{}; - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}")); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid))); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false)); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0)); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4)); - layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20)); - - json::JsonObject workAreaId{}; - workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(m_workAreaId.monitorId.deviceId.id)); - workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(m_workAreaId.monitorId.deviceId.instanceId)); - workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(m_workAreaId.monitorId.serialNumber)); - workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(m_workAreaId.monitorId.deviceId.number)); - workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr)); - - json::JsonObject obj{}; - obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaId); - obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout); - - layoutsArray.Append(obj); - } - - root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray); - json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root); - - AppliedLayouts::instance().LoadData(); - } - TEST_METHOD_INITIALIZE(Init) noexcept { AppZoneHistory::instance().LoadData(); - AppliedLayouts::instance().LoadData(); } TEST_METHOD_CLEANUP(CleanUp) noexcept { std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName()); - std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName()); - } - - TEST_METHOD (EmptyZonesMoveLeftByIndex) - { - // prepare - PrepareEmptyLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - - // test - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)0, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{} == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (EmptyZonesRightByIndex) - { - // prepare - PrepareEmptyLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - - // test - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)0, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{} == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveLeftNonAppliedWindowByIndex) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - - // test - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveRightNonAppliedWindowByIndex) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - - // test - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveAppliedWindowByIndex) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - const auto& layoutWindows = workArea->GetLayoutWindows(); - - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone - Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - - // test - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); - Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - } - - TEST_METHOD (MoveAppliedWindowByIndexCycle) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone - - // test - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ static_cast(workArea->GetLayout()->Zones().size() - 1) } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveAppliedWindowByIndexNoCycle) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone - - // test - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, false); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (EmptyZonesMoveByPosition) - { - // prepare - PrepareEmptyLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - - // test - workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_LEFT, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)0, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{} == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveLeftNonAppliedWindowByPosition) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - - // test - workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_LEFT, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveRightNonAppliedWindowByPosition) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - - // test - workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_RIGHT, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveAppliedWindowHorizontallyByPosition) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone - - // test - workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_RIGHT, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveAppliedWindowVerticallyByPosition) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone - - // test - workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_DOWN, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveAppliedWindowByPositionHorizontallyCycle) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone - - // test - workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_LEFT, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveAppliedWindowByPositionHorizontallyNoCycle) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone - - // test - workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_LEFT, false); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveAppliedWindowByPositionVerticallyCycle) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - const auto& layoutWindows = workArea->GetLayoutWindows(); - workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone - Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - - // test - workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_UP, true); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveAppliedWindowByPositionVerticallyNoCycle) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone - - // test - workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_UP, false); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (ExtendZoneHorizontally) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone - - // test - workArea->ExtendWindowByDirectionAndPosition(window, VK_RIGHT); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 0, 1 } == layoutWindows->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (ExtendZoneVertically) - { - // prepare - PrepareGridLayout(); - auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); - const auto window = Mocks::WindowCreate(m_hInst); - workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone - - // test - workArea->ExtendWindowByDirectionAndPosition(window, VK_DOWN); - - const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory(); - Assert::AreEqual((size_t)1, actualAppZoneHistory.size()); - - const auto& layoutWindows = workArea->GetLayoutWindows(); - Assert::IsTrue(ZoneIndexSet{ 0, 2 } == layoutWindows->GetZoneIndexSetFromWindow(window)); } TEST_METHOD (WhenWindowIsNotResizablePlacingItIntoTheZoneShouldNotResizeIt) { + LayoutData layout{ + .uuid = FancyZonesUtils::GuidFromString(L"{61FA9FC0-26A6-4B37-A834-491C148DFC58}").value(), + .type = FancyZonesDataTypes::ZoneSetLayoutType::Grid, + .showSpacing = false, + .spacing = 0, + .zoneCount = 4, + .sensitivityRadius = 20, + }; + AppliedLayouts::instance().ApplyLayout(m_workAreaId, layout); + const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); const auto window = Mocks::WindowCreate(m_hInst); @@ -610,7 +224,10 @@ namespace FancyZonesUnitTests SetWindowPos(window, nullptr, 150, 150, originalWidth, originalHeight, SWP_SHOWWINDOW); SetWindowLong(window, GWL_STYLE, GetWindowLong(window, GWL_STYLE) & ~WS_SIZEBOX); - workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true); + Assert::IsTrue(workArea->Snap(window, { 1 }, true)); + + // wait for the window to be resized + std::this_thread::sleep_for(std::chrono::milliseconds(10)); RECT inZoneRect; GetWindowRect(window, &inZoneRect); @@ -619,13 +236,46 @@ namespace FancyZonesUnitTests Assert::AreEqual(originalHeight, (int)inZoneRect.bottom - (int)inZoneRect.top); } + TEST_METHOD (WhenWindowIsResizablePlacingItIntoTheZoneShouldResizeIt) + { + LayoutData layout{ + .uuid = FancyZonesUtils::GuidFromString(L"{61FA9FC0-26A6-4B37-A834-491C148DFC58}").value(), + .type = FancyZonesDataTypes::ZoneSetLayoutType::Grid, + .showSpacing = false, + .spacing = 0, + .zoneCount = 4, + .sensitivityRadius = 20, + }; + AppliedLayouts::instance().ApplyLayout(m_workAreaId, layout); + + const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); + const auto window = Mocks::WindowCreate(m_hInst); + + SetWindowPos(window, nullptr, 150, 150, 450, 550, SWP_SHOWWINDOW); + + Assert::IsTrue(workArea->Snap(window, { 1 }, true)); + + // wait for the window to be resized + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + RECT zonedWindowRect; + GetWindowRect(window, &zonedWindowRect); + + RECT zoneRect = workArea->GetLayout()->Zones().at(1).GetZoneRect(); + + Assert::AreEqual(zoneRect.left, zonedWindowRect.left); + Assert::AreEqual(zoneRect.right, zonedWindowRect.right); + Assert::AreEqual(zoneRect.top, zonedWindowRect.top); + Assert::AreEqual(zoneRect.bottom, zonedWindowRect.bottom); + } + TEST_METHOD (SnapWindowPropertyTest) { const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); const auto window = Mocks::WindowCreate(m_hInst); const ZoneIndexSet expected = { 1, 2 }; - workArea->SnapWindow(window, expected); + Assert::IsTrue(workArea->Snap(window, expected)); const auto actual = FancyZonesWindowProperties::RetrieveZoneIndexProperty(window); Assert::AreEqual(expected.size(), actual.size()); @@ -641,7 +291,7 @@ namespace FancyZonesUnitTests const auto window = Mocks::WindowCreate(m_hInst); const ZoneIndexSet expected = { 1, 2 }; - workArea->SnapWindow(window, expected); + Assert::IsTrue(workArea->Snap(window, expected)); const auto processPath = get_process_path(window); const auto history = AppZoneHistory::instance().GetZoneHistory(processPath, m_workAreaId); @@ -654,13 +304,25 @@ namespace FancyZonesUnitTests } } + TEST_METHOD (SnapLayoutAssignedWindowsTest) + { + const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); + const auto window = Mocks::WindowCreate(m_hInst); + + const ZoneIndexSet expected = { 1, 2 }; + Assert::IsTrue(workArea->Snap(window, expected)); + + const auto& layoutWindows = workArea->GetLayoutWindows(); + Assert::IsTrue(expected == layoutWindows.GetZoneIndexSetFromWindow(window)); + } + TEST_METHOD (UnsnapPropertyTest) { const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); const auto window = Mocks::WindowCreate(m_hInst); - workArea->SnapWindow(window, { 1, 2 }); - workArea->UnsnapWindow(window); + Assert::IsTrue(workArea->Snap(window, { 1, 2 })); + Assert::IsTrue(workArea->Unsnap(window)); const auto actual = FancyZonesWindowProperties::RetrieveZoneIndexProperty(window); Assert::IsTrue(actual.empty()); @@ -671,13 +333,25 @@ namespace FancyZonesUnitTests const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); const auto window = Mocks::WindowCreate(m_hInst); - workArea->SnapWindow(window, { 1, 2 }); - workArea->UnsnapWindow(window); + Assert::IsTrue(workArea->Snap(window, { 1, 2 })); + Assert::IsTrue(workArea->Unsnap(window)); const auto processPath = get_process_path(window); const auto history = AppZoneHistory::instance().GetZoneHistory(processPath, m_workAreaId); Assert::IsFalse(history.has_value()); } + + TEST_METHOD (UnsnapLayoutAssignedWindowsTest) + { + const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect); + const auto window = Mocks::WindowCreate(m_hInst); + + Assert::IsTrue(workArea->Snap(window, { 1, 2 })); + Assert::IsTrue(workArea->Unsnap(window)); + + const auto& layoutWindows = workArea->GetLayoutWindows(); + Assert::IsTrue(layoutWindows.GetZoneIndexSetFromWindow(window).empty()); + } }; } From 5426759b9c4ad9620d7846fd8bf6027772398712 Mon Sep 17 00:00:00 2001 From: gokcekantarci <115616017+gokcekantarci@users.noreply.github.com> Date: Tue, 22 Aug 2023 18:16:42 +0300 Subject: [PATCH 09/11] Pt run] keep shell open (#28041) * [PTRun] LeaveShellOpen condition added to run command * [PTRun] Keep shell open added to shell plugin settings. * [PTRun] Unnecessary variable deleted. Formatting. * [PTRun] Variable name changed. --- .../Plugins/Microsoft.Plugin.Shell/Main.cs | 62 +++++++++++++++++-- .../Properties/Resources.Designer.cs | 9 +++ .../Properties/Resources.resx | 3 + 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Main.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Main.cs index a74783c851..28441e0b73 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Main.cs @@ -13,6 +13,8 @@ using System.Linq; using System.Reflection; using System.Windows.Input; using ManagedCommon; +using Microsoft.Plugin.Shell.Properties; +using Microsoft.PowerToys.Settings.UI.Library; using Wox.Infrastructure.Storage; using Wox.Plugin; using Wox.Plugin.Common; @@ -21,7 +23,7 @@ using Control = System.Windows.Controls.Control; namespace Microsoft.Plugin.Shell { - public class Main : IPlugin, IPluginI18n, IContextMenu, ISavable + public class Main : IPlugin, IPluginI18n, ISettingProvider, IContextMenu, ISavable { private static readonly IFileSystem FileSystem = new FileSystem(); private static readonly IPath Path = FileSystem.Path; @@ -37,6 +39,16 @@ namespace Microsoft.Plugin.Shell public string Description => Properties.Resources.wox_plugin_cmd_plugin_description; + public IEnumerable AdditionalOptions => new List() + { + new PluginAdditionalOption() + { + Key = "LeaveShellOpen", + DisplayLabel = Resources.wox_leave_shell_open, + Value = _settings.LeaveShellOpen, + }, + }; + private PluginInitContext _context; public Main() @@ -220,17 +232,41 @@ namespace Microsoft.Plugin.Shell if (ExistInPath(filename)) { var arguments = parts[1]; - info = ShellCommand.SetProcessStartInfo(filename, workingDirectory, arguments, runAsVerbArg); + if (_settings.LeaveShellOpen) + { + // Wrap the command in a cmd.exe process + info = ShellCommand.SetProcessStartInfo("cmd.exe", workingDirectory, $"/k \"{filename} {arguments}\"", runAsVerbArg); + } + else + { + info = ShellCommand.SetProcessStartInfo(filename, workingDirectory, arguments, runAsVerbArg); + } + } + else + { + if (_settings.LeaveShellOpen) + { + // Wrap the command in a cmd.exe process + info = ShellCommand.SetProcessStartInfo("cmd.exe", workingDirectory, $"/k \"{command}\"", runAsVerbArg); + } + else + { + info = ShellCommand.SetProcessStartInfo(command, verb: runAsVerbArg); + } + } + } + else + { + if (_settings.LeaveShellOpen) + { + // Wrap the command in a cmd.exe process + info = ShellCommand.SetProcessStartInfo("cmd.exe", workingDirectory, $"/k \"{command}\"", runAsVerbArg); } else { info = ShellCommand.SetProcessStartInfo(command, verb: runAsVerbArg); } } - else - { - info = ShellCommand.SetProcessStartInfo(command, verb: runAsVerbArg); - } } } else @@ -378,5 +414,19 @@ namespace Microsoft.Plugin.Shell return resultlist; } + + public void UpdateSettings(PowerLauncherPluginSettings settings) + { + var leaveShellOpen = false; + + if (settings != null && settings.AdditionalOptions != null) + { + var optionLeaveShellOpen = settings.AdditionalOptions.FirstOrDefault(x => x.Key == "LeaveShellOpen"); + leaveShellOpen = optionLeaveShellOpen?.Value ?? leaveShellOpen; + _settings.LeaveShellOpen = leaveShellOpen; + } + + Save(); + } } } diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.Designer.cs index 834bcdd7d3..0917fb2ed4 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.Designer.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.Designer.cs @@ -60,6 +60,15 @@ namespace Microsoft.Plugin.Shell.Properties { } } + /// + /// Looks up a localized string similar to Keep shell open. + /// + public static string wox_leave_shell_open { + get { + return ResourceManager.GetString("wox_leave_shell_open", resourceCulture); + } + } + /// /// Looks up a localized string similar to this command has been executed {0} times. /// diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.resx index 0c61bb5e97..78be672077 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.resx +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.resx @@ -141,4 +141,7 @@ Run as different user (Ctrl+Shift+U) + + Keep shell open + \ No newline at end of file From c2bb2a8c3aa0e8c225d0ce1af70971af36125566 Mon Sep 17 00:00:00 2001 From: Seraphima Zykova Date: Tue, 22 Aug 2023 21:25:03 +0300 Subject: [PATCH 10/11] [FancyZones Editor] Duplicating non-selected layout applies duplicated layout fix (#28042) --- .../editor/FancyZonesEditor/EditorWindow.cs | 8 +++ .../FancyZonesEditor/GridEditorWindow.xaml.cs | 6 -- .../editor/FancyZonesEditor/LayoutBackup.cs | 69 +++++++++++++++++++ .../FancyZonesEditor/MainWindow.xaml.cs | 53 ++------------ .../Models/MainWindowSettingsModel.cs | 21 ------ .../editor/FancyZonesEditor/Overlay.cs | 16 +++++ 6 files changed, 98 insertions(+), 75 deletions(-) create mode 100644 src/modules/fancyzones/editor/FancyZonesEditor/LayoutBackup.cs diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs b/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs index 908d5fe32f..2f9cd05372 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs @@ -45,6 +45,14 @@ namespace FancyZonesEditor protected void OnCancel(object sender, RoutedEventArgs e) { + // restore backup, clean up + App.Overlay.EndEditing(true); + + // select and draw applied layout + var settings = ((App)Application.Current).MainWindowSettings; + settings.SetSelectedModel(settings.AppliedModel); + App.Overlay.CurrentDataContext = settings.AppliedModel; + Close(); } } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/GridEditorWindow.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/GridEditorWindow.xaml.cs index 72179e5a2a..1aea412c66 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/GridEditorWindow.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/GridEditorWindow.xaml.cs @@ -16,15 +16,11 @@ namespace FancyZonesEditor KeyUp += GridEditorWindow_KeyUp; KeyDown += ((App)Application.Current).App_KeyDown; - - _stashedModel = (GridLayoutModel)(App.Overlay.CurrentDataContext as GridLayoutModel).Clone(); } protected new void OnCancel(object sender, RoutedEventArgs e) { base.OnCancel(sender, e); - GridLayoutModel model = App.Overlay.CurrentDataContext as GridLayoutModel; - _stashedModel.RestoreTo(model); } private void GridEditorWindow_KeyUp(object sender, KeyEventArgs e) @@ -37,8 +33,6 @@ namespace FancyZonesEditor ((App)Application.Current).App_KeyUp(sender, e); } - private GridLayoutModel _stashedModel; - // This is required to fix a WPF rendering bug when using custom chrome private void EditorWindow_ContentRendered(object sender, System.EventArgs e) { diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/LayoutBackup.cs b/src/modules/fancyzones/editor/FancyZonesEditor/LayoutBackup.cs new file mode 100644 index 0000000000..36dd671107 --- /dev/null +++ b/src/modules/fancyzones/editor/FancyZonesEditor/LayoutBackup.cs @@ -0,0 +1,69 @@ +// 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.Collections.Generic; +using System.Windows; +using FancyZonesEditor.Models; + +namespace FancyZonesEditor +{ + public class LayoutBackup + { + private LayoutModel _backup; + private List _defaultLayoutsBackup; + + public LayoutBackup() + { + } + + public void Backup(LayoutModel model) + { + if (model is GridLayoutModel grid) + { + _backup = new GridLayoutModel(grid); + } + else if (model is CanvasLayoutModel canvas) + { + _backup = new CanvasLayoutModel(canvas); + } + + _defaultLayoutsBackup = new List(MainWindowSettingsModel.DefaultLayouts.Layouts); + } + + public void Restore() + { + if (_backup != null) + { + var settings = ((App)Application.Current).MainWindowSettings; + var selectedModel = settings.SelectedModel; + + if (selectedModel == null) + { + return; + } + + if (_backup is GridLayoutModel grid) + { + grid.RestoreTo((GridLayoutModel)selectedModel); + grid.InitTemplateZones(); + } + else if (_backup is CanvasLayoutModel canvas) + { + canvas.RestoreTo((CanvasLayoutModel)selectedModel); + } + } + + if (_defaultLayoutsBackup != null) + { + MainWindowSettingsModel.DefaultLayouts.Restore(_defaultLayoutsBackup); + } + } + + public void Clear() + { + _backup = null; + _defaultLayoutsBackup = null; + } + } +} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs index 5c2eaeee36..b5dd83edb6 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs @@ -30,8 +30,6 @@ namespace FancyZonesEditor private const int MinimalForDefaultWrapPanelsHeight = 900; private readonly MainWindowSettingsModel _settings = ((App)Application.Current).MainWindowSettings; - private LayoutModel _backup; - private List _defaultLayoutsBackup; private ContentDialog _openedDialog; private TextBlock _createLayoutAnnounce; @@ -244,21 +242,15 @@ namespace FancyZonesEditor Logger.LogTrace(); var dataContext = ((FrameworkElement)sender).DataContext; - Select((LayoutModel)dataContext); - EditLayoutDialog.Hide(); - var mainEditor = App.Overlay; - if (mainEditor.CurrentDataContext is not LayoutModel model) + if (dataContext is not LayoutModel model) { return; } - model.IsSelected = false; - // make a copy model = model.Clone(); - mainEditor.CurrentDataContext = model; string name = model.Name; var index = name.LastIndexOf('('); @@ -293,11 +285,8 @@ namespace FancyZonesEditor } model.Name = name + " (" + (++maxCustomIndex) + ')'; - model.Persist(); - App.Overlay.Monitors[App.Overlay.CurrentDesktop].SetLayoutSettings(model); - App.FancyZonesEditorIO.SerializeAppliedLayouts(); App.FancyZonesEditorIO.SerializeCustomLayouts(); } @@ -327,7 +316,7 @@ namespace FancyZonesEditor private void OnClosing(object sender, EventArgs e) { Logger.LogTrace(); - CancelLayoutChanges(); + App.Overlay.EndEditing(true); App.FancyZonesEditorIO.SerializeAppliedLayouts(); App.FancyZonesEditorIO.SerializeCustomLayouts(); @@ -356,17 +345,7 @@ namespace FancyZonesEditor var dataContext = ((FrameworkElement)sender).DataContext; Select((LayoutModel)dataContext); - - if (_settings.SelectedModel is GridLayoutModel grid) - { - _backup = new GridLayoutModel(grid); - } - else if (_settings.SelectedModel is CanvasLayoutModel canvas) - { - _backup = new CanvasLayoutModel(canvas); - } - - _defaultLayoutsBackup = new List(MainWindowSettingsModel.DefaultLayouts.Layouts); + App.Overlay.StartEditing((LayoutModel)dataContext); Keyboard.ClearFocus(); EditLayoutDialogTitle.Text = string.Format(CultureInfo.CurrentCulture, Properties.Resources.Edit_Template, ((LayoutModel)dataContext).Name); @@ -459,7 +438,7 @@ namespace FancyZonesEditor // EditLayout: Cancel changes private void EditLayoutDialog_SecondaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) { - CancelLayoutChanges(); + App.Overlay.EndEditing(false); Select(_settings.AppliedModel); } @@ -474,8 +453,7 @@ namespace FancyZonesEditor return; } - _backup = null; - _defaultLayoutsBackup = null; + mainEditor.EndEditing(false); // update current settings if (model == _settings.AppliedModel) @@ -510,12 +488,6 @@ namespace FancyZonesEditor if (result == ContentDialogResult.Primary) { LayoutModel model = element.DataContext as LayoutModel; - - if (_backup != null && model.Guid == _backup.Guid) - { - _backup = null; - } - MainWindowSettingsModel.DefaultLayouts.Reset(model.Uuid); if (model == _settings.AppliedModel) @@ -591,21 +563,6 @@ namespace FancyZonesEditor } } - private void CancelLayoutChanges() - { - if (_backup != null) - { - _settings.RestoreSelectedModel(_backup); - _backup = null; - } - - if (_defaultLayoutsBackup != null) - { - MainWindowSettingsModel.DefaultLayouts.Restore(_defaultLayoutsBackup); - _defaultLayoutsBackup = null; - } - } - private void NumberBox_Loaded(object sender, RoutedEventArgs e) { // The TextBox inside a NumberBox doesn't inherit the Automation Properties name, so we have to set it. diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/MainWindowSettingsModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/MainWindowSettingsModel.cs index 4cea41f04d..0e5391f724 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/MainWindowSettingsModel.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/MainWindowSettingsModel.cs @@ -267,27 +267,6 @@ namespace FancyZonesEditor return foundModel; } - public void RestoreSelectedModel(LayoutModel model) - { - if (SelectedModel == null || model == null) - { - return; - } - - SelectedModel.SensitivityRadius = model.SensitivityRadius; - SelectedModel.TemplateZoneCount = model.TemplateZoneCount; - SelectedModel.IsSelected = model.IsSelected; - SelectedModel.IsApplied = model.IsApplied; - SelectedModel.Name = model.Name; - SelectedModel.QuickKey = model.QuickKey; - - if (model is GridLayoutModel grid) - { - ((GridLayoutModel)SelectedModel).Spacing = grid.Spacing; - ((GridLayoutModel)SelectedModel).ShowSpacing = grid.ShowSpacing; - } - } - public void SetSelectedModel(LayoutModel model) { if (_selectedModel != null) diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Overlay.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Overlay.cs index 1e55c3f61f..a1cbaeb77b 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Overlay.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Overlay.cs @@ -17,6 +17,7 @@ namespace FancyZonesEditor private LayoutPreview _layoutPreview; private UserControl _editorLayout; private EditorWindow _editorWindow; + private LayoutBackup _layoutBackup = new LayoutBackup(); public List Monitors { get; private set; } @@ -261,6 +262,21 @@ namespace FancyZonesEditor } } + public void StartEditing(LayoutModel model) + { + _layoutBackup.Backup(model); + } + + public void EndEditing(bool restoreBackup) + { + if (restoreBackup) + { + _layoutBackup.Restore(); + } + + _layoutBackup.Clear(); + } + public void CloseLayoutWindow() { for (int i = 0; i < DesktopsCount; i++) From efee03eb99732a28e7e861e612abd2cf4984fada Mon Sep 17 00:00:00 2001 From: Andrey Nekrasov Date: Wed, 23 Aug 2023 18:56:40 +0200 Subject: [PATCH 11/11] [KBM] Distinguish numpad keys (#28097) --- src/common/interop/keyboard_layout.cpp | 39 +++++-- .../KeyboardManagerEditor.cpp | 2 + .../KeyDropDownControl.cpp | 2 +- .../KeyboardEventHandlers.cpp | 64 +++++------ .../KeyboardManager.cpp | 5 +- .../keyboardmanager/common/Helpers.cpp | 47 +++++++- src/modules/keyboardmanager/common/Helpers.h | 6 ++ .../keyboardmanager/common/Shortcut.cpp | 14 +-- src/modules/keyboardmanager/common/Shortcut.h | 102 +++++------------- 9 files changed, 145 insertions(+), 136 deletions(-) diff --git a/src/common/interop/keyboard_layout.cpp b/src/common/interop/keyboard_layout.cpp index 87562d4bf3..6b40b66024 100644 --- a/src/common/interop/keyboard_layout.cpp +++ b/src/common/interop/keyboard_layout.cpp @@ -5,6 +5,8 @@ #include "keyboard_layout_impl.h" #include "shared_constants.h" +constexpr DWORD numpadOriginBit = 1ull << 31; + LayoutMap::LayoutMap() : impl(new LayoutMap::LayoutMapImpl()) { @@ -110,7 +112,6 @@ void LayoutMap::LayoutMapImpl::UpdateLayout() keyboardLayoutMap[VK_BACK] = L"Backspace"; keyboardLayoutMap[VK_TAB] = L"Tab"; keyboardLayoutMap[VK_CLEAR] = L"Clear"; - keyboardLayoutMap[VK_RETURN] = L"Enter"; keyboardLayoutMap[VK_SHIFT] = L"Shift"; keyboardLayoutMap[VK_CONTROL] = L"Ctrl"; keyboardLayoutMap[VK_MENU] = L"Alt"; @@ -118,20 +119,36 @@ void LayoutMap::LayoutMapImpl::UpdateLayout() keyboardLayoutMap[VK_CAPITAL] = L"Caps Lock"; keyboardLayoutMap[VK_ESCAPE] = L"Esc"; keyboardLayoutMap[VK_SPACE] = L"Space"; + + keyboardLayoutMap[VK_LEFT] = L"Left"; + keyboardLayoutMap[VK_RIGHT] = L"Right"; + keyboardLayoutMap[VK_UP] = L"Up"; + keyboardLayoutMap[VK_DOWN] = L"Down"; + keyboardLayoutMap[VK_INSERT] = L"Insert"; + keyboardLayoutMap[VK_DELETE] = L"Delete"; keyboardLayoutMap[VK_PRIOR] = L"PgUp"; keyboardLayoutMap[VK_NEXT] = L"PgDn"; - keyboardLayoutMap[VK_END] = L"End"; keyboardLayoutMap[VK_HOME] = L"Home"; - keyboardLayoutMap[VK_LEFT] = L"Left"; - keyboardLayoutMap[VK_UP] = L"Up"; - keyboardLayoutMap[VK_RIGHT] = L"Right"; - keyboardLayoutMap[VK_DOWN] = L"Down"; + keyboardLayoutMap[VK_END] = L"End"; + keyboardLayoutMap[VK_RETURN] = L"Enter"; + + keyboardLayoutMap[VK_LEFT | numpadOriginBit] = L"Left (Numpad)"; + keyboardLayoutMap[VK_RIGHT | numpadOriginBit] = L"Right (Numpad)"; + keyboardLayoutMap[VK_UP | numpadOriginBit] = L"Up (Numpad)"; + keyboardLayoutMap[VK_DOWN | numpadOriginBit] = L"Down (Numpad)"; + keyboardLayoutMap[VK_INSERT | numpadOriginBit] = L"Insert (Numpad)"; + keyboardLayoutMap[VK_DELETE | numpadOriginBit] = L"Delete (Numpad)"; + keyboardLayoutMap[VK_PRIOR | numpadOriginBit] = L"PgUp (Numpad)"; + keyboardLayoutMap[VK_NEXT | numpadOriginBit] = L"PgDn (Numpad)"; + keyboardLayoutMap[VK_HOME | numpadOriginBit] = L"Home (Numpad)"; + keyboardLayoutMap[VK_END | numpadOriginBit] = L"End (Numpad)"; + keyboardLayoutMap[VK_RETURN | numpadOriginBit] = L"Enter (Numpad)"; + keyboardLayoutMap[VK_DIVIDE | numpadOriginBit] = L"/ (Numpad)"; + keyboardLayoutMap[VK_SELECT] = L"Select"; keyboardLayoutMap[VK_PRINT] = L"Print"; keyboardLayoutMap[VK_EXECUTE] = L"Execute"; keyboardLayoutMap[VK_SNAPSHOT] = L"Print Screen"; - keyboardLayoutMap[VK_INSERT] = L"Insert"; - keyboardLayoutMap[VK_DELETE] = L"Delete"; keyboardLayoutMap[VK_HELP] = L"Help"; keyboardLayoutMap[VK_LWIN] = L"Win (Left)"; keyboardLayoutMap[VK_RWIN] = L"Win (Right)"; @@ -275,6 +292,12 @@ std::vector LayoutMap::LayoutMapImpl::GetKeyCodeList(const bool isShortcu } } + // Add numpad keys + for (auto it = keyboardLayoutMap.rbegin(); it->first & numpadOriginBit; ++it) + { + keyCodes.push_back(it->first); + } + // Sort the special keys in alphabetical order std::sort(specialKeys.begin(), specialKeys.end(), [&](const DWORD& lhs, const DWORD& rhs) { return keyboardLayoutMap[lhs] < keyboardLayoutMap[rhs]; diff --git a/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.cpp b/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.cpp index bb2cde24ad..54e2c7087d 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.cpp @@ -207,6 +207,8 @@ LRESULT KeyboardManagerEditor::KeyHookProc(int nCode, WPARAM wParam, LPARAM lPar { event.lParam = reinterpret_cast(lParam); event.wParam = wParam; + event.lParam->vkCode = Helpers::EncodeKeyNumpadOrigin(event.lParam->vkCode, event.lParam->flags & LLKHF_EXTENDED); + if (editor->HandleKeyboardHookEvent(&event) == 1) { // Reset Num Lock whenever a NumLock key down event is suppressed since Num Lock key state change occurs before it is intercepted by low level hooks diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyDropDownControl.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyDropDownControl.cpp index c847ad2a1b..991175019f 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyDropDownControl.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyDropDownControl.cpp @@ -27,7 +27,7 @@ DWORD KeyDropDownControl::GetSelectedValue(ComboBox comboBox) } auto value = winrt::unbox_value(dataContext); - return stoi(std::wstring(value)); + return stoul(std::wstring(value)); } void KeyDropDownControl::SetSelectedValue(std::wstring value) diff --git a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp index be2a8cc02b..8528a7dd12 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp @@ -42,8 +42,7 @@ namespace KeyboardEventHandlers key_count = std::get(it->second).Size(); } - LPINPUT keyEventList = new INPUT[size_t(key_count)](); - memset(keyEventList, 0, sizeof(keyEventList)); + LPINPUT keyEventList = new INPUT[size_t(key_count)]{}; // Handle remaps to VK_WIN_BOTH DWORD target; @@ -148,7 +147,6 @@ namespace KeyboardEventHandlers } int key_count = 2; LPINPUT keyEventList = new INPUT[size_t(key_count)](); - memset(keyEventList, 0, sizeof(keyEventList)); Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, (WORD)data->lParam->vkCode, 0, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG); Helpers::SetKeyEvent(keyEventList, 1, INPUT_KEYBOARD, (WORD)data->lParam->vkCode, KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG); @@ -232,8 +230,7 @@ namespace KeyboardEventHandlers { // key down for all new shortcut keys except the common modifiers key_count = dest_size - commonKeys; - keyEventList = new INPUT[key_count](); - memset(keyEventList, 0, sizeof(keyEventList)); + keyEventList = new INPUT[key_count]{}; int i = 0; Helpers::SetModifierKeyEvents(std::get(it->second.targetShortcut), it->second.winKeyInvoked, keyEventList, i, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first); Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast(std::get(it->second.targetShortcut).GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); @@ -243,8 +240,7 @@ namespace KeyboardEventHandlers { // Dummy key, key up for all the original shortcut modifier keys and key down for all the new shortcut keys but common keys in each are not repeated key_count = KeyboardManagerConstants::DUMMY_KEY_EVENT_SIZE + (src_size - 1) + (dest_size) - (2 * static_cast(commonKeys)); - keyEventList = new INPUT[key_count](); - memset(keyEventList, 0, sizeof(keyEventList)); + keyEventList = new INPUT[key_count]{}; // Send a dummy key event to prevent modifier press+release from being triggered. Example: Win+A->Ctrl+V, press Win+A, since Win will be released here we need to send a dummy event before it int i = 0; @@ -282,8 +278,7 @@ namespace KeyboardEventHandlers it->second.isOriginalActionKeyPressed = true; } - keyEventList = new INPUT[key_count](); - memset(keyEventList, 0, sizeof(keyEventList)); + keyEventList = new INPUT[key_count]{}; // Send a dummy key event to prevent modifier press+release from being triggered. Example: Win+A->V, press Win+A, since Win will be released here we need to send a dummy event before it int i = 0; @@ -302,7 +297,7 @@ namespace KeyboardEventHandlers // Modifier state reset might be required for this key depending on the shortcut's action and target modifier - ex: Win+Caps -> Ctrl if (it->first.GetCtrlKey() == NULL && it->first.GetAltKey() == NULL && it->first.GetShiftKey() == NULL) { - ResetIfModifierKeyForLowerLevelKeyHandlers(ii,static_cast(Helpers::FilterArtificialKeys(std::get(it->second.targetShortcut))), data->lParam->vkCode); + ResetIfModifierKeyForLowerLevelKeyHandlers(ii, static_cast(Helpers::FilterArtificialKeys(std::get(it->second.targetShortcut))), data->lParam->vkCode); } } @@ -361,8 +356,7 @@ namespace KeyboardEventHandlers key_count += 1; } - keyEventList = new INPUT[key_count](); - memset(keyEventList, 0, sizeof(keyEventList)); + keyEventList = new INPUT[key_count]{}; // Release new shortcut state (release in reverse order of shortcut to be accurate) int i = 0; @@ -400,8 +394,7 @@ namespace KeyboardEventHandlers key_count--; } - keyEventList = new INPUT[key_count](); - memset(keyEventList, 0, sizeof(keyEventList)); + keyEventList = new INPUT[key_count]{}; // Release new key state int i = 0; @@ -453,8 +446,7 @@ namespace KeyboardEventHandlers } size_t key_count = 1; - LPINPUT keyEventList = new INPUT[key_count](); - memset(keyEventList, 0, sizeof(keyEventList)); + LPINPUT keyEventList = new INPUT[key_count]{}; if (remapToShortcut) { Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast(std::get(it->second.targetShortcut).GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); @@ -476,13 +468,12 @@ namespace KeyboardEventHandlers LPINPUT keyEventList; if (remapToShortcut) { - keyEventList = new INPUT[key_count](); - memset(keyEventList, 0, sizeof(keyEventList)); + keyEventList = new INPUT[key_count]{}; Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast(std::get(it->second.targetShortcut).GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); } else if (std::get(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED) { - // If remapped to disable, do nothing and suppress the key event + // If remapped to disable, do nothing and suppress the key event // Since the original shortcut's action key is released, set it to false it->second.isOriginalActionKeyPressed = false; return 1; @@ -491,13 +482,12 @@ namespace KeyboardEventHandlers { // Check if the keyboard state is clear apart from the target remap key (by creating a temp Shortcut object with the target key) bool isKeyboardStateClear = Shortcut(std::vector({ Helpers::FilterArtificialKeys(std::get(it->second.targetShortcut)) })).IsKeyboardStateClearExceptShortcut(ii); - + // If the keyboard state is clear, we release the target key but do not reset the remap state if (isKeyboardStateClear) { - keyEventList = new INPUT[key_count](); - memset(keyEventList, 0, sizeof(keyEventList)); - Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD,static_cast(Helpers::FilterArtificialKeys(std::get(it->second.targetShortcut))), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); + keyEventList = new INPUT[key_count]{}; + Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast(Helpers::FilterArtificialKeys(std::get(it->second.targetShortcut))), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); } else { @@ -507,8 +497,7 @@ namespace KeyboardEventHandlers // 1 for releasing new key and original shortcut modifiers, and dummy key key_count = dest_size + (src_size - 1) + KeyboardManagerConstants::DUMMY_KEY_EVENT_SIZE; - keyEventList = new INPUT[key_count](); - memset(keyEventList, 0, sizeof(keyEventList)); + keyEventList = new INPUT[key_count]{}; // Release new key state int i = 0; @@ -574,7 +563,7 @@ namespace KeyboardEventHandlers size_t key_count; LPINPUT keyEventList = nullptr; - + // Check if a new remapping should be applied Shortcut currentlyPressed = it->first; currentlyPressed.actionKey = data->lParam->vkCode; @@ -588,8 +577,7 @@ namespace KeyboardEventHandlers DWORD to = std::get<0>(newRemapping.targetShortcut); bool isLastKeyStillPressed = ii.GetVirtualKeyState(static_cast(from.actionKey)); key_count = static_cast(from.Size()) - 1 + 1 + (isLastKeyStillPressed ? 1 : 0); - keyEventList = new INPUT[key_count](); - memset(keyEventList, 0, sizeof(keyEventList)); + keyEventList = new INPUT[key_count]{}; int i = 0; Helpers::SetModifierKeyEvents(from, it->second.winKeyInvoked, keyEventList, i, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); if (ii.GetVirtualKeyState(static_cast(from.actionKey))) @@ -599,7 +587,8 @@ namespace KeyboardEventHandlers i++; } Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast(to), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); - }else + } + else { Shortcut to = std::get(newRemapping.targetShortcut); bool isLastKeyStillPressed = ii.GetVirtualKeyState(static_cast(from.actionKey)); @@ -608,7 +597,7 @@ namespace KeyboardEventHandlers temp_key_count_calculation += static_cast(to.Size()) - 1; temp_key_count_calculation -= static_cast(2) * from.GetCommonModifiersCount(to); key_count = temp_key_count_calculation + 1 + (isLastKeyStillPressed ? 1 : 0); - keyEventList = new INPUT[key_count](); + keyEventList = new INPUT[key_count]{}; int i = 0; Helpers::SetModifierKeyEvents(from, it->second.winKeyInvoked, keyEventList, i, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, to); @@ -635,7 +624,7 @@ namespace KeyboardEventHandlers } else { - // Key up for all new shortcut keys, key down for original shortcut modifiers and current key press but common keys aren't repeated + // Key up for all new shortcut keys, key down for original shortcut modifiers and current key press but common keys aren't repeated key_count = (dest_size) + (src_size - 1) - (2 * static_cast(commonKeys)); // If the target shortcut's action key is pressed, then it should be released and original shortcut's action key should be set @@ -646,8 +635,7 @@ namespace KeyboardEventHandlers key_count += 2; } - keyEventList = new INPUT[key_count](); - memset(keyEventList, 0, sizeof(keyEventList)); + keyEventList = new INPUT[key_count]{}; // Release new shortcut state (release in reverse order of shortcut to be accurate) int i = 0; @@ -719,8 +707,7 @@ namespace KeyboardEventHandlers // Key down for original shortcut modifiers and action key, and current key press size_t key_count = src_size + 1; - LPINPUT keyEventList = new INPUT[key_count](); - memset(keyEventList, 0, sizeof(keyEventList)); + LPINPUT keyEventList = new INPUT[key_count]{}; // Set original shortcut key down state int i = 0; @@ -730,7 +717,7 @@ namespace KeyboardEventHandlers if (isRemapToDisable && isOriginalActionKeyPressed) { // Set original action key - Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD,static_cast(it->first.GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); + Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast(it->first.GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); i++; } else @@ -812,7 +799,7 @@ namespace KeyboardEventHandlers std::wstring query_string; AppSpecificShortcutRemapTable::iterator it; - + // Check if an app-specific shortcut is already activated if (state.GetActivatedApp() == KeyboardManagerConstants::NoActivatedApp) { @@ -854,8 +841,7 @@ namespace KeyboardEventHandlers if (Helpers::IsModifierKey(key) && !(key == VK_LWIN || key == VK_RWIN || key == CommonSharedConstants::VK_WIN_BOTH)) { int key_count = 1; - LPINPUT keyEventList = new INPUT[size_t(key_count)](); - memset(keyEventList, 0, sizeof(keyEventList)); + LPINPUT keyEventList = new INPUT[size_t(key_count)]{}; // Use the suppress flag to ensure these are not intercepted by any remapped keys or shortcuts Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast(key), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG); diff --git a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardManager.cpp b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardManager.cpp index 83369f2e32..4e9ce35ea0 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardManager.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardManager.cpp @@ -66,13 +66,14 @@ void KeyboardManager::LoadSettings() } } -LRESULT CALLBACK KeyboardManager::HookProc(int nCode, WPARAM wParam, LPARAM lParam) +LRESULT CALLBACK KeyboardManager::HookProc(int nCode, const WPARAM wParam, const LPARAM lParam) { LowlevelKeyboardEvent event; if (nCode == HC_ACTION) { event.lParam = reinterpret_cast(lParam); event.wParam = wParam; + event.lParam->vkCode = Helpers::EncodeKeyNumpadOrigin(event.lParam->vkCode, event.lParam->flags & LLKHF_EXTENDED); if (keyboardManagerObjectPtr->HandleKeyboardHookEvent(&event) == 1) { // Reset Num Lock whenever a NumLock key down event is suppressed since Num Lock key state change occurs before it is intercepted by low level hooks @@ -83,7 +84,7 @@ LRESULT CALLBACK KeyboardManager::HookProc(int nCode, WPARAM wParam, LPARAM lPar return 1; } } - + return CallNextHookEx(hookHandleCopy, nCode, wParam, lParam); } diff --git a/src/modules/keyboardmanager/common/Helpers.cpp b/src/modules/keyboardmanager/common/Helpers.cpp index e967d5ce98..00136c7ef5 100644 --- a/src/modules/keyboardmanager/common/Helpers.cpp +++ b/src/modules/keyboardmanager/common/Helpers.cpp @@ -9,6 +9,50 @@ namespace Helpers { + DWORD EncodeKeyNumpadOrigin(const DWORD key, const bool extended) + { + bool numpad_originated = false; + switch (key) + { + case VK_LEFT: + case VK_RIGHT: + case VK_UP: + case VK_DOWN: + case VK_INSERT: + case VK_DELETE: + case VK_PRIOR: + case VK_NEXT: + case VK_HOME: + case VK_END: + numpad_originated = !extended; + break; + case VK_RETURN: + case VK_DIVIDE: + numpad_originated = extended; + break; + } + + if (numpad_originated) + return key | GetNumpadOriginEncodingBit(); + else + return key; + } + + DWORD ClearKeyNumpadOrigin(const DWORD key) + { + return (key & ~GetNumpadOriginEncodingBit()); + } + + bool IsNumpadOriginated(const DWORD key) + { + return !!(key & GetNumpadOriginEncodingBit()); + } + DWORD GetNumpadOriginEncodingBit() + { + // Intentionally do not mimic KF_EXTENDED to avoid confusion, because it's not the same thing + // See EncodeKeyNumpadOrigin. + return 1ull << 31; + } // Function to check if the key is a modifier key bool IsModifierKey(DWORD key) { @@ -18,7 +62,8 @@ namespace Helpers // Function to get the combined key for modifier keys DWORD GetCombinedKey(DWORD key) { - switch (key) { + switch (key) + { case VK_LWIN: case VK_RWIN: return CommonSharedConstants::VK_WIN_BOTH; diff --git a/src/modules/keyboardmanager/common/Helpers.h b/src/modules/keyboardmanager/common/Helpers.h index 4040b11b4b..27005a0146 100644 --- a/src/modules/keyboardmanager/common/Helpers.h +++ b/src/modules/keyboardmanager/common/Helpers.h @@ -14,6 +14,12 @@ namespace Helpers Shift, Action }; + + // Functions to encode that a key is originated from numpad + DWORD EncodeKeyNumpadOrigin(const DWORD key, const bool extended); + DWORD ClearKeyNumpadOrigin(const DWORD key); + bool IsNumpadOriginated(const DWORD key); + DWORD GetNumpadOriginEncodingBit(); // Function to check if the key is a modifier key bool IsModifierKey(DWORD key); diff --git a/src/modules/keyboardmanager/common/Shortcut.cpp b/src/modules/keyboardmanager/common/Shortcut.cpp index acc7edde14..efe64e99b8 100644 --- a/src/modules/keyboardmanager/common/Shortcut.cpp +++ b/src/modules/keyboardmanager/common/Shortcut.cpp @@ -194,7 +194,7 @@ DWORD Shortcut::GetShiftKey() const } // Function to check if the input key matches the win key expected in the shortcut -bool Shortcut::CheckWinKey(const DWORD& input) const +bool Shortcut::CheckWinKey(const DWORD input) const { if (winKey == ModifierKey::Disabled) { @@ -216,7 +216,7 @@ bool Shortcut::CheckWinKey(const DWORD& input) const } // Function to check if the input key matches the ctrl key expected in the shortcut -bool Shortcut::CheckCtrlKey(const DWORD& input) const +bool Shortcut::CheckCtrlKey(const DWORD input) const { if (ctrlKey == ModifierKey::Disabled) { @@ -238,7 +238,7 @@ bool Shortcut::CheckCtrlKey(const DWORD& input) const } // Function to check if the input key matches the alt key expected in the shortcut -bool Shortcut::CheckAltKey(const DWORD& input) const +bool Shortcut::CheckAltKey(const DWORD input) const { if (altKey == ModifierKey::Disabled) { @@ -260,7 +260,7 @@ bool Shortcut::CheckAltKey(const DWORD& input) const } // Function to check if the input key matches the shift key expected in the shortcut -bool Shortcut::CheckShiftKey(const DWORD& input) const +bool Shortcut::CheckShiftKey(const DWORD input) const { if (shiftKey == ModifierKey::Disabled) { @@ -282,7 +282,7 @@ bool Shortcut::CheckShiftKey(const DWORD& input) const } // Function to set a key in the shortcut based on the passed key code argument. Returns false if it is already set to the same value. This can be used to avoid UI refreshing -bool Shortcut::SetKey(const DWORD& input) +bool Shortcut::SetKey(const DWORD input) { // Since there isn't a key for a common Win key we use the key code defined by us if (input == CommonSharedConstants::VK_WIN_BOTH) @@ -394,7 +394,7 @@ bool Shortcut::SetKey(const DWORD& input) } // Function to reset the state of a shortcut key based on the passed key code argument. Since there is no VK_WIN code, use the second argument for setting common win key. -void Shortcut::ResetKey(const DWORD& input) +void Shortcut::ResetKey(const DWORD input) { // Since there isn't a key for a common Win key this is handled with a separate argument. if (input == CommonSharedConstants::VK_WIN_BOTH || input == VK_LWIN || input == VK_RWIN) @@ -415,7 +415,7 @@ void Shortcut::ResetKey(const DWORD& input) } else { - actionKey = NULL; + actionKey = {}; } } diff --git a/src/modules/keyboardmanager/common/Shortcut.h b/src/modules/keyboardmanager/common/Shortcut.h index 16a0d0fd6c..f48d321764 100644 --- a/src/modules/keyboardmanager/common/Shortcut.h +++ b/src/modules/keyboardmanager/common/Shortcut.h @@ -1,5 +1,8 @@ #pragma once #include "ModifierKey.h" + +#include +#include #include namespace KeyboardManagerInput @@ -14,91 +17,34 @@ private: // Function to split a wstring based on a delimiter and return a vector of split strings std::vector splitwstring(const std::wstring& input, wchar_t delimiter); -public: - ModifierKey winKey; - ModifierKey ctrlKey; - ModifierKey altKey; - ModifierKey shiftKey; - DWORD actionKey; - - // By default create an empty shortcut - Shortcut() : - winKey(ModifierKey::Disabled), ctrlKey(ModifierKey::Disabled), altKey(ModifierKey::Disabled), shiftKey(ModifierKey::Disabled), actionKey(NULL) + inline auto comparator() const { + return std::make_tuple(winKey, ctrlKey, altKey, shiftKey, actionKey); } +public: + ModifierKey winKey = ModifierKey::Disabled; + ModifierKey ctrlKey = ModifierKey::Disabled; + ModifierKey altKey = ModifierKey::Disabled; + ModifierKey shiftKey = ModifierKey::Disabled; + DWORD actionKey = {}; + + Shortcut() = default; + // Constructor to initialize Shortcut from it's virtual key code string representation. Shortcut(const std::wstring& shortcutVK); // Constructor to initialize shortcut from a list of keys Shortcut(const std::vector& keys); - // == operator - inline bool operator==(const Shortcut& sc) const + inline friend auto operator<=>(const Shortcut& lhs, const Shortcut& rhs) noexcept { - return (winKey == sc.winKey && ctrlKey == sc.ctrlKey && altKey == sc.altKey && shiftKey == sc.shiftKey && actionKey == sc.actionKey); + return lhs.comparator() <=> rhs.comparator(); } - // Less than operator must be defined to use with std::map. - inline bool operator<(const Shortcut& sc) const + inline friend bool operator==(const Shortcut& lhs, const Shortcut& rhs) noexcept { - // Compare win key first - if (winKey < sc.winKey) - { - return true; - } - else if (winKey > sc.winKey) - { - return false; - } - else - { - // If win key is equal, then compare ctrl key - if (ctrlKey < sc.ctrlKey) - { - return true; - } - else if (ctrlKey > sc.ctrlKey) - { - return false; - } - else - { - // If ctrl key is equal, then compare alt key - if (altKey < sc.altKey) - { - return true; - } - else if (altKey > sc.altKey) - { - return false; - } - else - { - // If alt key is equal, then compare shift key - if (shiftKey < sc.shiftKey) - { - return true; - } - else if (shiftKey > sc.shiftKey) - { - return false; - } - else - { - // If shift key is equal, then compare action key - if (actionKey < sc.actionKey) - { - return true; - } - else - { - return false; - } - } - } - } - } + return lhs.comparator() == rhs.comparator(); } // Function to return the number of keys in the shortcut @@ -126,22 +72,22 @@ public: DWORD GetShiftKey() const; // Function to check if the input key matches the win key expected in the shortcut - bool CheckWinKey(const DWORD& input) const; + bool CheckWinKey(const DWORD input) const; // Function to check if the input key matches the ctrl key expected in the shortcut - bool CheckCtrlKey(const DWORD& input) const; + bool CheckCtrlKey(const DWORD input) const; // Function to check if the input key matches the alt key expected in the shortcut - bool CheckAltKey(const DWORD& input) const; + bool CheckAltKey(const DWORD input) const; // Function to check if the input key matches the shift key expected in the shortcut - bool CheckShiftKey(const DWORD& input) const; + bool CheckShiftKey(const DWORD input) const; // Function to set a key in the shortcut based on the passed key code argument. Returns false if it is already set to the same value. This can be used to avoid UI refreshing - bool SetKey(const DWORD& input); + bool SetKey(const DWORD input); // Function to reset the state of a shortcut key based on the passed key code argument - void ResetKey(const DWORD& input); + void ResetKey(const DWORD input); // Function to return the string representation of the shortcut in virtual key codes appended in a string by ";" separator. winrt::hstring ToHstringVK() const;