From c93eb92cd0be1961a6ef9aa0064898bcd462eab4 Mon Sep 17 00:00:00 2001 From: Seraphima Zykova Date: Wed, 23 Jun 2021 15:48:54 +0300 Subject: [PATCH] [FancyZones] Move FancyZones out of the runner process (#11818) * rename dll -> FancyZonesModuleInterface (#11488) * [FancyZones] Rename "fancyzones/tests" -> "fancyzones/FancyZonesTests" (#11492) * [FancyZones] Rename "fancyzones/lib" -> "fancyzones/FancyZonesLib" (#11489) * [FancyZones] New FancyZones project. (#11544) * [FancyZones] Allow single instance of "PowerToys.FancyZones.exe" (#11558) * [FancyZones] Updated bug reports (#11571) * [FancyZones] Updated installer (#11572) * [FancyZones] Update string resources (#11596) * [FancyZones] Terminate FancyZones with runner (#11696) * [FancyZones] Drop support for the module interface API to save settings (#11661) * Settings telemetry for FancyZones (#11766) * commented out test * enable dpi awareness for the process --- PowerToys.sln | 28 +- installer/PowerToysSetup/Product.wxs | 3 +- src/common/interop/shared_constants.h | 2 + .../utils/UnhandledExceptionHandler_x64.h | 4 + .../fancyzones/FancyZones/FancyZones.vcxproj | 187 ++ .../FancyZones/FancyZones.vcxproj.filters | 42 + .../fancyzones/FancyZones/FancyZonesApp.cpp | 183 ++ .../fancyzones/FancyZones/FancyZonesApp.h | 63 + src/modules/fancyzones/FancyZones/main.cpp | 73 + .../fancyzones/FancyZones/packages.config | 5 + src/modules/fancyzones/FancyZones/pch.cpp | 1 + src/modules/fancyzones/FancyZones/pch.h | 10 + .../{lib => FancyZonesLib}/CallTracer.cpp | 0 .../{lib => FancyZonesLib}/CallTracer.h | 0 .../{lib => FancyZonesLib}/FancyZones.cpp | 2914 +++++++++-------- .../{lib => FancyZonesLib}/FancyZones.h | 0 .../{lib => FancyZonesLib}/FancyZonesData.cpp | 5 +- .../{lib => FancyZonesLib}/FancyZonesData.h | 14 +- .../FancyZonesDataTypes.cpp | 0 .../FancyZonesDataTypes.h | 0 .../FancyZonesLib.vcxproj | 229 +- .../FancyZonesLib.vcxproj.filters | 332 +- .../FancyZonesWinHookEventIDs.cpp | 0 .../FancyZonesWinHookEventIDs.h | 0 .../{lib => FancyZonesLib}/FileWatcher.cpp | 0 .../{lib => FancyZonesLib}/FileWatcher.h | 0 .../{lib => FancyZonesLib}/GenericKeyHook.h | 0 .../{lib => FancyZonesLib}/JsonHelpers.cpp | 0 .../{lib => FancyZonesLib}/JsonHelpers.h | 0 .../{lib => FancyZonesLib}/KeyState.h | 0 .../{lib => FancyZonesLib}/LocProject.json | 4 +- .../MonitorWorkAreaHandler.cpp | 0 .../MonitorWorkAreaHandler.h | 0 .../OnThreadExecutor.cpp | 0 .../{lib => FancyZonesLib}/Resources.resx | 0 .../SecondaryMouseButtonsHook.cpp | 0 .../SecondaryMouseButtonsHook.h | 0 .../{lib => FancyZonesLib}/Settings.cpp | 577 ++-- .../{lib => FancyZonesLib}/Settings.h | 4 +- .../VirtualDesktopUtils.cpp | 0 .../VirtualDesktopUtils.h | 0 .../WindowMoveHandler.cpp | 722 ++-- .../WindowMoveHandler.h | 162 +- .../{lib => FancyZonesLib}/Zone.cpp | 0 .../fancyzones/{lib => FancyZonesLib}/Zone.h | 0 .../{lib => FancyZonesLib}/ZoneSet.cpp | 0 .../{lib => FancyZonesLib}/ZoneSet.h | 0 .../{lib => FancyZonesLib}/ZoneWindow.cpp | 1190 +++---- .../{lib => FancyZonesLib}/ZoneWindow.h | 2 +- .../ZoneWindowDrawing.cpp | 0 .../ZoneWindowDrawing.h | 0 .../{lib => FancyZonesLib}/fancyzones.base.rc | 0 .../modules/fancyzones/lib/Resources.resx.lcl | 2 +- .../modules/fancyzones/lib/Resources.resx.lcl | 2 +- .../modules/fancyzones/lib/Resources.resx.lcl | 2 +- .../modules/fancyzones/lib/Resources.resx.lcl | 2 +- .../modules/fancyzones/lib/Resources.resx.lcl | 2 +- .../modules/fancyzones/lib/Resources.resx.lcl | 2 +- .../modules/fancyzones/lib/Resources.resx.lcl | 2 +- .../modules/fancyzones/lib/Resources.resx.lcl | 2 +- .../modules/fancyzones/lib/Resources.resx.lcl | 2 +- .../modules/fancyzones/lib/Resources.resx.lcl | 2 +- .../modules/fancyzones/lib/Resources.resx.lcl | 2 +- .../modules/fancyzones/lib/Resources.resx.lcl | 2 +- .../modules/fancyzones/lib/Resources.resx.lcl | 2 +- .../modules/fancyzones/lib/Resources.resx.lcl | 2 +- .../modules/fancyzones/lib/Resources.resx.lcl | 2 +- .../modules/fancyzones/lib/Resources.resx.lcl | 2 +- .../modules/fancyzones/lib/Resources.resx.lcl | 2 +- .../on_thread_executor.h | 0 .../packages.config | 0 .../fancyzones/{lib => FancyZonesLib}/pch.cpp | 10 +- .../fancyzones/{lib => FancyZonesLib}/pch.h | 56 +- .../{lib => FancyZonesLib}/resource.base.h | 4 +- .../{lib => FancyZonesLib}/trace.cpp | 14 +- .../fancyzones/{lib => FancyZonesLib}/trace.h | 2 +- .../{lib => FancyZonesLib}/util.cpp | 2 +- .../fancyzones/{lib => FancyZonesLib}/util.h | 0 .../FancyZonesModuleInterface.vcxproj} | 153 +- ...FancyZonesModuleInterface.vcxproj.filters} | 73 +- .../FancyZonesModuleInterface/dllmain.cpp | 187 ++ .../packages.config | 8 +- .../pch.cpp | 2 +- .../{dll => FancyZonesModuleInterface}/pch.h | 32 +- .../targetver.h | Bin .../UnitTests/FancyZones.Spec.cpp | 6 +- .../UnitTests/FancyZonesSettings.Spec.cpp | 32 +- .../UnitTests/JsonHelpers.Tests.cpp | 8 +- .../UnitTests/UnitTests-FancyZones.rc | 0 .../UnitTests/UnitTests.vcxproj | 173 +- .../UnitTests/UnitTests.vcxproj.filters | 132 +- .../UnitTests/Util.Spec.cpp | 2 +- .../UnitTests/Util.cpp | 0 .../UnitTests/Util.h | 154 +- .../UnitTests/Zone.Spec.cpp | 92 +- .../UnitTests/ZoneSet.Spec.cpp | 2255 +++++++------ .../UnitTests/ZoneWindow.Spec.cpp | 14 +- .../UnitTests}/packages.config | 8 +- .../UnitTests/pch.cpp | 10 +- .../UnitTests/pch.h | 36 +- .../UnitTests/resource.h | 0 src/modules/fancyzones/dll/dllmain.cpp | 336 -- src/runner/main.cpp | 2 +- .../FancyZonesSettingsIPCMessage.cs | 29 - .../SndFancyZonesSettings.cs | 27 - .../ViewModels/FancyZonesViewModel.cs | 20 +- .../ViewModelTests/FancyZones.cs | 368 ++- .../Views/FancyZonesPage.xaml.cs | 5 +- .../BugReportTool/EventViewer.cpp | 1 + .../BugReportTool/RegistryUtils.cpp | 3 +- 110 files changed, 5753 insertions(+), 5293 deletions(-) create mode 100644 src/modules/fancyzones/FancyZones/FancyZones.vcxproj create mode 100644 src/modules/fancyzones/FancyZones/FancyZones.vcxproj.filters create mode 100644 src/modules/fancyzones/FancyZones/FancyZonesApp.cpp create mode 100644 src/modules/fancyzones/FancyZones/FancyZonesApp.h create mode 100644 src/modules/fancyzones/FancyZones/main.cpp create mode 100644 src/modules/fancyzones/FancyZones/packages.config create mode 100644 src/modules/fancyzones/FancyZones/pch.cpp create mode 100644 src/modules/fancyzones/FancyZones/pch.h rename src/modules/fancyzones/{lib => FancyZonesLib}/CallTracer.cpp (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/CallTracer.h (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/FancyZones.cpp (96%) rename src/modules/fancyzones/{lib => FancyZonesLib}/FancyZones.h (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/FancyZonesData.cpp (99%) rename src/modules/fancyzones/{lib => FancyZonesLib}/FancyZonesData.h (94%) rename src/modules/fancyzones/{lib => FancyZonesLib}/FancyZonesDataTypes.cpp (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/FancyZonesDataTypes.h (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/FancyZonesLib.vcxproj (96%) rename src/modules/fancyzones/{lib => FancyZonesLib}/FancyZonesLib.vcxproj.filters (96%) rename src/modules/fancyzones/{lib => FancyZonesLib}/FancyZonesWinHookEventIDs.cpp (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/FancyZonesWinHookEventIDs.h (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/FileWatcher.cpp (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/FileWatcher.h (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/GenericKeyHook.h (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/JsonHelpers.cpp (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/JsonHelpers.h (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/KeyState.h (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/LocProject.json (56%) rename src/modules/fancyzones/{lib => FancyZonesLib}/MonitorWorkAreaHandler.cpp (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/MonitorWorkAreaHandler.h (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/OnThreadExecutor.cpp (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/Resources.resx (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/SecondaryMouseButtonsHook.cpp (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/SecondaryMouseButtonsHook.h (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/Settings.cpp (90%) rename src/modules/fancyzones/{lib => FancyZonesLib}/Settings.h (97%) rename src/modules/fancyzones/{lib => FancyZonesLib}/VirtualDesktopUtils.cpp (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/VirtualDesktopUtils.h (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/WindowMoveHandler.cpp (97%) rename src/modules/fancyzones/{lib => FancyZonesLib}/WindowMoveHandler.h (97%) rename src/modules/fancyzones/{lib => FancyZonesLib}/Zone.cpp (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/Zone.h (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/ZoneSet.cpp (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/ZoneSet.h (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/ZoneWindow.cpp (96%) rename src/modules/fancyzones/{lib => FancyZonesLib}/ZoneWindow.h (99%) rename src/modules/fancyzones/{lib => FancyZonesLib}/ZoneWindowDrawing.cpp (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/ZoneWindowDrawing.h (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/fancyzones.base.rc (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/loc/cs/src/modules/fancyzones/lib/Resources.resx.lcl (98%) rename src/modules/fancyzones/{lib => FancyZonesLib}/loc/de/src/modules/fancyzones/lib/Resources.resx.lcl (98%) rename src/modules/fancyzones/{lib => FancyZonesLib}/loc/es/src/modules/fancyzones/lib/Resources.resx.lcl (98%) rename src/modules/fancyzones/{lib => FancyZonesLib}/loc/fr/src/modules/fancyzones/lib/Resources.resx.lcl (98%) rename src/modules/fancyzones/{lib => FancyZonesLib}/loc/hu/src/modules/fancyzones/lib/Resources.resx.lcl (98%) rename src/modules/fancyzones/{lib => FancyZonesLib}/loc/it/src/modules/fancyzones/lib/Resources.resx.lcl (98%) rename src/modules/fancyzones/{lib => FancyZonesLib}/loc/ja/src/modules/fancyzones/lib/Resources.resx.lcl (98%) rename src/modules/fancyzones/{lib => FancyZonesLib}/loc/ko/src/modules/fancyzones/lib/Resources.resx.lcl (98%) rename src/modules/fancyzones/{lib => FancyZonesLib}/loc/nl/src/modules/fancyzones/lib/Resources.resx.lcl (98%) rename src/modules/fancyzones/{lib => FancyZonesLib}/loc/pl/src/modules/fancyzones/lib/Resources.resx.lcl (98%) rename src/modules/fancyzones/{lib => FancyZonesLib}/loc/pt-BR/src/modules/fancyzones/lib/Resources.resx.lcl (98%) rename src/modules/fancyzones/{lib => FancyZonesLib}/loc/pt-PT/src/modules/fancyzones/lib/Resources.resx.lcl (98%) rename src/modules/fancyzones/{lib => FancyZonesLib}/loc/ru/src/modules/fancyzones/lib/Resources.resx.lcl (99%) rename src/modules/fancyzones/{lib => FancyZonesLib}/loc/sv/src/modules/fancyzones/lib/Resources.resx.lcl (98%) rename src/modules/fancyzones/{lib => FancyZonesLib}/loc/tr/src/modules/fancyzones/lib/Resources.resx.lcl (98%) rename src/modules/fancyzones/{lib => FancyZonesLib}/loc/zh-Hans/src/modules/fancyzones/lib/Resources.resx.lcl (98%) rename src/modules/fancyzones/{lib => FancyZonesLib}/loc/zh-Hant/src/modules/fancyzones/lib/Resources.resx.lcl (98%) rename src/modules/fancyzones/{lib => FancyZonesLib}/on_thread_executor.h (100%) rename src/modules/fancyzones/{tests/UnitTests => FancyZonesLib}/packages.config (100%) rename src/modules/fancyzones/{lib => FancyZonesLib}/pch.cpp (97%) rename src/modules/fancyzones/{lib => FancyZonesLib}/pch.h (95%) rename src/modules/fancyzones/{lib => FancyZonesLib}/resource.base.h (73%) rename src/modules/fancyzones/{lib => FancyZonesLib}/trace.cpp (97%) rename src/modules/fancyzones/{lib => FancyZonesLib}/trace.h (93%) rename src/modules/fancyzones/{lib => FancyZonesLib}/util.cpp (99%) rename src/modules/fancyzones/{lib => FancyZonesLib}/util.h (100%) rename src/modules/fancyzones/{dll/FancyZonesModule.vcxproj => FancyZonesModuleInterface/FancyZonesModuleInterface.vcxproj} (94%) rename src/modules/fancyzones/{dll/FancyZonesModule.vcxproj.filters => FancyZonesModuleInterface/FancyZonesModuleInterface.vcxproj.filters} (88%) create mode 100644 src/modules/fancyzones/FancyZonesModuleInterface/dllmain.cpp rename src/modules/fancyzones/{lib => FancyZonesModuleInterface}/packages.config (94%) rename src/modules/fancyzones/{dll => FancyZonesModuleInterface}/pch.cpp (94%) rename src/modules/fancyzones/{dll => FancyZonesModuleInterface}/pch.h (95%) rename src/modules/fancyzones/{dll => FancyZonesModuleInterface}/targetver.h (100%) rename src/modules/fancyzones/{tests => FancyZonesTests}/UnitTests/FancyZones.Spec.cpp (99%) rename src/modules/fancyzones/{tests => FancyZonesTests}/UnitTests/FancyZonesSettings.Spec.cpp (97%) rename src/modules/fancyzones/{tests => FancyZonesTests}/UnitTests/JsonHelpers.Tests.cpp (99%) rename src/modules/fancyzones/{tests => FancyZonesTests}/UnitTests/UnitTests-FancyZones.rc (100%) rename src/modules/fancyzones/{tests => FancyZonesTests}/UnitTests/UnitTests.vcxproj (96%) rename src/modules/fancyzones/{tests => FancyZonesTests}/UnitTests/UnitTests.vcxproj.filters (96%) rename src/modules/fancyzones/{tests => FancyZonesTests}/UnitTests/Util.Spec.cpp (99%) rename src/modules/fancyzones/{tests => FancyZonesTests}/UnitTests/Util.cpp (100%) rename src/modules/fancyzones/{tests => FancyZonesTests}/UnitTests/Util.h (94%) rename src/modules/fancyzones/{tests => FancyZonesTests}/UnitTests/Zone.Spec.cpp (92%) rename src/modules/fancyzones/{tests => FancyZonesTests}/UnitTests/ZoneSet.Spec.cpp (97%) rename src/modules/fancyzones/{tests => FancyZonesTests}/UnitTests/ZoneWindow.Spec.cpp (98%) rename src/modules/fancyzones/{dll => FancyZonesTests/UnitTests}/packages.config (94%) rename src/modules/fancyzones/{tests => FancyZonesTests}/UnitTests/pch.cpp (97%) rename src/modules/fancyzones/{tests => FancyZonesTests}/UnitTests/pch.h (94%) rename src/modules/fancyzones/{tests => FancyZonesTests}/UnitTests/resource.h (100%) delete mode 100644 src/modules/fancyzones/dll/dllmain.cpp delete mode 100644 src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/FancyZonesSettingsIPCMessage.cs delete mode 100644 src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/SndFancyZonesSettings.cs diff --git a/PowerToys.sln b/PowerToys.sln index 6ccb0cab09..f4a534638c 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -39,14 +39,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "interface", "interface", "{ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fancyzones", "fancyzones", "{D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FancyZonesLib", "src\modules\fancyzones\lib\FancyZonesLib.vcxproj", "{F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FancyZonesLib", "src\modules\fancyzones\FancyZonesLib\FancyZonesLib.vcxproj", "{F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fancyzones", "src\modules\fancyzones\dll\FancyZonesModule.vcxproj", "{48804216-2A0E-4168-A6D8-9CD068D14227}" - ProjectSection(ProjectDependencies) = postProject - {5CCC8468-DEC8-4D36-99D4-5C891BEBD481} = {5CCC8468-DEC8-4D36-99D4-5C891BEBD481} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests-FancyZones", "src\modules\fancyzones\tests\UnitTests\UnitTests.vcxproj", "{9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests-FancyZones", "src\modules\fancyzones\FancyZonesTests\UnitTests\UnitTests.vcxproj", "{9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}" ProjectSection(ProjectDependencies) = postProject {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} = {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} EndProjectSection @@ -345,6 +340,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ShortcutGuideModuleInterfac EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ShortcutGuide", "src\modules\ShortcutGuide\ShortcutGuide\ShortcutGuide.vcxproj", "{2EDB3EB4-FA92-4BFF-B2D8-566584837231}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FancyZonesModuleInterface", "src\modules\fancyzones\FancyZonesModuleInterface\FancyZonesModuleInterface.vcxproj", "{48804216-2A0E-4168-A6D8-9CD068D14227}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FancyZones", "src\modules\fancyzones\FancyZones\FancyZones.vcxproj", "{390AE700-B55F-4202-91EA-A822EB75B9BD}" +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerToys.Update", "src\Update\PowerToys.Update.vcxproj", "{44CE9AE1-4390-42C5-BACC-0FD6B40AA203}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.WindowsSettings", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.WindowsSettings\Microsoft.PowerToys.Run.Plugin.WindowsSettings.csproj", "{5043CECE-E6A7-4867-9CBE-02D27D83747A}" @@ -363,10 +362,6 @@ Global {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Debug|x64.Build.0 = Debug|x64 {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Release|x64.ActiveCfg = Release|x64 {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99}.Release|x64.Build.0 = Release|x64 - {48804216-2A0E-4168-A6D8-9CD068D14227}.Debug|x64.ActiveCfg = Debug|x64 - {48804216-2A0E-4168-A6D8-9CD068D14227}.Debug|x64.Build.0 = Debug|x64 - {48804216-2A0E-4168-A6D8-9CD068D14227}.Release|x64.ActiveCfg = Release|x64 - {48804216-2A0E-4168-A6D8-9CD068D14227}.Release|x64.Build.0 = Release|x64 {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}.Debug|x64.ActiveCfg = Debug|x64 {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}.Debug|x64.Build.0 = Debug|x64 {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9}.Release|x64.ActiveCfg = Release|x64 @@ -701,6 +696,14 @@ Global {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Debug|x64.Build.0 = Debug|x64 {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Release|x64.ActiveCfg = Release|x64 {2EDB3EB4-FA92-4BFF-B2D8-566584837231}.Release|x64.Build.0 = Release|x64 + {48804216-2A0E-4168-A6D8-9CD068D14227}.Debug|x64.ActiveCfg = Debug|x64 + {48804216-2A0E-4168-A6D8-9CD068D14227}.Debug|x64.Build.0 = Debug|x64 + {48804216-2A0E-4168-A6D8-9CD068D14227}.Release|x64.ActiveCfg = Release|x64 + {48804216-2A0E-4168-A6D8-9CD068D14227}.Release|x64.Build.0 = Release|x64 + {390AE700-B55F-4202-91EA-A822EB75B9BD}.Debug|x64.ActiveCfg = Debug|x64 + {390AE700-B55F-4202-91EA-A822EB75B9BD}.Debug|x64.Build.0 = Debug|x64 + {390AE700-B55F-4202-91EA-A822EB75B9BD}.Release|x64.ActiveCfg = Release|x64 + {390AE700-B55F-4202-91EA-A822EB75B9BD}.Release|x64.Build.0 = Release|x64 {44CE9AE1-4390-42C5-BACC-0FD6B40AA203}.Debug|x64.ActiveCfg = Debug|x64 {44CE9AE1-4390-42C5-BACC-0FD6B40AA203}.Debug|x64.Build.0 = Debug|x64 {44CE9AE1-4390-42C5-BACC-0FD6B40AA203}.Release|x64.ActiveCfg = Release|x64 @@ -717,7 +720,6 @@ Global {3BB8493E-D18E-4485-A320-CB40F90F55AE} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} - {48804216-2A0E-4168-A6D8-9CD068D14227} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} {1A066C63-64B3-45F8-92FE-664E1CCE8077} = {1AFB6476-670D-4E80-A464-657E01DFF482} {5CCC8468-DEC8-4D36-99D4-5C891BEBD481} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} @@ -814,6 +816,8 @@ Global {106CBECA-0701-4FC3-838C-9DF816A19AE2} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {2D604C07-51FC-46BB-9EB7-75AECC7F5E81} = {106CBECA-0701-4FC3-838C-9DF816A19AE2} {2EDB3EB4-FA92-4BFF-B2D8-566584837231} = {106CBECA-0701-4FC3-838C-9DF816A19AE2} + {48804216-2A0E-4168-A6D8-9CD068D14227} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} + {390AE700-B55F-4202-91EA-A822EB75B9BD} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} {5043CECE-E6A7-4867-9CBE-02D27D83747A} = {4AFC9975-2456-4C70-94A4-84073C1CED93} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index c99638b3c5..3b7fde2275 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -418,11 +418,12 @@ - + + diff --git a/src/common/interop/shared_constants.h b/src/common/interop/shared_constants.h index 32eed0aa9b..176530ce1d 100644 --- a/src/common/interop/shared_constants.h +++ b/src/common/interop/shared_constants.h @@ -26,6 +26,8 @@ namespace CommonSharedConstants const wchar_t SHORTCUT_GUIDE_EXIT_EVENT[] = L"Local\\ShortcutGuide-ExitEvent-35697cdd-a3d2-47d6-a246-34efcc73eac0"; + const wchar_t FANCY_ZONES_EDITOR_TOGGLE_EVENT[] = L"Local\\FancyZones-ToggleEditorEvent-1e174338-06a3-472b-874d-073b21c62f14"; + // Max DWORD for key code to disable keys. const DWORD VK_DISABLED = 0x100; } diff --git a/src/common/utils/UnhandledExceptionHandler_x64.h b/src/common/utils/UnhandledExceptionHandler_x64.h index c8d0cd68dd..0723e45934 100644 --- a/src/common/utils/UnhandledExceptionHandler_x64.h +++ b/src/common/utils/UnhandledExceptionHandler_x64.h @@ -1,8 +1,12 @@ +#pragma once + #include #include #include #include #include + +#include "winapi_error.h" #include "../logger/logger.h" static IMAGEHLP_SYMBOL64* pSymbol = (IMAGEHLP_SYMBOL64*)malloc(sizeof(IMAGEHLP_SYMBOL64) + MAX_PATH * sizeof(TCHAR)); diff --git a/src/modules/fancyzones/FancyZones/FancyZones.vcxproj b/src/modules/fancyzones/FancyZones/FancyZones.vcxproj new file mode 100644 index 0000000000..40b872f210 --- /dev/null +++ b/src/modules/fancyzones/FancyZones/FancyZones.vcxproj @@ -0,0 +1,187 @@ + + + + + + + Debug + x64 + + + Release + x64 + + + + + + true + Use + + + + + + pch.h + Level3 + false + true + stdcpplatest + /await %(AdditionalOptions) + _UNICODE;UNICODE;%(PreprocessorDefinitions) + + + Windows + + + true + + + + + + _DEBUG;%(PreprocessorDefinitions) + Disabled + true + MultiThreadedDebug + + + true + + + + + NDEBUG;%(PreprocessorDefinitions) + MaxSpeed + false + MultiThreaded + true + true + + + true + true + true + + + + + 10.0.17134.0 + 16.0 + Win32Proj + {ff1d7936-842a-4bbb-8bea-e9fe796de700} + FancyZones + + + + Application + v142 + $(SolutionDir)$(Platform)\$(Configuration)\obj\$(ProjectName)\ + Unicode + Spectre + + + + true + true + + + false + true + false + + + + + + + + + + + + + + + true + PowerToys.$(MSBuildProjectName) + $(SolutionDir)$(Platform)\$(Configuration)\modules\FancyZones\ + + + false + PowerToys.$(MSBuildProjectName) + $(SolutionDir)$(Platform)\$(Configuration)\modules\FancyZones\ + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + ./../;$(SolutionDir)src\common\Telemetry;$(SolutionDir)src\common;$(SolutionDir)src\;./;%(AdditionalIncludeDirectories) + + + Windows + true + shcore.lib;shlwapi.lib;DbgHelp.lib;uxtheme.lib;dwmapi.lib;%(AdditionalDependencies) + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + ./../;$(SolutionDir)src\common\Telemetry;$(SolutionDir)src\common;$(SolutionDir)src\;./;%(AdditionalIncludeDirectories) + + + Windows + true + true + true + shcore.lib;shlwapi.lib;DbgHelp.lib;uxtheme.lib;dwmapi.lib;%(AdditionalDependencies) + + + + + + + Create + Create + + + + + + + + + + + + + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + + {f9c68edf-ac74-4b77-9af1-005d9c9f6a99} + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZones/FancyZones.vcxproj.filters b/src/modules/fancyzones/FancyZones/FancyZones.vcxproj.filters new file mode 100644 index 0000000000..3f34fdfd49 --- /dev/null +++ b/src/modules/fancyzones/FancyZones/FancyZones.vcxproj.filters @@ -0,0 +1,42 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + + + + + + Header Files + + + Header Files + + + + + + \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZones/FancyZonesApp.cpp b/src/modules/fancyzones/FancyZones/FancyZonesApp.cpp new file mode 100644 index 0000000000..d0730b60ce --- /dev/null +++ b/src/modules/fancyzones/FancyZones/FancyZonesApp.cpp @@ -0,0 +1,183 @@ +#include "pch.h" +#include "FancyZonesApp.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + + +FancyZonesApp::FancyZonesApp(const std::wstring& appName, const std::wstring& appKey) +{ + DPIAware::EnableDPIAwarenessForThisProcess(); + + m_settings = MakeFancyZonesSettings(reinterpret_cast(&__ImageBase), appName.c_str(), appKey.c_str()); + FancyZonesDataInstance().LoadFancyZonesData(); + + InitializeWinhookEventIds(); + m_app = MakeFancyZones(reinterpret_cast(&__ImageBase), m_settings, std::bind(&FancyZonesApp::DisableModule, this)); + + InitHooks(); + + s_instance = this; +} + +FancyZonesApp::~FancyZonesApp() +{ + if (m_app) + { + m_app->Destroy(); + m_app = nullptr; + + if (s_llKeyboardHook) + { + if (UnhookWindowsHookEx(s_llKeyboardHook)) + { + s_llKeyboardHook = nullptr; + } + } + + m_staticWinEventHooks.erase(std::remove_if(begin(m_staticWinEventHooks), + end(m_staticWinEventHooks), + [](const HWINEVENTHOOK hook) { + return UnhookWinEvent(hook); + }), + end(m_staticWinEventHooks)); + if (m_objectLocationWinEventHook) + { + if (UnhookWinEvent(m_objectLocationWinEventHook)) + { + m_objectLocationWinEventHook = nullptr; + } + } + } +} + +void FancyZonesApp::Run() +{ + if (m_app) + { + m_app->Run(); + } +} + +void FancyZonesApp::InitHooks() +{ +#if defined(DISABLE_LOWLEVEL_HOOKS_WHEN_DEBUGGED) + const bool hook_disabled = IsDebuggerPresent(); +#else + const bool hook_disabled = false; +#endif + + if (!hook_disabled) + { + s_llKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), NULL); + if (!s_llKeyboardHook) + { + DWORD errorCode = GetLastError(); + show_last_error_message(L"SetWindowsHookEx", errorCode, GET_RESOURCE_STRING(IDS_POWERTOYS_FANCYZONES).c_str()); + auto errorMessage = get_last_error_message(errorCode); + Trace::FancyZones::Error(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"enable.SetWindowsHookEx"); + } + } + + std::array events_to_subscribe = { + EVENT_SYSTEM_MOVESIZESTART, + EVENT_SYSTEM_MOVESIZEEND, + EVENT_OBJECT_NAMECHANGE, + EVENT_OBJECT_UNCLOAKED, + EVENT_OBJECT_SHOW, + EVENT_OBJECT_CREATE + }; + for (const auto event : events_to_subscribe) + { + auto hook = SetWinEventHook(event, event, nullptr, WinHookProc, 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); + if (hook) + { + m_staticWinEventHooks.emplace_back(hook); + } + else + { + MessageBoxW(NULL, + GET_RESOURCE_STRING(IDS_WINDOW_EVENT_LISTENER_ERROR).c_str(), + GET_RESOURCE_STRING(IDS_POWERTOYS_FANCYZONES).c_str(), + MB_OK | MB_ICONERROR); + } + } +} + +void FancyZonesApp::DisableModule() noexcept +{ + PostQuitMessage(0); +} + +void FancyZonesApp::HandleWinHookEvent(WinHookEvent* data) noexcept +{ + auto fzCallback = m_app.as(); + switch (data->event) + { + case EVENT_SYSTEM_MOVESIZESTART: + { + fzCallback->HandleWinHookEvent(data); + if (!m_objectLocationWinEventHook) + { + m_objectLocationWinEventHook = SetWinEventHook(EVENT_OBJECT_LOCATIONCHANGE, + EVENT_OBJECT_LOCATIONCHANGE, + nullptr, + WinHookProc, + 0, + 0, + WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); + } + } + break; + + case EVENT_SYSTEM_MOVESIZEEND: + { + if (UnhookWinEvent(m_objectLocationWinEventHook)) + { + m_objectLocationWinEventHook = nullptr; + } + fzCallback->HandleWinHookEvent(data); + } + break; + + case EVENT_OBJECT_LOCATIONCHANGE: + { + fzCallback->HandleWinHookEvent(data); + } + break; + + case EVENT_OBJECT_NAMECHANGE: + { + // The accessibility name of the desktop window changes whenever the user + // switches virtual desktops. + if (data->hwnd == GetDesktopWindow()) + { + m_app.as()->VirtualDesktopChanged(); + } + } + break; + + case EVENT_OBJECT_UNCLOAKED: + case EVENT_OBJECT_SHOW: + case EVENT_OBJECT_CREATE: + { + fzCallback->HandleWinHookEvent(data); + } + break; + + default: + break; + } +} + +intptr_t FancyZonesApp::HandleKeyboardHookEvent(LowlevelKeyboardEvent* data) noexcept +{ + return m_app.as()->OnKeyDown(data->lParam); +} diff --git a/src/modules/fancyzones/FancyZones/FancyZonesApp.h b/src/modules/fancyzones/FancyZones/FancyZonesApp.h new file mode 100644 index 0000000000..b327d8c052 --- /dev/null +++ b/src/modules/fancyzones/FancyZones/FancyZonesApp.h @@ -0,0 +1,63 @@ +#pragma once + +#include + +#include + +class FancyZonesApp +{ +public: + FancyZonesApp(const std::wstring& appName, const std::wstring& appKey); + ~FancyZonesApp(); + + void Run(); + +private: + static inline FancyZonesApp* s_instance = nullptr; + static inline HHOOK s_llKeyboardHook = nullptr; + + winrt::com_ptr m_app; + HWINEVENTHOOK m_objectLocationWinEventHook = nullptr; + std::vector m_staticWinEventHooks; + winrt::com_ptr m_settings; + + void DisableModule() noexcept; + + void InitHooks(); + + void HandleWinHookEvent(WinHookEvent* data) noexcept; + intptr_t HandleKeyboardHookEvent(LowlevelKeyboardEvent* data) noexcept; + + static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) + { + LowlevelKeyboardEvent event; + if (nCode == HC_ACTION && wParam == WM_KEYDOWN) + { + event.lParam = reinterpret_cast(lParam); + event.wParam = wParam; + if (s_instance) + { + if (s_instance->HandleKeyboardHookEvent(&event) == 1) + { + return 1; + } + } + } + return CallNextHookEx(NULL, nCode, wParam, lParam); + } + + static void CALLBACK WinHookProc(HWINEVENTHOOK winEventHook, + DWORD event, + HWND window, + LONG object, + LONG child, + DWORD eventThread, + DWORD eventTime) + { + WinHookEvent data{ event, window, object, child, eventThread, eventTime }; + if (s_instance) + { + s_instance->HandleWinHookEvent(&data); + } + } +}; diff --git a/src/modules/fancyzones/FancyZones/main.cpp b/src/modules/fancyzones/FancyZones/main.cpp new file mode 100644 index 0000000000..34708f11bf --- /dev/null +++ b/src/modules/fancyzones/FancyZones/main.cpp @@ -0,0 +1,73 @@ +#include "pch.h" + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +// Non-localizable +const std::wstring moduleName = L"FancyZones"; +const std::wstring internalPath = L""; +const std::wstring instanceMutexName = L"Local\\PowerToys_FancyZones_InstanceMutex"; + +int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ PWSTR lpCmdLine, _In_ int nCmdShow) +{ + winrt::init_apartment(); + InitUnhandledExceptionHandler_x64(); + + LoggerHelpers::init_logger(moduleName, internalPath, LogSettings::fancyZonesLoggerName); + + auto mutex = CreateMutex(nullptr, true, instanceMutexName.c_str()); + if (mutex == nullptr) + { + Logger::error(L"Failed to create mutex. {}", get_last_error_or_default(GetLastError())); + } + + if (GetLastError() == ERROR_ALREADY_EXISTS) + { + Logger::warn(L"FancyZones instance is already running"); + return 0; + } + + std::wstring pid = std::wstring(lpCmdLine); + if (!pid.empty()) + { + auto mainThreadId = GetCurrentThreadId(); + ProcessWaiter::OnProcessTerminate(pid, [mainThreadId](int err) { + if (err != ERROR_SUCCESS) + { + Logger::error(L"Failed to wait for parent process exit. {}", get_last_error_or_default(err)); + } + else + { + Logger::trace(L"PowerToys runner exited."); + } + + Logger::trace(L"Exiting FancyZones"); + PostThreadMessage(mainThreadId, WM_QUIT, 0, 0); + }); + } + + Trace::RegisterProvider(); + + FancyZonesApp app(GET_RESOURCE_STRING(IDS_FANCYZONES), NonLocalizable::FancyZonesStr); + app.Run(); + + run_message_loop(); + + Trace::UnregisterProvider(); + + return 0; +} diff --git a/src/modules/fancyzones/FancyZones/packages.config b/src/modules/fancyzones/FancyZones/packages.config new file mode 100644 index 0000000000..d758b61ef1 --- /dev/null +++ b/src/modules/fancyzones/FancyZones/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZones/pch.cpp b/src/modules/fancyzones/FancyZones/pch.cpp new file mode 100644 index 0000000000..bcb5590be1 --- /dev/null +++ b/src/modules/fancyzones/FancyZones/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/src/modules/fancyzones/FancyZones/pch.h b/src/modules/fancyzones/FancyZones/pch.h new file mode 100644 index 0000000000..472d051e25 --- /dev/null +++ b/src/modules/fancyzones/FancyZones/pch.h @@ -0,0 +1,10 @@ +#pragma once +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include +#include +#include \ No newline at end of file diff --git a/src/modules/fancyzones/lib/CallTracer.cpp b/src/modules/fancyzones/FancyZonesLib/CallTracer.cpp similarity index 100% rename from src/modules/fancyzones/lib/CallTracer.cpp rename to src/modules/fancyzones/FancyZonesLib/CallTracer.cpp diff --git a/src/modules/fancyzones/lib/CallTracer.h b/src/modules/fancyzones/FancyZonesLib/CallTracer.h similarity index 100% rename from src/modules/fancyzones/lib/CallTracer.h rename to src/modules/fancyzones/FancyZonesLib/CallTracer.h diff --git a/src/modules/fancyzones/lib/FancyZones.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp similarity index 96% rename from src/modules/fancyzones/lib/FancyZones.cpp rename to src/modules/fancyzones/FancyZonesLib/FancyZones.cpp index 6665b28322..edad77583e 100644 --- a/src/modules/fancyzones/lib/FancyZones.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp @@ -1,1440 +1,1474 @@ -#include "pch.h" - -#include -#include -#include -#include - -#include "FancyZones.h" -#include "lib/Settings.h" -#include "lib/ZoneWindow.h" -#include "lib/FancyZonesData.h" -#include "lib/ZoneSet.h" -#include "lib/FileWatcher.h" -#include "lib/WindowMoveHandler.h" -#include "lib/FancyZonesWinHookEventIDs.h" -#include "lib/util.h" -#include "on_thread_executor.h" -#include "trace.h" -#include "VirtualDesktopUtils.h" -#include "MonitorWorkAreaHandler.h" -#include "util.h" -#include "CallTracer.h" - -#include - -enum class DisplayChangeType -{ - WorkArea, - DisplayChange, - VirtualDesktop, - Initialization -}; - -namespace -{ - constexpr int CUSTOM_POSITIONING_LEFT_TOP_PADDING = 16; - - struct require_read_lock - { - template - require_read_lock(const std::shared_lock& lock) - { - lock; - } - - template - require_read_lock(const std::unique_lock& lock) - { - lock; - } - }; - - struct require_write_lock - { - template - require_write_lock(const std::unique_lock& lock) - { - lock; - } - }; -} - -// Non-localizable strings -namespace NonLocalizable -{ - const wchar_t ToolWindowClassName[] = L"SuperFancyZones"; - const wchar_t FZEditorExecutablePath[] = L"modules\\FancyZones\\FancyZonesEditor.exe"; - const wchar_t SplashClassName[] = L"MsoSplash"; -} - -struct FancyZones : public winrt::implements -{ -public: - FancyZones(HINSTANCE hinstance, const winrt::com_ptr& settings, std::function disableModuleCallback) noexcept : - m_hinstance(hinstance), - m_settings(settings), - m_windowMoveHandler(settings, [this]() { - PostMessageW(m_window, WM_PRIV_LOCATIONCHANGE, NULL, NULL); - }), - m_fileWatcher(FancyZonesDataInstance().GetZonesSettingsFileName(), [this]() { - PostMessageW(m_window, WM_PRIV_FILE_UPDATE, NULL, NULL); - }) - { - m_settings->SetCallback(this); - - this->disableModuleCallback = std::move(disableModuleCallback); - } - - // IFancyZones - IFACEMETHODIMP_(void) - Run() noexcept; - IFACEMETHODIMP_(void) - Destroy() noexcept; - - void MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen) noexcept - { - std::unique_lock writeLock(m_lock); - if (m_settings->GetSettings()->spanZonesAcrossMonitors) - { - monitor = NULL; - } - m_windowMoveHandler.MoveSizeStart(window, monitor, ptScreen, m_workAreaHandler.GetWorkAreasByDesktopId(m_currentDesktopId)); - } - - void MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen) noexcept - { - std::unique_lock writeLock(m_lock); - if (m_settings->GetSettings()->spanZonesAcrossMonitors) - { - monitor = NULL; - } - m_windowMoveHandler.MoveSizeUpdate(monitor, ptScreen, m_workAreaHandler.GetWorkAreasByDesktopId(m_currentDesktopId)); - } - - void MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept - { - _TRACER_; - std::unique_lock writeLock(m_lock); - m_windowMoveHandler.MoveSizeEnd(window, ptScreen, m_workAreaHandler.GetWorkAreasByDesktopId(m_currentDesktopId)); - } - - IFACEMETHODIMP_(void) - HandleWinHookEvent(const WinHookEvent* data) noexcept - { - const auto wparam = reinterpret_cast(data->hwnd); - const LONG lparam = 0; - std::shared_lock readLock(m_lock); - switch (data->event) - { - case EVENT_SYSTEM_MOVESIZESTART: - PostMessageW(m_window, WM_PRIV_MOVESIZESTART, wparam, lparam); - break; - case EVENT_SYSTEM_MOVESIZEEND: - PostMessageW(m_window, WM_PRIV_MOVESIZEEND, wparam, lparam); - break; - case EVENT_OBJECT_LOCATIONCHANGE: - PostMessageW(m_window, WM_PRIV_LOCATIONCHANGE, wparam, lparam); - break; - case EVENT_OBJECT_NAMECHANGE: - PostMessageW(m_window, WM_PRIV_NAMECHANGE, wparam, lparam); - break; - - case EVENT_OBJECT_UNCLOAKED: - case EVENT_OBJECT_SHOW: - case EVENT_OBJECT_CREATE: - if (data->idObject == OBJID_WINDOW) - { - PostMessageW(m_window, WM_PRIV_WINDOWCREATED, wparam, lparam); - } - break; - } - } - - IFACEMETHODIMP_(void) - VirtualDesktopChanged() noexcept; - IFACEMETHODIMP_(void) - VirtualDesktopInitialize() noexcept; - IFACEMETHODIMP_(bool) - OnKeyDown(PKBDLLHOOKSTRUCT info) noexcept; - IFACEMETHODIMP_(void) - ToggleEditor() noexcept; - IFACEMETHODIMP_(void) - SettingsChanged() noexcept; - - void WindowCreated(HWND window) noexcept; - - // IZoneWindowHost - IFACEMETHODIMP_(void) - MoveWindowsOnActiveZoneSetChange() noexcept; - IFACEMETHODIMP_(COLORREF) - GetZoneColor() noexcept - { - return (FancyZonesUtils::HexToRGB(m_settings->GetSettings()->zoneColor)); - } - IFACEMETHODIMP_(COLORREF) - GetZoneBorderColor() noexcept - { - return (FancyZonesUtils::HexToRGB(m_settings->GetSettings()->zoneBorderColor)); - } - IFACEMETHODIMP_(COLORREF) - GetZoneHighlightColor() noexcept - { - return (FancyZonesUtils::HexToRGB(m_settings->GetSettings()->zoneHighlightColor)); - } - IFACEMETHODIMP_(int) - GetZoneHighlightOpacity() noexcept - { - return m_settings->GetSettings()->zoneHighlightOpacity; - } - - IFACEMETHODIMP_(bool) - isMakeDraggedWindowTransparentActive() noexcept - { - return m_settings->GetSettings()->makeDraggedWindowTransparent; - } - - IFACEMETHODIMP_(bool) - InMoveSize() noexcept - { - std::shared_lock readLock(m_lock); - return m_windowMoveHandler.InMoveSize(); - } - - IFACEMETHODIMP_(Settings::OverlappingZonesAlgorithm) - GetOverlappingZonesAlgorithm() noexcept - { - return m_settings->GetSettings()->overlappingZonesAlgorithm; - } - - LRESULT WndProc(HWND, UINT, WPARAM, LPARAM) noexcept; - void OnDisplayChange(DisplayChangeType changeType, require_write_lock) noexcept; - void AddZoneWindow(HMONITOR monitor, const std::wstring& deviceId, require_write_lock) noexcept; - -protected: - static LRESULT CALLBACK s_WndProc(HWND, UINT, WPARAM, LPARAM) noexcept; - -private: - - void UpdateZoneWindows(require_write_lock) noexcept; - void UpdateWindowsPositions(require_write_lock) noexcept; - bool OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexcept; - bool OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept; - bool OnSnapHotkey(DWORD vkCode) noexcept; - bool ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr zoneWindow) noexcept; - - void RegisterVirtualDesktopUpdates(std::vector& ids) noexcept; - - bool IsSplashScreen(HWND window); - bool ShouldProcessNewWindow(HWND window) noexcept; - std::vector GetZoneIndexSetFromWorkAreaHistory(HWND window, winrt::com_ptr workArea) noexcept; - std::pair, std::vector> GetAppZoneHistoryInfo(HWND window, HMONITOR monitor, std::unordered_map>& workAreaMap) noexcept; - std::pair, std::vector> GetAppZoneHistoryInfo(HWND window, HMONITOR monitor, bool isPrimaryMonitor) noexcept; - void MoveWindowIntoZone(HWND window, winrt::com_ptr zoneWindow, const std::vector& zoneIndexSet) noexcept; - - void OnEditorExitEvent(require_write_lock) noexcept; - void UpdateZoneSets(require_write_lock) noexcept; - bool ShouldProcessSnapHotkey(DWORD vkCode) noexcept; - void ApplyQuickLayout(int key) noexcept; - void FlashZones(require_write_lock) noexcept; - - std::vector> GetRawMonitorData() noexcept; - std::vector GetMonitorsSorted() noexcept; - HMONITOR WorkAreaKeyFromWindow(HWND window) noexcept; - - const HINSTANCE m_hinstance{}; - - mutable std::shared_mutex m_lock; - HWND m_window{}; - WindowMoveHandler m_windowMoveHandler; - MonitorWorkAreaHandler m_workAreaHandler; - FileWatcher m_fileWatcher; - - winrt::com_ptr m_settings{}; - GUID m_previousDesktopId{}; // UUID of previously active virtual desktop. - GUID m_currentDesktopId{}; // UUID of the current virtual desktop. - wil::unique_handle m_terminateEditorEvent; // Handle of FancyZonesEditor.exe we launch and wait on - wil::unique_handle m_terminateVirtualDesktopTrackerEvent; - - OnThreadExecutor m_dpiUnawareThread; - OnThreadExecutor m_virtualDesktopTrackerThread; - - // If non-recoverable error occurs, trigger disabling of entire FancyZones. - static std::function disableModuleCallback; - - static UINT WM_PRIV_VD_INIT; // Scheduled when FancyZones is initialized - static UINT WM_PRIV_VD_SWITCH; // Scheduled when virtual desktop switch occurs - static UINT WM_PRIV_VD_UPDATE; // Scheduled on virtual desktops update (creation/deletion) - static UINT WM_PRIV_EDITOR; // Scheduled when the editor exits - static UINT WM_PRIV_FILE_UPDATE; // Scheduled when the a watched file is updated - - static UINT WM_PRIV_SNAP_HOTKEY; // Scheduled when we receive a snap hotkey key down press - static UINT WM_PRIV_QUICK_LAYOUT_KEY; // Scheduled when we receive a key down press to quickly apply a layout - - // Did we terminate the editor or was it closed cleanly? - enum class EditorExitKind : byte - { - Exit, - Terminate - }; -}; - -std::function FancyZones::disableModuleCallback = {}; - -UINT FancyZones::WM_PRIV_VD_INIT = RegisterWindowMessage(L"{469818a8-00fa-4069-b867-a1da484fcd9a}"); -UINT FancyZones::WM_PRIV_VD_SWITCH = RegisterWindowMessage(L"{128c2cb0-6bdf-493e-abbe-f8705e04aa95}"); -UINT FancyZones::WM_PRIV_VD_UPDATE = RegisterWindowMessage(L"{b8b72b46-f42f-4c26-9e20-29336cf2f22e}"); -UINT FancyZones::WM_PRIV_EDITOR = RegisterWindowMessage(L"{87543824-7080-4e91-9d9c-0404642fc7b6}"); -UINT FancyZones::WM_PRIV_FILE_UPDATE = RegisterWindowMessage(L"{632f17a9-55a7-45f1-a4db-162e39271d92}"); -UINT FancyZones::WM_PRIV_SNAP_HOTKEY = RegisterWindowMessage(L"{763c03a3-03d9-4cde-8d71-f0358b0b4b52}"); -UINT FancyZones::WM_PRIV_QUICK_LAYOUT_KEY = RegisterWindowMessage(L"{72f4fd8e-23f1-43ab-bbbc-029363df9a84}"); - -// IFancyZones -IFACEMETHODIMP_(void) -FancyZones::Run() noexcept -{ - std::unique_lock writeLock(m_lock); - - WNDCLASSEXW wcex{}; - wcex.cbSize = sizeof(WNDCLASSEX); - wcex.lpfnWndProc = s_WndProc; - wcex.hInstance = m_hinstance; - wcex.lpszClassName = NonLocalizable::ToolWindowClassName; - RegisterClassExW(&wcex); - - BufferedPaintInit(); - - m_window = CreateWindowExW(WS_EX_TOOLWINDOW, NonLocalizable::ToolWindowClassName, L"", WS_POPUP, 0, 0, 0, 0, nullptr, nullptr, m_hinstance, this); - if (!m_window) - { - return; - } - - RegisterHotKey(m_window, 1, m_settings->GetSettings()->editorHotkey.get_modifiers(), m_settings->GetSettings()->editorHotkey.get_code()); - - VirtualDesktopInitialize(); - - m_dpiUnawareThread.submit(OnThreadExecutor::task_t{ [] { - SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE); - SetThreadDpiHostingBehavior(DPI_HOSTING_BEHAVIOR_MIXED); - } }) - .wait(); - - m_terminateVirtualDesktopTrackerEvent.reset(CreateEvent(nullptr, FALSE, FALSE, nullptr)); - m_virtualDesktopTrackerThread.submit(OnThreadExecutor::task_t{ [&] { VirtualDesktopUtils::HandleVirtualDesktopUpdates(m_window, WM_PRIV_VD_UPDATE, m_terminateVirtualDesktopTrackerEvent.get()); } }); -} - -// IFancyZones -IFACEMETHODIMP_(void) -FancyZones::Destroy() noexcept -{ - std::unique_lock writeLock(m_lock); - m_workAreaHandler.Clear(); - BufferedPaintUnInit(); - if (m_window) - { - DestroyWindow(m_window); - m_window = nullptr; - } - if (m_terminateVirtualDesktopTrackerEvent) - { - SetEvent(m_terminateVirtualDesktopTrackerEvent.get()); - } -} - -// IFancyZonesCallback -IFACEMETHODIMP_(void) -FancyZones::VirtualDesktopChanged() noexcept -{ - // VirtualDesktopChanged is called from a reentrant WinHookProc function, therefore we must postpone the actual logic - // until we're in FancyZones::WndProc, which is not reentrant. - PostMessage(m_window, WM_PRIV_VD_SWITCH, 0, 0); -} - -// IFancyZonesCallback -IFACEMETHODIMP_(void) -FancyZones::VirtualDesktopInitialize() noexcept -{ - PostMessage(m_window, WM_PRIV_VD_INIT, 0, 0); -} - -bool FancyZones::ShouldProcessNewWindow(HWND window) noexcept -{ - using namespace FancyZonesUtils; - // Avoid processing splash screens, already stamped (zoned) windows, or those windows - // that belong to excluded applications list. - if (IsSplashScreen(window) || - (reinterpret_cast(::GetProp(window, ZonedWindowProperties::PropertyMultipleZoneID)) != 0) || - !IsCandidateForLastKnownZone(window, m_settings->GetSettings()->excludedAppsArray)) - { - return false; - } - return true; -} - -std::vector FancyZones::GetZoneIndexSetFromWorkAreaHistory( - HWND window, - winrt::com_ptr workArea) noexcept -{ - const auto activeZoneSet = workArea->ActiveZoneSet(); - if (activeZoneSet) - { - wil::unique_cotaskmem_string zoneSetId; - if (SUCCEEDED(StringFromCLSID(activeZoneSet->Id(), &zoneSetId))) - { - return FancyZonesDataInstance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), zoneSetId.get()); - } - } - return {}; -} - -std::pair, std::vector> FancyZones::GetAppZoneHistoryInfo( - HWND window, - HMONITOR monitor, - std::unordered_map>& workAreaMap) noexcept -{ - if (workAreaMap.contains(monitor)) - { - auto workArea = workAreaMap[monitor]; - workAreaMap.erase(monitor); // monitor processed, remove entry from the map - return { workArea, GetZoneIndexSetFromWorkAreaHistory(window, workArea) }; - } - return { nullptr, {} }; -} - -std::pair, std::vector> FancyZones::GetAppZoneHistoryInfo(HWND window, HMONITOR monitor, bool isPrimaryMonitor) noexcept -{ - std::pair, std::vector> appZoneHistoryInfo{ nullptr, {} }; - auto workAreaMap = m_workAreaHandler.GetWorkAreasByDesktopId(m_currentDesktopId); - - // Search application history on currently active monitor. - appZoneHistoryInfo = GetAppZoneHistoryInfo(window, monitor, workAreaMap); - - if (isPrimaryMonitor && appZoneHistoryInfo.second.empty()) - { - // No application history on primary monitor, search on remaining monitors. - for (const auto& [monitor, workArea] : workAreaMap) - { - auto zoneIndexSet = GetZoneIndexSetFromWorkAreaHistory(window, workArea); - if (!zoneIndexSet.empty()) - { - return { workArea, zoneIndexSet }; - } - } - } - - return appZoneHistoryInfo; -} - -void FancyZones::MoveWindowIntoZone(HWND window, winrt::com_ptr zoneWindow, const std::vector& zoneIndexSet) noexcept -{ - _TRACER_; - auto& fancyZonesData = FancyZonesDataInstance(); - if (!fancyZonesData.IsAnotherWindowOfApplicationInstanceZoned(window, zoneWindow->UniqueId())) - { - m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, zoneIndexSet, zoneWindow); - fancyZonesData.UpdateProcessIdToHandleMap(window, zoneWindow->UniqueId()); - } -} - -inline int RectWidth(const RECT& rect) -{ - return rect.right - rect.left; -} - -inline int RectHeight(const RECT& rect) -{ - return rect.bottom - rect.top; -} - -RECT FitOnScreen(const RECT& windowRect, const RECT& originMonitorRect, const RECT& destMonitorRect) -{ - // New window position on active monitor. If window fits the screen, this will be final position. - int left = destMonitorRect.left + (windowRect.left - originMonitorRect.left); - int top = destMonitorRect.top + (windowRect.top - originMonitorRect.top); - int W = RectWidth(windowRect); - int H = RectHeight(windowRect); - - if ((left < destMonitorRect.left) || (left + W > destMonitorRect.right)) - { - // Set left window border to left border of screen (add padding). Resize window width if needed. - left = destMonitorRect.left + CUSTOM_POSITIONING_LEFT_TOP_PADDING; - W = min(W, RectWidth(destMonitorRect) - CUSTOM_POSITIONING_LEFT_TOP_PADDING); - } - if ((top < destMonitorRect.top) || (top + H > destMonitorRect.bottom)) - { - // Set top window border to top border of screen (add padding). Resize window height if needed. - top = destMonitorRect.top + CUSTOM_POSITIONING_LEFT_TOP_PADDING; - H = min(H, RectHeight(destMonitorRect) - CUSTOM_POSITIONING_LEFT_TOP_PADDING); - } - - return { .left = left, - .top = top, - .right = left + W, - .bottom = top + H }; -} - -void OpenWindowOnActiveMonitor(HWND window, HMONITOR monitor) noexcept -{ - // By default Windows opens new window on primary monitor. - // Try to preserve window width and height, adjust top-left corner if needed. - HMONITOR origin = MonitorFromWindow(window, MONITOR_DEFAULTTOPRIMARY); - if (origin == monitor) - { - // Certain applications by design open in last known position, regardless of FancyZones. - // If that position is on currently active monitor, skip custom positioning. - return; - } - - WINDOWPLACEMENT placement{}; - if (GetWindowPlacement(window, &placement)) - { - MONITORINFOEX originMi; - originMi.cbSize = sizeof(originMi); - if (GetMonitorInfo(origin, &originMi)) - { - MONITORINFOEX destMi; - destMi.cbSize = sizeof(destMi); - if (GetMonitorInfo(monitor, &destMi)) - { - RECT newPosition = FitOnScreen(placement.rcNormalPosition, originMi.rcWork, destMi.rcWork); - FancyZonesUtils::SizeWindowToRect(window, newPosition); - } - } - } -} - -// IFancyZonesCallback -IFACEMETHODIMP_(void) -FancyZones::WindowCreated(HWND window) noexcept -{ - std::shared_lock readLock(m_lock); - GUID desktopId{}; - if (VirtualDesktopUtils::GetWindowDesktopId(window, &desktopId) && desktopId != m_currentDesktopId) - { - // Switch between virtual desktops results with posting same windows messages that also indicate - // creation of new window. We need to check if window being processed is on currently active desktop. - return; - } - const bool moveToAppLastZone = m_settings->GetSettings()->appLastZone_moveWindows; - const bool openOnActiveMonitor = m_settings->GetSettings()->openWindowOnActiveMonitor; - if ((moveToAppLastZone || openOnActiveMonitor) && ShouldProcessNewWindow(window)) - { - HMONITOR primary = MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY); - HMONITOR active = primary; - - POINT cursorPosition{}; - if (GetCursorPos(&cursorPosition)) - { - active = MonitorFromPoint(cursorPosition, MONITOR_DEFAULTTOPRIMARY); - } - - bool windowZoned{ false }; - if (moveToAppLastZone) - { - const bool primaryActive = (primary == active); - std::pair, std::vector> appZoneHistoryInfo = GetAppZoneHistoryInfo(window, active, primaryActive); - if (!appZoneHistoryInfo.second.empty()) - { - MoveWindowIntoZone(window, appZoneHistoryInfo.first, appZoneHistoryInfo.second); - windowZoned = true; - } - } - if (!windowZoned && openOnActiveMonitor) - { - m_dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] { OpenWindowOnActiveMonitor(window, active); } }).wait(); - } - } -} - -// IFancyZonesCallback -IFACEMETHODIMP_(bool) -FancyZones::OnKeyDown(PKBDLLHOOKSTRUCT info) noexcept -{ - // Return true to swallow the keyboard event - bool const shift = GetAsyncKeyState(VK_SHIFT) & 0x8000; - bool const win = GetAsyncKeyState(VK_LWIN) & 0x8000 || GetAsyncKeyState(VK_RWIN) & 0x8000; - bool const alt = GetAsyncKeyState(VK_MENU) & 0x8000; - bool const ctrl = GetAsyncKeyState(VK_CONTROL) & 0x8000; - if ((win && !shift && !ctrl) || (win && ctrl && alt)) - { - if ((info->vkCode == VK_RIGHT) || (info->vkCode == VK_LEFT) || (info->vkCode == VK_UP) || (info->vkCode == VK_DOWN)) - { - if (ShouldProcessSnapHotkey(info->vkCode)) - { - Trace::FancyZones::OnKeyDown(info->vkCode, win, ctrl, false /*inMoveSize*/); - // Win+Left, Win+Right will cycle through Zones in the active ZoneSet when WM_PRIV_SNAP_HOTKEY's handled - PostMessageW(m_window, WM_PRIV_SNAP_HOTKEY, 0, info->vkCode); - return true; - } - } - } - - if (m_settings->GetSettings()->quickLayoutSwitch) - { - int digitPressed = -1; - if ('0' <= info->vkCode && info->vkCode <= '9') - { - digitPressed = info->vkCode - '0'; - } - else if (VK_NUMPAD0 <= info->vkCode && info->vkCode <= VK_NUMPAD9) - { - digitPressed = info->vkCode - VK_NUMPAD0; - } - - bool dragging = m_windowMoveHandler.InMoveSize(); - bool changeLayoutWhileNotDragging = !dragging && !shift && win && ctrl && alt && digitPressed != -1; - bool changeLayoutWhileDragging = dragging && digitPressed != -1; - - if (changeLayoutWhileNotDragging || changeLayoutWhileDragging) - { - auto quickKeysMap = FancyZonesDataInstance().GetLayoutQuickKeys(); - if (std::any_of(quickKeysMap.begin(), quickKeysMap.end(), [=](auto item) { return item.second == digitPressed; })) - { - PostMessageW(m_window, WM_PRIV_QUICK_LAYOUT_KEY, 0, static_cast(digitPressed)); - Trace::FancyZones::QuickLayoutSwitched(changeLayoutWhileNotDragging); - return true; - } - } - } - - if (m_windowMoveHandler.IsDragEnabled() && shift) - { - return true; - } - return false; -} - -// IFancyZonesCallback -void FancyZones::ToggleEditor() noexcept -{ - _TRACER_; - { - std::shared_lock readLock(m_lock); - if (m_terminateEditorEvent) - { - SetEvent(m_terminateEditorEvent.get()); - return; - } - } - - { - std::unique_lock writeLock(m_lock); - m_terminateEditorEvent.reset(CreateEvent(nullptr, true, false, nullptr)); - } - - std::shared_lock readLock(m_lock); - - HMONITOR targetMonitor{}; - - const bool use_cursorpos_editor_startupscreen = m_settings->GetSettings()->use_cursorpos_editor_startupscreen; - if (use_cursorpos_editor_startupscreen) - { - POINT currentCursorPos{}; - GetCursorPos(¤tCursorPos); - targetMonitor = MonitorFromPoint(currentCursorPos, MONITOR_DEFAULTTOPRIMARY); - } - else - { - targetMonitor = MonitorFromWindow(GetForegroundWindow(), MONITOR_DEFAULTTOPRIMARY); - } - - if (!targetMonitor) - { - return; - } - - wil::unique_cotaskmem_string virtualDesktopId; - if (!SUCCEEDED(StringFromCLSID(m_currentDesktopId, &virtualDesktopId))) - { - return; - } - - /* - * Divider: / - * Parts: - * (1) Process id - * (2) Span zones across monitors - * (3) Monitor id where the Editor should be opened - * (4) Monitors count - * - * Data for each monitor: - * (5) Monitor id - * (6) DPI - * (7) monitor left - * (8) monitor top - * ... - */ - std::wstring params; - const std::wstring divider = L"/"; - params += std::to_wstring(GetCurrentProcessId()) + divider; /* Process id */ - const bool spanZonesAcrossMonitors = m_settings->GetSettings()->spanZonesAcrossMonitors; - params += std::to_wstring(spanZonesAcrossMonitors) + divider; /* Span zones */ - std::vector> allMonitors; - allMonitors = FancyZonesUtils::GetAllMonitorInfo<&MONITORINFOEX::rcWork>(); - - if (spanZonesAcrossMonitors) - { - params += FancyZonesUtils::GenerateUniqueIdAllMonitorsArea(virtualDesktopId.get()) + divider; /* Monitor id where the Editor should be opened */ - } - - // device id map - std::unordered_map displayDeviceIdxMap; - - bool showDpiWarning = false; - int prevDpiX = -1, prevDpiY = -1; - std::wstring monitorsDataStr; - for (auto& monitorData : allMonitors) - { - HMONITOR monitor = monitorData.first; - auto monitorInfo = monitorData.second; - - std::wstring deviceId = FancyZonesUtils::GetDisplayDeviceId(monitorInfo.szDevice, displayDeviceIdxMap); - std::wstring monitorId = FancyZonesUtils::GenerateUniqueId(monitor, deviceId, virtualDesktopId.get()); - - if (monitor == targetMonitor && !spanZonesAcrossMonitors) - { - params += monitorId + divider; /* Monitor id where the Editor should be opened */ - } - - monitorsDataStr += std::move(monitorId) + divider; /* Monitor id */ - UINT dpiX = 0; - UINT dpiY = 0; - if (GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY) == S_OK) - { - monitorsDataStr += std::to_wstring(dpiX) + divider; /* DPI */ - if (spanZonesAcrossMonitors && prevDpiX != -1 && (prevDpiX != dpiX || prevDpiY != dpiY)) - { - showDpiWarning = true; - } - - prevDpiX = dpiX; - prevDpiY = dpiY; - } - - monitorsDataStr += std::to_wstring(monitorInfo.rcMonitor.left) + divider; /* Top coordinate */ - monitorsDataStr += std::to_wstring(monitorInfo.rcMonitor.top) + divider; /* Left coordinate */ - } - - params += std::to_wstring(allMonitors.size()) + divider; /* Monitors count */ - params += monitorsDataStr; - - FancyZonesDataInstance().SaveFancyZonesEditorParameters(spanZonesAcrossMonitors, virtualDesktopId.get(), targetMonitor); /* Write parameters to json file */ - - if (showDpiWarning) - { - // We must show the message box in a separate thread, since this code is called from a low-level - // keyboard hook callback, and launching messageboxes from it has unexpected side effects - //std::thread{ [] { - // MessageBoxW(nullptr, - // GET_RESOURCE_STRING(IDS_SPAN_ACROSS_ZONES_WARNING).c_str(), - // GET_RESOURCE_STRING(IDS_POWERTOYS_FANCYZONES).c_str(), - // MB_OK | MB_ICONWARNING); - //} }.detach(); - } - - SHELLEXECUTEINFO sei{ sizeof(sei) }; - sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI }; - sei.lpFile = NonLocalizable::FZEditorExecutablePath; - sei.lpParameters = params.c_str(); - sei.nShow = SW_SHOWDEFAULT; - ShellExecuteEx(&sei); - Trace::FancyZones::EditorLaunched(1); - - // Launch the editor on a background thread - // Wait for the editor's process to exit - // Post back to the main thread to update - std::thread waitForEditorThread([window = m_window, processHandle = sei.hProcess, terminateEditorEvent = m_terminateEditorEvent.get()]() { - HANDLE waitEvents[2] = { processHandle, terminateEditorEvent }; - auto result = WaitForMultipleObjects(2, waitEvents, false, INFINITE); - if (result == WAIT_OBJECT_0 + 0) - { - // Editor exited - // Update any changes it may have made - PostMessage(window, WM_PRIV_EDITOR, 0, static_cast(EditorExitKind::Exit)); - } - else if (result == WAIT_OBJECT_0 + 1) - { - // User hit Win+~ while editor is already running - // Shut it down - TerminateProcess(processHandle, 2); - PostMessage(window, WM_PRIV_EDITOR, 0, static_cast(EditorExitKind::Terminate)); - } - CloseHandle(processHandle); - }); - - waitForEditorThread.detach(); -} - -void FancyZones::SettingsChanged() noexcept -{ - _TRACER_; - std::unique_lock writeLock(m_lock); - - // Update the hotkey - UnregisterHotKey(m_window, 1); - RegisterHotKey(m_window, 1, m_settings->GetSettings()->editorHotkey.get_modifiers(), m_settings->GetSettings()->editorHotkey.get_code()); - - // Needed if we toggled spanZonesAcrossMonitors - m_workAreaHandler.Clear(); - OnDisplayChange(DisplayChangeType::Initialization, writeLock); -} - -// IZoneWindowHost -IFACEMETHODIMP_(void) -FancyZones::MoveWindowsOnActiveZoneSetChange() noexcept -{ - if (m_settings->GetSettings()->zoneSetChange_moveWindows) - { - std::unique_lock writeLock(m_lock); - UpdateWindowsPositions(writeLock); - } -} - -LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept -{ - switch (message) - { - case WM_HOTKEY: - { - if (wparam == 1) - { - ToggleEditor(); - } - } - break; - - case WM_SETTINGCHANGE: - { - if (wparam == SPI_SETWORKAREA) - { - // Changes in taskbar position resulted in different size of work area. - // Invalidate cached work-areas so they can be recreated with latest information. - std::unique_lock writeLock(m_lock); - m_workAreaHandler.Clear(); - OnDisplayChange(DisplayChangeType::WorkArea, writeLock); - } - } - break; - - case WM_DISPLAYCHANGE: - { - // Display resolution changed. Invalidate cached work-areas so they can be recreated with latest information. - std::unique_lock writeLock(m_lock); - m_workAreaHandler.Clear(); - OnDisplayChange(DisplayChangeType::DisplayChange, writeLock); - } - break; - - default: - { - POINT ptScreen; - GetPhysicalCursorPos(&ptScreen); - - if (message == WM_PRIV_SNAP_HOTKEY) - { - OnSnapHotkey(static_cast(lparam)); - } - else if (message == WM_PRIV_VD_INIT) - { - std::unique_lock writeLock(m_lock); - OnDisplayChange(DisplayChangeType::Initialization, writeLock); - } - else if (message == WM_PRIV_VD_SWITCH) - { - std::unique_lock writeLock(m_lock); - OnDisplayChange(DisplayChangeType::VirtualDesktop, writeLock); - } - else if (message == WM_PRIV_VD_UPDATE) - { - std::vector ids{}; - if (VirtualDesktopUtils::GetVirtualDesktopIds(ids)) - { - RegisterVirtualDesktopUpdates(ids); - } - } - else if (message == WM_PRIV_EDITOR) - { - if (lparam == static_cast(EditorExitKind::Exit)) - { - std::unique_lock writeLock(m_lock); - OnEditorExitEvent(writeLock); - } - - { - // Clean up the event either way - std::unique_lock writeLock(m_lock); - m_terminateEditorEvent.release(); - } - } - else if (message == WM_PRIV_MOVESIZESTART) - { - auto hwnd = reinterpret_cast(wparam); - if (auto monitor = MonitorFromPoint(ptScreen, MONITOR_DEFAULTTONULL)) - { - MoveSizeStart(hwnd, monitor, ptScreen); - } - } - else if (message == WM_PRIV_MOVESIZEEND) - { - auto hwnd = reinterpret_cast(wparam); - MoveSizeEnd(hwnd, ptScreen); - } - else if (message == WM_PRIV_LOCATIONCHANGE && InMoveSize()) - { - if (auto monitor = MonitorFromPoint(ptScreen, MONITOR_DEFAULTTONULL)) - { - MoveSizeUpdate(monitor, ptScreen); - } - } - else if (message == WM_PRIV_WINDOWCREATED) - { - auto hwnd = reinterpret_cast(wparam); - WindowCreated(hwnd); - } - else if (message == WM_PRIV_FILE_UPDATE) - { - FancyZonesDataInstance().LoadFancyZonesData(); - std::unique_lock writeLock(m_lock); - UpdateZoneSets(writeLock); - } - else if (message == WM_PRIV_QUICK_LAYOUT_KEY) - { - ApplyQuickLayout(static_cast(lparam)); - } - else - { - return DefWindowProc(window, message, wparam, lparam); - } - } - break; - } - return 0; -} - -void FancyZones::OnDisplayChange(DisplayChangeType changeType, require_write_lock lock) noexcept -{ - _TRACER_; - if (changeType == DisplayChangeType::VirtualDesktop || - changeType == DisplayChangeType::Initialization) - { - m_previousDesktopId = m_currentDesktopId; - GUID currentVirtualDesktopId{}; - if (VirtualDesktopUtils::GetCurrentVirtualDesktopId(¤tVirtualDesktopId)) - { - m_currentDesktopId = currentVirtualDesktopId; - if (m_previousDesktopId != GUID_NULL && m_currentDesktopId != m_previousDesktopId) - { - Trace::VirtualDesktopChanged(); - } - } - if (changeType == DisplayChangeType::Initialization) - { - std::vector ids{}; - if (VirtualDesktopUtils::GetVirtualDesktopIds(ids) && !ids.empty()) - { - FancyZonesDataInstance().UpdatePrimaryDesktopData(ids[0]); - FancyZonesDataInstance().RemoveDeletedDesktops(ids); - } - } - } - - UpdateZoneWindows(lock); - - if ((changeType == DisplayChangeType::WorkArea) || (changeType == DisplayChangeType::DisplayChange)) - { - if (m_settings->GetSettings()->displayChange_moveWindows) - { - UpdateWindowsPositions(lock); - } - } -} - -void FancyZones::AddZoneWindow(HMONITOR monitor, const std::wstring& deviceId, require_write_lock) noexcept -{ - _TRACER_; - if (m_workAreaHandler.IsNewWorkArea(m_currentDesktopId, monitor)) - { - wil::unique_cotaskmem_string virtualDesktopId; - if (SUCCEEDED(StringFromCLSID(m_currentDesktopId, &virtualDesktopId))) - { - std::wstring uniqueId; - - if (monitor) - { - uniqueId = FancyZonesUtils::GenerateUniqueId(monitor, deviceId, virtualDesktopId.get()); - } - else - { - uniqueId = FancyZonesUtils::GenerateUniqueIdAllMonitorsArea(virtualDesktopId.get()); - } - - std::wstring parentId{}; - auto parentArea = m_workAreaHandler.GetWorkArea(m_previousDesktopId, monitor); - if (parentArea) - { - parentId = parentArea->UniqueId(); - } - auto workArea = MakeZoneWindow(this, m_hinstance, monitor, uniqueId, parentId); - if (workArea) - { - m_workAreaHandler.AddWorkArea(m_currentDesktopId, monitor, workArea); - FancyZonesDataInstance().SaveZoneSettings(); - } - } - } -} - -LRESULT CALLBACK FancyZones::s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept -{ - auto thisRef = reinterpret_cast(GetWindowLongPtr(window, GWLP_USERDATA)); - if (!thisRef && (message == WM_CREATE)) - { - const auto createStruct = reinterpret_cast(lparam); - thisRef = reinterpret_cast(createStruct->lpCreateParams); - SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(thisRef)); - } - - return thisRef ? thisRef->WndProc(window, message, wparam, lparam) : - DefWindowProc(window, message, wparam, lparam); -} - -void FancyZones::UpdateZoneWindows(require_write_lock lock) noexcept -{ - // Mapping between display device name and device index (operating system identifies each display device with an index value). - std::unordered_map displayDeviceIdxMap; - struct capture - { - FancyZones* fancyZones; - std::unordered_map* displayDeviceIdx; - require_write_lock lock; - }; - - auto callback = [](HMONITOR monitor, HDC, RECT*, LPARAM data) -> BOOL { - capture* params = reinterpret_cast(data); - MONITORINFOEX mi{ { .cbSize = sizeof(mi) } }; - if (GetMonitorInfoW(monitor, &mi)) - { - auto& displayDeviceIdxMap = *(params->displayDeviceIdx); - FancyZones* fancyZones = params->fancyZones; - - std::wstring deviceId = FancyZonesUtils::GetDisplayDeviceId(mi.szDevice, displayDeviceIdxMap); - fancyZones->AddZoneWindow(monitor, deviceId, params->lock); - } - return TRUE; - }; - - if (m_settings->GetSettings()->spanZonesAcrossMonitors) - { - AddZoneWindow(nullptr, {}, lock); - } - else - { - capture capture{ this, &displayDeviceIdxMap, lock }; - EnumDisplayMonitors(nullptr, nullptr, callback, reinterpret_cast(&capture)); - } -} - -void FancyZones::UpdateWindowsPositions(require_write_lock) noexcept -{ - auto callback = [](HWND window, LPARAM data) -> BOOL { - size_t bitmask = reinterpret_cast(::GetProp(window, ZonedWindowProperties::PropertyMultipleZoneID)); - - if (bitmask != 0) - { - std::vector indexSet; - for (int i = 0; i < std::numeric_limits::digits; i++) - { - if ((1ull << i) & bitmask) - { - indexSet.push_back(i); - } - } - - auto strongThis = reinterpret_cast(data); - auto zoneWindow = strongThis->m_workAreaHandler.GetWorkArea(window); - if (zoneWindow) - { - strongThis->m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, indexSet, zoneWindow); - } - } - return TRUE; - }; - EnumWindows(callback, reinterpret_cast(this)); -} - -bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexcept -{ - _TRACER_; - HMONITOR current = WorkAreaKeyFromWindow(window); - - std::vector monitorInfo = GetMonitorsSorted(); - if (current && monitorInfo.size() > 1 && m_settings->GetSettings()->moveWindowAcrossMonitors) - { - // Multi monitor environment. - auto currMonitorInfo = std::find(std::begin(monitorInfo), std::end(monitorInfo), current); - do - { - std::unique_lock writeLock(m_lock); - if (m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */, m_workAreaHandler.GetWorkArea(m_currentDesktopId, *currMonitorInfo))) - { - 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 - { - // Single monitor environment, or combined multi-monitor environment. - std::unique_lock writeLock(m_lock); - if (m_settings->GetSettings()->restoreSize) - { - bool moved = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */, m_workAreaHandler.GetWorkArea(m_currentDesktopId, current)); - if (!moved) - { - FancyZonesUtils::RestoreWindowOrigin(window); - FancyZonesUtils::RestoreWindowSize(window); - } - return true; - } - else - { - return m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, true /* cycle through zones */, m_workAreaHandler.GetWorkArea(m_currentDesktopId, current)); - } - } - - 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 && m_settings->GetSettings()->moveWindowAcrossMonitors) - { - // Multi monitor environment. - // First, try to stay on the same monitor - bool success = ProcessDirectedSnapHotkey(window, vkCode, false, m_workAreaHandler.GetWorkArea(m_currentDesktopId, 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(m_currentDesktopId, monitor); - if (workArea) - { - auto zoneSet = workArea->ActiveZoneSet(); - if (zoneSet) - { - const auto zones = zoneSet->GetZones(); - 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; - } - - size_t chosenIdx = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects); - - if (chosenIdx < zoneRects.size()) - { - // Moving to another monitor succeeded - const auto& [trueZoneIdx, zoneWindow] = zoneRectsInfo[chosenIdx]; - m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }, zoneWindow); - 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(m_currentDesktopId, current); - if (workArea) - { - auto zoneSet = workArea->ActiveZoneSet(); - if (zoneSet) - { - const auto zones = zoneSet->GetZones(); - 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, zoneWindow] = zoneRectsInfo[chosenIdx]; - m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }, zoneWindow); - return true; - } - else - { - // Giving up - return false; - } - } - else - { - // Single monitor environment, or combined multi-monitor environment. - return ProcessDirectedSnapHotkey(window, vkCode, true, m_workAreaHandler.GetWorkArea(m_currentDesktopId, 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 (m_settings->GetSettings()->moveWindowsBasedOnPosition) - { - return OnSnapHotkeyBasedOnPosition(window, vkCode); - } - else - { - return (vkCode == VK_LEFT || vkCode == VK_RIGHT) && OnSnapHotkeyBasedOnZoneNumber(window, vkCode); - } - return false; -} - -bool FancyZones::ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr zoneWindow) noexcept -{ - // Check whether Alt is used in the shortcut key combination - if (GetAsyncKeyState(VK_MENU) & 0x8000) - { - return m_windowMoveHandler.ExtendWindowByDirectionAndPosition(window, vkCode, zoneWindow); - } - else - { - return m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle, zoneWindow); - } -} - -void FancyZones::RegisterVirtualDesktopUpdates(std::vector& ids) noexcept -{ - _TRACER_; - std::unique_lock writeLock(m_lock); - - m_workAreaHandler.RegisterUpdates(ids); - std::vector active{}; - if (VirtualDesktopUtils::GetVirtualDesktopIds(active) && !active.empty()) - { - FancyZonesDataInstance().UpdatePrimaryDesktopData(active[0]); - FancyZonesDataInstance().RemoveDeletedDesktops(active); - } -} - -bool FancyZones::IsSplashScreen(HWND window) -{ - wchar_t className[MAX_PATH]; - if (GetClassName(window, className, MAX_PATH) == 0) - { - return false; - } - - return wcscmp(NonLocalizable::SplashClassName, className) == 0; -} - -void FancyZones::OnEditorExitEvent(require_write_lock lock) noexcept -{ - // Collect information about changes in zone layout after editor exited. - FancyZonesDataInstance().LoadFancyZonesData(); - UpdateZoneSets(lock); -} - -void FancyZones::UpdateZoneSets(require_write_lock lock) noexcept -{ - for (auto workArea : m_workAreaHandler.GetAllWorkAreas()) - { - workArea->UpdateActiveZoneSet(); - } - if (m_settings->GetSettings()->zoneSetChange_moveWindows) - { - UpdateWindowsPositions(lock); - } -} - -bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept -{ - auto window = GetForegroundWindow(); - if (m_settings->GetSettings()->overrideSnapHotkeys && FancyZonesUtils::IsCandidateForZoning(window, m_settings->GetSettings()->excludedAppsArray)) - { - HMONITOR monitor = WorkAreaKeyFromWindow(window); - - auto zoneWindow = m_workAreaHandler.GetWorkArea(m_currentDesktopId, monitor); - if (zoneWindow && zoneWindow->ActiveZoneSet() && zoneWindow->ActiveZoneSet()->LayoutType() != FancyZonesDataTypes::ZoneSetLayoutType::Blank) - { - if (vkCode == VK_UP || vkCode == VK_DOWN) - { - return m_settings->GetSettings()->moveWindowsBasedOnPosition; - } - else - { - return true; - } - } - } - return false; -} - -void FancyZones::ApplyQuickLayout(int key) noexcept -{ - std::unique_lock writeLock(m_lock); - - std::wstring uuid; - for (auto [zoneUuid, hotkey] : FancyZonesDataInstance().GetLayoutQuickKeys()) - { - if (hotkey == key) - { - uuid = zoneUuid; - } - } - - auto workArea = m_workAreaHandler.GetWorkAreaFromCursor(m_currentDesktopId); - - // Find a custom zone set with this uuid and apply it - auto customZoneSets = FancyZonesDataInstance().GetCustomZoneSetsMap(); - - if (!customZoneSets.contains(uuid)) - { - return; - } - - FancyZonesDataTypes::ZoneSetData data{ .uuid = uuid, .type = FancyZonesDataTypes::ZoneSetLayoutType::Custom }; - FancyZonesDataInstance().SetActiveZoneSet(workArea->UniqueId(), data); - FancyZonesDataInstance().SaveZoneSettings(); - UpdateZoneSets(writeLock); - FlashZones(writeLock); -} - -void FancyZones::FlashZones(require_write_lock) noexcept -{ - if (m_settings->GetSettings()->flashZonesOnQuickSwitch && !m_windowMoveHandler.IsDragEnabled()) - { - for (auto [monitor, workArea] : m_workAreaHandler.GetWorkAreasByDesktopId(m_currentDesktopId)) - { - workArea->FlashZones(); - } - } -} - -std::vector FancyZones::GetMonitorsSorted() noexcept -{ - std::shared_lock readLock(m_lock); - - 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 -{ - _TRACER_; - std::shared_lock readLock(m_lock); - - std::vector> monitorInfo; - const auto& activeWorkAreaMap = m_workAreaHandler.GetWorkAreasByDesktopId(m_currentDesktopId); - for (const auto& [monitor, workArea] : activeWorkAreaMap) - { - if (workArea->ActiveZoneSet() != 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 (m_settings->GetSettings()->spanZonesAcrossMonitors) - { - return NULL; - } - else - { - return MonitorFromWindow(window, MONITOR_DEFAULTTONULL); - } -} - -winrt::com_ptr MakeFancyZones(HINSTANCE hinstance, - const winrt::com_ptr& settings, - std::function disableCallback) noexcept -{ - if (!settings) - { - return nullptr; - } - - return winrt::make_self(hinstance, settings, disableCallback); -} +#include "pch.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "FancyZones.h" +#include "FancyZonesLib/Settings.h" +#include "FancyZonesLib/ZoneWindow.h" +#include "FancyZonesLib/FancyZonesData.h" +#include "FancyZonesLib/ZoneSet.h" +#include "FancyZonesLib/FileWatcher.h" +#include "FancyZonesLib/WindowMoveHandler.h" +#include "FancyZonesLib/FancyZonesWinHookEventIDs.h" +#include "FancyZonesLib/util.h" +#include "on_thread_executor.h" +#include "trace.h" +#include "VirtualDesktopUtils.h" +#include "MonitorWorkAreaHandler.h" +#include "util.h" +#include "CallTracer.h" + +#include + +enum class DisplayChangeType +{ + WorkArea, + DisplayChange, + VirtualDesktop, + Initialization +}; + +namespace +{ + constexpr int CUSTOM_POSITIONING_LEFT_TOP_PADDING = 16; + + struct require_read_lock + { + template + require_read_lock(const std::shared_lock& lock) + { + lock; + } + + template + require_read_lock(const std::unique_lock& lock) + { + lock; + } + }; + + struct require_write_lock + { + template + require_write_lock(const std::unique_lock& lock) + { + lock; + } + }; +} + +// Non-localizable strings +namespace NonLocalizable +{ + const wchar_t ToolWindowClassName[] = L"SuperFancyZones"; + const wchar_t FZEditorExecutablePath[] = L"modules\\FancyZones\\FancyZonesEditor.exe"; + const wchar_t SplashClassName[] = L"MsoSplash"; +} + +struct FancyZones : public winrt::implements +{ +public: + FancyZones(HINSTANCE hinstance, const winrt::com_ptr& settings, std::function disableModuleCallback) noexcept : + m_hinstance(hinstance), + m_settings(settings), + m_windowMoveHandler(settings, [this]() { + PostMessageW(m_window, WM_PRIV_LOCATIONCHANGE, NULL, NULL); + }), + m_zonesSettingsFileWatcher(FancyZonesDataInstance().GetZonesSettingsFileName(), [this]() { + PostMessageW(m_window, WM_PRIV_FILE_UPDATE, NULL, NULL); + }), + m_settingsFileWatcher(FancyZonesDataInstance().GetSettingsFileName(), [this]() { + PostMessageW(m_window, WM_PRIV_SETTINGS_CHANGED, NULL, NULL); + }) + { + m_settings->SetCallback(this); + + this->disableModuleCallback = std::move(disableModuleCallback); + } + + // IFancyZones + IFACEMETHODIMP_(void) + Run() noexcept; + IFACEMETHODIMP_(void) + Destroy() noexcept; + + void MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen) noexcept + { + std::unique_lock writeLock(m_lock); + if (m_settings->GetSettings()->spanZonesAcrossMonitors) + { + monitor = NULL; + } + m_windowMoveHandler.MoveSizeStart(window, monitor, ptScreen, m_workAreaHandler.GetWorkAreasByDesktopId(m_currentDesktopId)); + } + + void MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen) noexcept + { + std::unique_lock writeLock(m_lock); + if (m_settings->GetSettings()->spanZonesAcrossMonitors) + { + monitor = NULL; + } + m_windowMoveHandler.MoveSizeUpdate(monitor, ptScreen, m_workAreaHandler.GetWorkAreasByDesktopId(m_currentDesktopId)); + } + + void MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept + { + _TRACER_; + std::unique_lock writeLock(m_lock); + m_windowMoveHandler.MoveSizeEnd(window, ptScreen, m_workAreaHandler.GetWorkAreasByDesktopId(m_currentDesktopId)); + } + + IFACEMETHODIMP_(void) + HandleWinHookEvent(const WinHookEvent* data) noexcept + { + const auto wparam = reinterpret_cast(data->hwnd); + const LONG lparam = 0; + std::shared_lock readLock(m_lock); + switch (data->event) + { + case EVENT_SYSTEM_MOVESIZESTART: + PostMessageW(m_window, WM_PRIV_MOVESIZESTART, wparam, lparam); + break; + case EVENT_SYSTEM_MOVESIZEEND: + PostMessageW(m_window, WM_PRIV_MOVESIZEEND, wparam, lparam); + break; + case EVENT_OBJECT_LOCATIONCHANGE: + PostMessageW(m_window, WM_PRIV_LOCATIONCHANGE, wparam, lparam); + break; + case EVENT_OBJECT_NAMECHANGE: + PostMessageW(m_window, WM_PRIV_NAMECHANGE, wparam, lparam); + break; + + case EVENT_OBJECT_UNCLOAKED: + case EVENT_OBJECT_SHOW: + case EVENT_OBJECT_CREATE: + if (data->idObject == OBJID_WINDOW) + { + PostMessageW(m_window, WM_PRIV_WINDOWCREATED, wparam, lparam); + } + break; + } + } + + IFACEMETHODIMP_(void) + VirtualDesktopChanged() noexcept; + IFACEMETHODIMP_(void) + VirtualDesktopInitialize() noexcept; + IFACEMETHODIMP_(bool) + OnKeyDown(PKBDLLHOOKSTRUCT info) noexcept; + IFACEMETHODIMP_(void) + ToggleEditor() noexcept; + IFACEMETHODIMP_(void) + SettingsChanged() noexcept; + + void WindowCreated(HWND window) noexcept; + + // IZoneWindowHost + IFACEMETHODIMP_(void) + MoveWindowsOnActiveZoneSetChange() noexcept; + IFACEMETHODIMP_(COLORREF) + GetZoneColor() noexcept + { + return (FancyZonesUtils::HexToRGB(m_settings->GetSettings()->zoneColor)); + } + IFACEMETHODIMP_(COLORREF) + GetZoneBorderColor() noexcept + { + return (FancyZonesUtils::HexToRGB(m_settings->GetSettings()->zoneBorderColor)); + } + IFACEMETHODIMP_(COLORREF) + GetZoneHighlightColor() noexcept + { + return (FancyZonesUtils::HexToRGB(m_settings->GetSettings()->zoneHighlightColor)); + } + IFACEMETHODIMP_(int) + GetZoneHighlightOpacity() noexcept + { + return m_settings->GetSettings()->zoneHighlightOpacity; + } + + IFACEMETHODIMP_(bool) + isMakeDraggedWindowTransparentActive() noexcept + { + return m_settings->GetSettings()->makeDraggedWindowTransparent; + } + + IFACEMETHODIMP_(bool) + InMoveSize() noexcept + { + std::shared_lock readLock(m_lock); + return m_windowMoveHandler.InMoveSize(); + } + + IFACEMETHODIMP_(Settings::OverlappingZonesAlgorithm) + GetOverlappingZonesAlgorithm() noexcept + { + return m_settings->GetSettings()->overlappingZonesAlgorithm; + } + + LRESULT WndProc(HWND, UINT, WPARAM, LPARAM) noexcept; + void OnDisplayChange(DisplayChangeType changeType, require_write_lock) noexcept; + void AddZoneWindow(HMONITOR monitor, const std::wstring& deviceId, require_write_lock) noexcept; + +protected: + static LRESULT CALLBACK s_WndProc(HWND, UINT, WPARAM, LPARAM) noexcept; + +private: + + void UpdateZoneWindows(require_write_lock) noexcept; + void UpdateWindowsPositions(require_write_lock) noexcept; + bool OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexcept; + bool OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept; + bool OnSnapHotkey(DWORD vkCode) noexcept; + bool ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr zoneWindow) noexcept; + + void RegisterVirtualDesktopUpdates(std::vector& ids) noexcept; + + bool IsSplashScreen(HWND window); + bool ShouldProcessNewWindow(HWND window) noexcept; + std::vector GetZoneIndexSetFromWorkAreaHistory(HWND window, winrt::com_ptr workArea) noexcept; + std::pair, std::vector> GetAppZoneHistoryInfo(HWND window, HMONITOR monitor, std::unordered_map>& workAreaMap) noexcept; + std::pair, std::vector> GetAppZoneHistoryInfo(HWND window, HMONITOR monitor, bool isPrimaryMonitor) noexcept; + void MoveWindowIntoZone(HWND window, winrt::com_ptr zoneWindow, const std::vector& zoneIndexSet) noexcept; + + void OnEditorExitEvent(require_write_lock) noexcept; + void UpdateZoneSets(require_write_lock) noexcept; + bool ShouldProcessSnapHotkey(DWORD vkCode) noexcept; + void ApplyQuickLayout(int key) noexcept; + void FlashZones(require_write_lock) noexcept; + + std::vector> GetRawMonitorData() noexcept; + std::vector GetMonitorsSorted() noexcept; + HMONITOR WorkAreaKeyFromWindow(HWND window) noexcept; + + const HINSTANCE m_hinstance{}; + + mutable std::shared_mutex m_lock; + HWND m_window{}; + WindowMoveHandler m_windowMoveHandler; + MonitorWorkAreaHandler m_workAreaHandler; + + FileWatcher m_zonesSettingsFileWatcher; + FileWatcher m_settingsFileWatcher; + + winrt::com_ptr m_settings{}; + GUID m_previousDesktopId{}; // UUID of previously active virtual desktop. + GUID m_currentDesktopId{}; // UUID of the current virtual desktop. + wil::unique_handle m_terminateEditorEvent; // Handle of FancyZonesEditor.exe we launch and wait on + wil::unique_handle m_terminateVirtualDesktopTrackerEvent; + + OnThreadExecutor m_dpiUnawareThread; + OnThreadExecutor m_virtualDesktopTrackerThread; + + EventWaiter m_toggleEditorEventWaiter; + + // If non-recoverable error occurs, trigger disabling of entire FancyZones. + static std::function disableModuleCallback; + + static UINT WM_PRIV_VD_INIT; // Scheduled when FancyZones is initialized + static UINT WM_PRIV_VD_SWITCH; // Scheduled when virtual desktop switch occurs + static UINT WM_PRIV_VD_UPDATE; // Scheduled on virtual desktops update (creation/deletion) + static UINT WM_PRIV_EDITOR; // Scheduled when the editor exits + static UINT WM_PRIV_FILE_UPDATE; // Scheduled when the a watched file is updated + static UINT WM_PRIV_SETTINGS_CHANGED; + + static UINT WM_PRIV_SNAP_HOTKEY; // Scheduled when we receive a snap hotkey key down press + static UINT WM_PRIV_QUICK_LAYOUT_KEY; // Scheduled when we receive a key down press to quickly apply a layout + + // Did we terminate the editor or was it closed cleanly? + enum class EditorExitKind : byte + { + Exit, + Terminate + }; +}; + +std::function FancyZones::disableModuleCallback = {}; + +UINT FancyZones::WM_PRIV_VD_INIT = RegisterWindowMessage(L"{469818a8-00fa-4069-b867-a1da484fcd9a}"); +UINT FancyZones::WM_PRIV_VD_SWITCH = RegisterWindowMessage(L"{128c2cb0-6bdf-493e-abbe-f8705e04aa95}"); +UINT FancyZones::WM_PRIV_VD_UPDATE = RegisterWindowMessage(L"{b8b72b46-f42f-4c26-9e20-29336cf2f22e}"); +UINT FancyZones::WM_PRIV_EDITOR = RegisterWindowMessage(L"{87543824-7080-4e91-9d9c-0404642fc7b6}"); +UINT FancyZones::WM_PRIV_FILE_UPDATE = RegisterWindowMessage(L"{632f17a9-55a7-45f1-a4db-162e39271d92}"); +UINT FancyZones::WM_PRIV_SNAP_HOTKEY = RegisterWindowMessage(L"{763c03a3-03d9-4cde-8d71-f0358b0b4b52}"); +UINT FancyZones::WM_PRIV_QUICK_LAYOUT_KEY = RegisterWindowMessage(L"{72f4fd8e-23f1-43ab-bbbc-029363df9a84}"); +UINT FancyZones::WM_PRIV_SETTINGS_CHANGED = RegisterWindowMessage(L"{15baab3d-c67b-4a15-aFF0-13610e05e947}"); + +// IFancyZones +IFACEMETHODIMP_(void) +FancyZones::Run() noexcept +{ + std::unique_lock writeLock(m_lock); + + WNDCLASSEXW wcex{}; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.lpfnWndProc = s_WndProc; + wcex.hInstance = m_hinstance; + wcex.lpszClassName = NonLocalizable::ToolWindowClassName; + RegisterClassExW(&wcex); + + BufferedPaintInit(); + + m_window = CreateWindowExW(WS_EX_TOOLWINDOW, NonLocalizable::ToolWindowClassName, L"", WS_POPUP, 0, 0, 0, 0, nullptr, nullptr, m_hinstance, this); + if (!m_window) + { + return; + } + + RegisterHotKey(m_window, 1, m_settings->GetSettings()->editorHotkey.get_modifiers(), m_settings->GetSettings()->editorHotkey.get_code()); + + VirtualDesktopInitialize(); + + m_dpiUnawareThread.submit(OnThreadExecutor::task_t{ [] { + SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE); + SetThreadDpiHostingBehavior(DPI_HOSTING_BEHAVIOR_MIXED); + } }) + .wait(); + + m_terminateVirtualDesktopTrackerEvent.reset(CreateEvent(nullptr, FALSE, FALSE, nullptr)); + m_virtualDesktopTrackerThread.submit(OnThreadExecutor::task_t{ [&] { VirtualDesktopUtils::HandleVirtualDesktopUpdates(m_window, WM_PRIV_VD_UPDATE, m_terminateVirtualDesktopTrackerEvent.get()); } }); + + m_toggleEditorEventWaiter = EventWaiter(CommonSharedConstants::FANCY_ZONES_EDITOR_TOGGLE_EVENT, [&](int err) { + if (err == ERROR_SUCCESS) + { + Logger::trace(L"{} event was signaled", CommonSharedConstants::FANCY_ZONES_EDITOR_TOGGLE_EVENT); + PostMessage(m_window, WM_HOTKEY, 1, 0); + } + }); +} + +// IFancyZones +IFACEMETHODIMP_(void) +FancyZones::Destroy() noexcept +{ + std::unique_lock writeLock(m_lock); + m_workAreaHandler.Clear(); + BufferedPaintUnInit(); + if (m_window) + { + DestroyWindow(m_window); + m_window = nullptr; + } + if (m_terminateVirtualDesktopTrackerEvent) + { + SetEvent(m_terminateVirtualDesktopTrackerEvent.get()); + } + + m_settings->ResetCallback(); +} + +// IFancyZonesCallback +IFACEMETHODIMP_(void) +FancyZones::VirtualDesktopChanged() noexcept +{ + // VirtualDesktopChanged is called from a reentrant WinHookProc function, therefore we must postpone the actual logic + // until we're in FancyZones::WndProc, which is not reentrant. + PostMessage(m_window, WM_PRIV_VD_SWITCH, 0, 0); +} + +// IFancyZonesCallback +IFACEMETHODIMP_(void) +FancyZones::VirtualDesktopInitialize() noexcept +{ + PostMessage(m_window, WM_PRIV_VD_INIT, 0, 0); +} + +bool FancyZones::ShouldProcessNewWindow(HWND window) noexcept +{ + using namespace FancyZonesUtils; + // Avoid processing splash screens, already stamped (zoned) windows, or those windows + // that belong to excluded applications list. + if (IsSplashScreen(window) || + (reinterpret_cast(::GetProp(window, ZonedWindowProperties::PropertyMultipleZoneID)) != 0) || + !IsCandidateForLastKnownZone(window, m_settings->GetSettings()->excludedAppsArray)) + { + return false; + } + return true; +} + +std::vector FancyZones::GetZoneIndexSetFromWorkAreaHistory( + HWND window, + winrt::com_ptr workArea) noexcept +{ + const auto activeZoneSet = workArea->ActiveZoneSet(); + if (activeZoneSet) + { + wil::unique_cotaskmem_string zoneSetId; + if (SUCCEEDED(StringFromCLSID(activeZoneSet->Id(), &zoneSetId))) + { + return FancyZonesDataInstance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), zoneSetId.get()); + } + } + return {}; +} + +std::pair, std::vector> FancyZones::GetAppZoneHistoryInfo( + HWND window, + HMONITOR monitor, + std::unordered_map>& workAreaMap) noexcept +{ + if (workAreaMap.contains(monitor)) + { + auto workArea = workAreaMap[monitor]; + workAreaMap.erase(monitor); // monitor processed, remove entry from the map + return { workArea, GetZoneIndexSetFromWorkAreaHistory(window, workArea) }; + } + return { nullptr, {} }; +} + +std::pair, std::vector> FancyZones::GetAppZoneHistoryInfo(HWND window, HMONITOR monitor, bool isPrimaryMonitor) noexcept +{ + std::pair, std::vector> appZoneHistoryInfo{ nullptr, {} }; + auto workAreaMap = m_workAreaHandler.GetWorkAreasByDesktopId(m_currentDesktopId); + + // Search application history on currently active monitor. + appZoneHistoryInfo = GetAppZoneHistoryInfo(window, monitor, workAreaMap); + + if (isPrimaryMonitor && appZoneHistoryInfo.second.empty()) + { + // No application history on primary monitor, search on remaining monitors. + for (const auto& [monitor, workArea] : workAreaMap) + { + auto zoneIndexSet = GetZoneIndexSetFromWorkAreaHistory(window, workArea); + if (!zoneIndexSet.empty()) + { + return { workArea, zoneIndexSet }; + } + } + } + + return appZoneHistoryInfo; +} + +void FancyZones::MoveWindowIntoZone(HWND window, winrt::com_ptr zoneWindow, const std::vector& zoneIndexSet) noexcept +{ + _TRACER_; + auto& fancyZonesData = FancyZonesDataInstance(); + if (!fancyZonesData.IsAnotherWindowOfApplicationInstanceZoned(window, zoneWindow->UniqueId())) + { + m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, zoneIndexSet, zoneWindow); + fancyZonesData.UpdateProcessIdToHandleMap(window, zoneWindow->UniqueId()); + } +} + +inline int RectWidth(const RECT& rect) +{ + return rect.right - rect.left; +} + +inline int RectHeight(const RECT& rect) +{ + return rect.bottom - rect.top; +} + +RECT FitOnScreen(const RECT& windowRect, const RECT& originMonitorRect, const RECT& destMonitorRect) +{ + // New window position on active monitor. If window fits the screen, this will be final position. + int left = destMonitorRect.left + (windowRect.left - originMonitorRect.left); + int top = destMonitorRect.top + (windowRect.top - originMonitorRect.top); + int W = RectWidth(windowRect); + int H = RectHeight(windowRect); + + if ((left < destMonitorRect.left) || (left + W > destMonitorRect.right)) + { + // Set left window border to left border of screen (add padding). Resize window width if needed. + left = destMonitorRect.left + CUSTOM_POSITIONING_LEFT_TOP_PADDING; + W = min(W, RectWidth(destMonitorRect) - CUSTOM_POSITIONING_LEFT_TOP_PADDING); + } + if ((top < destMonitorRect.top) || (top + H > destMonitorRect.bottom)) + { + // Set top window border to top border of screen (add padding). Resize window height if needed. + top = destMonitorRect.top + CUSTOM_POSITIONING_LEFT_TOP_PADDING; + H = min(H, RectHeight(destMonitorRect) - CUSTOM_POSITIONING_LEFT_TOP_PADDING); + } + + return { .left = left, + .top = top, + .right = left + W, + .bottom = top + H }; +} + +void OpenWindowOnActiveMonitor(HWND window, HMONITOR monitor) noexcept +{ + // By default Windows opens new window on primary monitor. + // Try to preserve window width and height, adjust top-left corner if needed. + HMONITOR origin = MonitorFromWindow(window, MONITOR_DEFAULTTOPRIMARY); + if (origin == monitor) + { + // Certain applications by design open in last known position, regardless of FancyZones. + // If that position is on currently active monitor, skip custom positioning. + return; + } + + WINDOWPLACEMENT placement{}; + if (GetWindowPlacement(window, &placement)) + { + MONITORINFOEX originMi; + originMi.cbSize = sizeof(originMi); + if (GetMonitorInfo(origin, &originMi)) + { + MONITORINFOEX destMi; + destMi.cbSize = sizeof(destMi); + if (GetMonitorInfo(monitor, &destMi)) + { + RECT newPosition = FitOnScreen(placement.rcNormalPosition, originMi.rcWork, destMi.rcWork); + FancyZonesUtils::SizeWindowToRect(window, newPosition); + } + } + } +} + +// IFancyZonesCallback +IFACEMETHODIMP_(void) +FancyZones::WindowCreated(HWND window) noexcept +{ + std::shared_lock readLock(m_lock); + GUID desktopId{}; + if (VirtualDesktopUtils::GetWindowDesktopId(window, &desktopId) && desktopId != m_currentDesktopId) + { + // Switch between virtual desktops results with posting same windows messages that also indicate + // creation of new window. We need to check if window being processed is on currently active desktop. + return; + } + const bool moveToAppLastZone = m_settings->GetSettings()->appLastZone_moveWindows; + const bool openOnActiveMonitor = m_settings->GetSettings()->openWindowOnActiveMonitor; + if ((moveToAppLastZone || openOnActiveMonitor) && ShouldProcessNewWindow(window)) + { + HMONITOR primary = MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY); + HMONITOR active = primary; + + POINT cursorPosition{}; + if (GetCursorPos(&cursorPosition)) + { + active = MonitorFromPoint(cursorPosition, MONITOR_DEFAULTTOPRIMARY); + } + + bool windowZoned{ false }; + if (moveToAppLastZone) + { + const bool primaryActive = (primary == active); + std::pair, std::vector> appZoneHistoryInfo = GetAppZoneHistoryInfo(window, active, primaryActive); + if (!appZoneHistoryInfo.second.empty()) + { + MoveWindowIntoZone(window, appZoneHistoryInfo.first, appZoneHistoryInfo.second); + windowZoned = true; + } + } + if (!windowZoned && openOnActiveMonitor) + { + m_dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] { OpenWindowOnActiveMonitor(window, active); } }).wait(); + } + } +} + +// IFancyZonesCallback +IFACEMETHODIMP_(bool) +FancyZones::OnKeyDown(PKBDLLHOOKSTRUCT info) noexcept +{ + // Return true to swallow the keyboard event + bool const shift = GetAsyncKeyState(VK_SHIFT) & 0x8000; + bool const win = GetAsyncKeyState(VK_LWIN) & 0x8000 || GetAsyncKeyState(VK_RWIN) & 0x8000; + bool const alt = GetAsyncKeyState(VK_MENU) & 0x8000; + bool const ctrl = GetAsyncKeyState(VK_CONTROL) & 0x8000; + if ((win && !shift && !ctrl) || (win && ctrl && alt)) + { + if ((info->vkCode == VK_RIGHT) || (info->vkCode == VK_LEFT) || (info->vkCode == VK_UP) || (info->vkCode == VK_DOWN)) + { + if (ShouldProcessSnapHotkey(info->vkCode)) + { + Trace::FancyZones::OnKeyDown(info->vkCode, win, ctrl, false /*inMoveSize*/); + // Win+Left, Win+Right will cycle through Zones in the active ZoneSet when WM_PRIV_SNAP_HOTKEY's handled + PostMessageW(m_window, WM_PRIV_SNAP_HOTKEY, 0, info->vkCode); + return true; + } + } + } + + if (m_settings->GetSettings()->quickLayoutSwitch) + { + int digitPressed = -1; + if ('0' <= info->vkCode && info->vkCode <= '9') + { + digitPressed = info->vkCode - '0'; + } + else if (VK_NUMPAD0 <= info->vkCode && info->vkCode <= VK_NUMPAD9) + { + digitPressed = info->vkCode - VK_NUMPAD0; + } + + bool dragging = m_windowMoveHandler.InMoveSize(); + bool changeLayoutWhileNotDragging = !dragging && !shift && win && ctrl && alt && digitPressed != -1; + bool changeLayoutWhileDragging = dragging && digitPressed != -1; + + if (changeLayoutWhileNotDragging || changeLayoutWhileDragging) + { + auto quickKeysMap = FancyZonesDataInstance().GetLayoutQuickKeys(); + if (std::any_of(quickKeysMap.begin(), quickKeysMap.end(), [=](auto item) { return item.second == digitPressed; })) + { + PostMessageW(m_window, WM_PRIV_QUICK_LAYOUT_KEY, 0, static_cast(digitPressed)); + Trace::FancyZones::QuickLayoutSwitched(changeLayoutWhileNotDragging); + return true; + } + } + } + + if (m_windowMoveHandler.IsDragEnabled() && shift) + { + return true; + } + return false; +} + +// IFancyZonesCallback +void FancyZones::ToggleEditor() noexcept +{ + _TRACER_; + { + std::shared_lock readLock(m_lock); + if (m_terminateEditorEvent) + { + SetEvent(m_terminateEditorEvent.get()); + return; + } + } + + { + std::unique_lock writeLock(m_lock); + m_terminateEditorEvent.reset(CreateEvent(nullptr, true, false, nullptr)); + } + + std::shared_lock readLock(m_lock); + + HMONITOR targetMonitor{}; + + const bool use_cursorpos_editor_startupscreen = m_settings->GetSettings()->use_cursorpos_editor_startupscreen; + if (use_cursorpos_editor_startupscreen) + { + POINT currentCursorPos{}; + GetCursorPos(¤tCursorPos); + targetMonitor = MonitorFromPoint(currentCursorPos, MONITOR_DEFAULTTOPRIMARY); + } + else + { + targetMonitor = MonitorFromWindow(GetForegroundWindow(), MONITOR_DEFAULTTOPRIMARY); + } + + if (!targetMonitor) + { + return; + } + + wil::unique_cotaskmem_string virtualDesktopId; + if (!SUCCEEDED(StringFromCLSID(m_currentDesktopId, &virtualDesktopId))) + { + return; + } + + /* + * Divider: / + * Parts: + * (1) Process id + * (2) Span zones across monitors + * (3) Monitor id where the Editor should be opened + * (4) Monitors count + * + * Data for each monitor: + * (5) Monitor id + * (6) DPI + * (7) monitor left + * (8) monitor top + * ... + */ + std::wstring params; + const std::wstring divider = L"/"; + params += std::to_wstring(GetCurrentProcessId()) + divider; /* Process id */ + const bool spanZonesAcrossMonitors = m_settings->GetSettings()->spanZonesAcrossMonitors; + params += std::to_wstring(spanZonesAcrossMonitors) + divider; /* Span zones */ + std::vector> allMonitors; + allMonitors = FancyZonesUtils::GetAllMonitorInfo<&MONITORINFOEX::rcWork>(); + + if (spanZonesAcrossMonitors) + { + params += FancyZonesUtils::GenerateUniqueIdAllMonitorsArea(virtualDesktopId.get()) + divider; /* Monitor id where the Editor should be opened */ + } + + // device id map + std::unordered_map displayDeviceIdxMap; + + bool showDpiWarning = false; + int prevDpiX = -1, prevDpiY = -1; + std::wstring monitorsDataStr; + for (auto& monitorData : allMonitors) + { + HMONITOR monitor = monitorData.first; + auto monitorInfo = monitorData.second; + + std::wstring deviceId = FancyZonesUtils::GetDisplayDeviceId(monitorInfo.szDevice, displayDeviceIdxMap); + std::wstring monitorId = FancyZonesUtils::GenerateUniqueId(monitor, deviceId, virtualDesktopId.get()); + + if (monitor == targetMonitor && !spanZonesAcrossMonitors) + { + params += monitorId + divider; /* Monitor id where the Editor should be opened */ + } + + monitorsDataStr += std::move(monitorId) + divider; /* Monitor id */ + UINT dpiX = 0; + UINT dpiY = 0; + if (GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY) == S_OK) + { + monitorsDataStr += std::to_wstring(dpiX) + divider; /* DPI */ + if (spanZonesAcrossMonitors && prevDpiX != -1 && (prevDpiX != dpiX || prevDpiY != dpiY)) + { + showDpiWarning = true; + } + + prevDpiX = dpiX; + prevDpiY = dpiY; + } + + monitorsDataStr += std::to_wstring(monitorInfo.rcMonitor.left) + divider; /* Top coordinate */ + monitorsDataStr += std::to_wstring(monitorInfo.rcMonitor.top) + divider; /* Left coordinate */ + } + + params += std::to_wstring(allMonitors.size()) + divider; /* Monitors count */ + params += monitorsDataStr; + + FancyZonesDataInstance().SaveFancyZonesEditorParameters(spanZonesAcrossMonitors, virtualDesktopId.get(), targetMonitor); /* Write parameters to json file */ + + if (showDpiWarning) + { + // We must show the message box in a separate thread, since this code is called from a low-level + // keyboard hook callback, and launching messageboxes from it has unexpected side effects + //std::thread{ [] { + // MessageBoxW(nullptr, + // GET_RESOURCE_STRING(IDS_SPAN_ACROSS_ZONES_WARNING).c_str(), + // GET_RESOURCE_STRING(IDS_POWERTOYS_FANCYZONES).c_str(), + // MB_OK | MB_ICONWARNING); + //} }.detach(); + } + + SHELLEXECUTEINFO sei{ sizeof(sei) }; + sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI }; + sei.lpFile = NonLocalizable::FZEditorExecutablePath; + sei.lpParameters = params.c_str(); + sei.nShow = SW_SHOWDEFAULT; + ShellExecuteEx(&sei); + Trace::FancyZones::EditorLaunched(1); + + // Launch the editor on a background thread + // Wait for the editor's process to exit + // Post back to the main thread to update + std::thread waitForEditorThread([window = m_window, processHandle = sei.hProcess, terminateEditorEvent = m_terminateEditorEvent.get()]() { + HANDLE waitEvents[2] = { processHandle, terminateEditorEvent }; + auto result = WaitForMultipleObjects(2, waitEvents, false, INFINITE); + if (result == WAIT_OBJECT_0 + 0) + { + // Editor exited + // Update any changes it may have made + PostMessage(window, WM_PRIV_EDITOR, 0, static_cast(EditorExitKind::Exit)); + } + else if (result == WAIT_OBJECT_0 + 1) + { + // User hit Win+~ while editor is already running + // Shut it down + TerminateProcess(processHandle, 2); + PostMessage(window, WM_PRIV_EDITOR, 0, static_cast(EditorExitKind::Terminate)); + } + CloseHandle(processHandle); + }); + + waitForEditorThread.detach(); +} + +void FancyZones::SettingsChanged() noexcept +{ + _TRACER_; + std::unique_lock writeLock(m_lock); + + // Update the hotkey + UnregisterHotKey(m_window, 1); + auto modifiers = m_settings->GetSettings()->editorHotkey.get_modifiers(); + auto code = m_settings->GetSettings()->editorHotkey.get_code(); + auto result = RegisterHotKey(m_window, 1, modifiers, code); + + if (!result) + { + Logger::error(L"Failed to register hotkey: {}", get_last_error_or_default(GetLastError())); + } + + // Needed if we toggled spanZonesAcrossMonitors + m_workAreaHandler.Clear(); + + PostMessageW(m_window, WM_PRIV_VD_INIT, NULL, NULL); +} + +// IZoneWindowHost +IFACEMETHODIMP_(void) +FancyZones::MoveWindowsOnActiveZoneSetChange() noexcept +{ + if (m_settings->GetSettings()->zoneSetChange_moveWindows) + { + std::unique_lock writeLock(m_lock); + UpdateWindowsPositions(writeLock); + } +} + +LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept +{ + switch (message) + { + case WM_HOTKEY: + { + if (wparam == 1) + { + ToggleEditor(); + } + } + break; + + case WM_SETTINGCHANGE: + { + if (wparam == SPI_SETWORKAREA) + { + // Changes in taskbar position resulted in different size of work area. + // Invalidate cached work-areas so they can be recreated with latest information. + std::unique_lock writeLock(m_lock); + m_workAreaHandler.Clear(); + OnDisplayChange(DisplayChangeType::WorkArea, writeLock); + } + } + break; + + case WM_DISPLAYCHANGE: + { + // Display resolution changed. Invalidate cached work-areas so they can be recreated with latest information. + std::unique_lock writeLock(m_lock); + m_workAreaHandler.Clear(); + OnDisplayChange(DisplayChangeType::DisplayChange, writeLock); + } + break; + + default: + { + POINT ptScreen; + GetPhysicalCursorPos(&ptScreen); + + if (message == WM_PRIV_SNAP_HOTKEY) + { + OnSnapHotkey(static_cast(lparam)); + } + else if (message == WM_PRIV_VD_INIT) + { + std::unique_lock writeLock(m_lock); + OnDisplayChange(DisplayChangeType::Initialization, writeLock); + } + else if (message == WM_PRIV_VD_SWITCH) + { + std::unique_lock writeLock(m_lock); + OnDisplayChange(DisplayChangeType::VirtualDesktop, writeLock); + } + else if (message == WM_PRIV_VD_UPDATE) + { + std::vector ids{}; + if (VirtualDesktopUtils::GetVirtualDesktopIds(ids)) + { + RegisterVirtualDesktopUpdates(ids); + } + } + else if (message == WM_PRIV_EDITOR) + { + if (lparam == static_cast(EditorExitKind::Exit)) + { + std::unique_lock writeLock(m_lock); + OnEditorExitEvent(writeLock); + } + + { + // Clean up the event either way + std::unique_lock writeLock(m_lock); + m_terminateEditorEvent.release(); + } + } + else if (message == WM_PRIV_MOVESIZESTART) + { + auto hwnd = reinterpret_cast(wparam); + if (auto monitor = MonitorFromPoint(ptScreen, MONITOR_DEFAULTTONULL)) + { + MoveSizeStart(hwnd, monitor, ptScreen); + } + } + else if (message == WM_PRIV_MOVESIZEEND) + { + auto hwnd = reinterpret_cast(wparam); + MoveSizeEnd(hwnd, ptScreen); + } + else if (message == WM_PRIV_LOCATIONCHANGE && InMoveSize()) + { + if (auto monitor = MonitorFromPoint(ptScreen, MONITOR_DEFAULTTONULL)) + { + MoveSizeUpdate(monitor, ptScreen); + } + } + else if (message == WM_PRIV_WINDOWCREATED) + { + auto hwnd = reinterpret_cast(wparam); + WindowCreated(hwnd); + } + else if (message == WM_PRIV_FILE_UPDATE) + { + FancyZonesDataInstance().LoadFancyZonesData(); + std::unique_lock writeLock(m_lock); + UpdateZoneSets(writeLock); + } + else if (message == WM_PRIV_QUICK_LAYOUT_KEY) + { + ApplyQuickLayout(static_cast(lparam)); + } + else if (message == WM_PRIV_SETTINGS_CHANGED) + { + m_settings->ReloadSettings(); + } + else + { + return DefWindowProc(window, message, wparam, lparam); + } + } + break; + } + return 0; +} + +void FancyZones::OnDisplayChange(DisplayChangeType changeType, require_write_lock lock) noexcept +{ + _TRACER_; + if (changeType == DisplayChangeType::VirtualDesktop || + changeType == DisplayChangeType::Initialization) + { + m_previousDesktopId = m_currentDesktopId; + GUID currentVirtualDesktopId{}; + if (VirtualDesktopUtils::GetCurrentVirtualDesktopId(¤tVirtualDesktopId)) + { + m_currentDesktopId = currentVirtualDesktopId; + if (m_previousDesktopId != GUID_NULL && m_currentDesktopId != m_previousDesktopId) + { + Trace::VirtualDesktopChanged(); + } + } + if (changeType == DisplayChangeType::Initialization) + { + std::vector ids{}; + if (VirtualDesktopUtils::GetVirtualDesktopIds(ids) && !ids.empty()) + { + FancyZonesDataInstance().UpdatePrimaryDesktopData(ids[0]); + FancyZonesDataInstance().RemoveDeletedDesktops(ids); + } + } + } + + UpdateZoneWindows(lock); + + if ((changeType == DisplayChangeType::WorkArea) || (changeType == DisplayChangeType::DisplayChange)) + { + if (m_settings->GetSettings()->displayChange_moveWindows) + { + UpdateWindowsPositions(lock); + } + } +} + +void FancyZones::AddZoneWindow(HMONITOR monitor, const std::wstring& deviceId, require_write_lock) noexcept +{ + _TRACER_; + if (m_workAreaHandler.IsNewWorkArea(m_currentDesktopId, monitor)) + { + wil::unique_cotaskmem_string virtualDesktopId; + if (SUCCEEDED(StringFromCLSID(m_currentDesktopId, &virtualDesktopId))) + { + std::wstring uniqueId; + + if (monitor) + { + uniqueId = FancyZonesUtils::GenerateUniqueId(monitor, deviceId, virtualDesktopId.get()); + } + else + { + uniqueId = FancyZonesUtils::GenerateUniqueIdAllMonitorsArea(virtualDesktopId.get()); + } + + std::wstring parentId{}; + auto parentArea = m_workAreaHandler.GetWorkArea(m_previousDesktopId, monitor); + if (parentArea) + { + parentId = parentArea->UniqueId(); + } + auto workArea = MakeZoneWindow(this, m_hinstance, monitor, uniqueId, parentId); + if (workArea) + { + m_workAreaHandler.AddWorkArea(m_currentDesktopId, monitor, workArea); + FancyZonesDataInstance().SaveZoneSettings(); + } + } + } +} + +LRESULT CALLBACK FancyZones::s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept +{ + auto thisRef = reinterpret_cast(GetWindowLongPtr(window, GWLP_USERDATA)); + if (!thisRef && (message == WM_CREATE)) + { + const auto createStruct = reinterpret_cast(lparam); + thisRef = reinterpret_cast(createStruct->lpCreateParams); + SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(thisRef)); + } + + return thisRef ? thisRef->WndProc(window, message, wparam, lparam) : + DefWindowProc(window, message, wparam, lparam); +} + +void FancyZones::UpdateZoneWindows(require_write_lock lock) noexcept +{ + // Mapping between display device name and device index (operating system identifies each display device with an index value). + std::unordered_map displayDeviceIdxMap; + struct capture + { + FancyZones* fancyZones; + std::unordered_map* displayDeviceIdx; + require_write_lock lock; + }; + + auto callback = [](HMONITOR monitor, HDC, RECT*, LPARAM data) -> BOOL { + capture* params = reinterpret_cast(data); + MONITORINFOEX mi{ { .cbSize = sizeof(mi) } }; + if (GetMonitorInfoW(monitor, &mi)) + { + auto& displayDeviceIdxMap = *(params->displayDeviceIdx); + FancyZones* fancyZones = params->fancyZones; + + std::wstring deviceId = FancyZonesUtils::GetDisplayDeviceId(mi.szDevice, displayDeviceIdxMap); + fancyZones->AddZoneWindow(monitor, deviceId, params->lock); + } + return TRUE; + }; + + if (m_settings->GetSettings()->spanZonesAcrossMonitors) + { + AddZoneWindow(nullptr, {}, lock); + } + else + { + capture capture{ this, &displayDeviceIdxMap, lock }; + EnumDisplayMonitors(nullptr, nullptr, callback, reinterpret_cast(&capture)); + } +} + +void FancyZones::UpdateWindowsPositions(require_write_lock) noexcept +{ + auto callback = [](HWND window, LPARAM data) -> BOOL { + size_t bitmask = reinterpret_cast(::GetProp(window, ZonedWindowProperties::PropertyMultipleZoneID)); + + if (bitmask != 0) + { + std::vector indexSet; + for (int i = 0; i < std::numeric_limits::digits; i++) + { + if ((1ull << i) & bitmask) + { + indexSet.push_back(i); + } + } + + auto strongThis = reinterpret_cast(data); + auto zoneWindow = strongThis->m_workAreaHandler.GetWorkArea(window); + if (zoneWindow) + { + strongThis->m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, indexSet, zoneWindow); + } + } + return TRUE; + }; + EnumWindows(callback, reinterpret_cast(this)); +} + +bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexcept +{ + _TRACER_; + HMONITOR current = WorkAreaKeyFromWindow(window); + + std::vector monitorInfo = GetMonitorsSorted(); + if (current && monitorInfo.size() > 1 && m_settings->GetSettings()->moveWindowAcrossMonitors) + { + // Multi monitor environment. + auto currMonitorInfo = std::find(std::begin(monitorInfo), std::end(monitorInfo), current); + do + { + std::unique_lock writeLock(m_lock); + if (m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */, m_workAreaHandler.GetWorkArea(m_currentDesktopId, *currMonitorInfo))) + { + 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 + { + // Single monitor environment, or combined multi-monitor environment. + std::unique_lock writeLock(m_lock); + if (m_settings->GetSettings()->restoreSize) + { + bool moved = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */, m_workAreaHandler.GetWorkArea(m_currentDesktopId, current)); + if (!moved) + { + FancyZonesUtils::RestoreWindowOrigin(window); + FancyZonesUtils::RestoreWindowSize(window); + } + return true; + } + else + { + return m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, true /* cycle through zones */, m_workAreaHandler.GetWorkArea(m_currentDesktopId, current)); + } + } + + 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 && m_settings->GetSettings()->moveWindowAcrossMonitors) + { + // Multi monitor environment. + // First, try to stay on the same monitor + bool success = ProcessDirectedSnapHotkey(window, vkCode, false, m_workAreaHandler.GetWorkArea(m_currentDesktopId, 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(m_currentDesktopId, monitor); + if (workArea) + { + auto zoneSet = workArea->ActiveZoneSet(); + if (zoneSet) + { + const auto zones = zoneSet->GetZones(); + 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; + } + + size_t chosenIdx = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects); + + if (chosenIdx < zoneRects.size()) + { + // Moving to another monitor succeeded + const auto& [trueZoneIdx, zoneWindow] = zoneRectsInfo[chosenIdx]; + m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }, zoneWindow); + 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(m_currentDesktopId, current); + if (workArea) + { + auto zoneSet = workArea->ActiveZoneSet(); + if (zoneSet) + { + const auto zones = zoneSet->GetZones(); + 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, zoneWindow] = zoneRectsInfo[chosenIdx]; + m_windowMoveHandler.MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx }, zoneWindow); + return true; + } + else + { + // Giving up + return false; + } + } + else + { + // Single monitor environment, or combined multi-monitor environment. + return ProcessDirectedSnapHotkey(window, vkCode, true, m_workAreaHandler.GetWorkArea(m_currentDesktopId, 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 (m_settings->GetSettings()->moveWindowsBasedOnPosition) + { + return OnSnapHotkeyBasedOnPosition(window, vkCode); + } + else + { + return (vkCode == VK_LEFT || vkCode == VK_RIGHT) && OnSnapHotkeyBasedOnZoneNumber(window, vkCode); + } + return false; +} + +bool FancyZones::ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr zoneWindow) noexcept +{ + // Check whether Alt is used in the shortcut key combination + if (GetAsyncKeyState(VK_MENU) & 0x8000) + { + return m_windowMoveHandler.ExtendWindowByDirectionAndPosition(window, vkCode, zoneWindow); + } + else + { + return m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle, zoneWindow); + } +} + +void FancyZones::RegisterVirtualDesktopUpdates(std::vector& ids) noexcept +{ + _TRACER_; + std::unique_lock writeLock(m_lock); + + m_workAreaHandler.RegisterUpdates(ids); + std::vector active{}; + if (VirtualDesktopUtils::GetVirtualDesktopIds(active) && !active.empty()) + { + FancyZonesDataInstance().UpdatePrimaryDesktopData(active[0]); + FancyZonesDataInstance().RemoveDeletedDesktops(active); + } +} + +bool FancyZones::IsSplashScreen(HWND window) +{ + wchar_t className[MAX_PATH]; + if (GetClassName(window, className, MAX_PATH) == 0) + { + return false; + } + + return wcscmp(NonLocalizable::SplashClassName, className) == 0; +} + +void FancyZones::OnEditorExitEvent(require_write_lock lock) noexcept +{ + // Collect information about changes in zone layout after editor exited. + FancyZonesDataInstance().LoadFancyZonesData(); + UpdateZoneSets(lock); +} + +void FancyZones::UpdateZoneSets(require_write_lock lock) noexcept +{ + for (auto workArea : m_workAreaHandler.GetAllWorkAreas()) + { + workArea->UpdateActiveZoneSet(); + } + if (m_settings->GetSettings()->zoneSetChange_moveWindows) + { + UpdateWindowsPositions(lock); + } +} + +bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept +{ + auto window = GetForegroundWindow(); + if (m_settings->GetSettings()->overrideSnapHotkeys && FancyZonesUtils::IsCandidateForZoning(window, m_settings->GetSettings()->excludedAppsArray)) + { + HMONITOR monitor = WorkAreaKeyFromWindow(window); + + auto zoneWindow = m_workAreaHandler.GetWorkArea(m_currentDesktopId, monitor); + if (zoneWindow && zoneWindow->ActiveZoneSet() && zoneWindow->ActiveZoneSet()->LayoutType() != FancyZonesDataTypes::ZoneSetLayoutType::Blank) + { + if (vkCode == VK_UP || vkCode == VK_DOWN) + { + return m_settings->GetSettings()->moveWindowsBasedOnPosition; + } + else + { + return true; + } + } + } + return false; +} + +void FancyZones::ApplyQuickLayout(int key) noexcept +{ + std::unique_lock writeLock(m_lock); + + std::wstring uuid; + for (auto [zoneUuid, hotkey] : FancyZonesDataInstance().GetLayoutQuickKeys()) + { + if (hotkey == key) + { + uuid = zoneUuid; + } + } + + auto workArea = m_workAreaHandler.GetWorkAreaFromCursor(m_currentDesktopId); + + // Find a custom zone set with this uuid and apply it + auto customZoneSets = FancyZonesDataInstance().GetCustomZoneSetsMap(); + + if (!customZoneSets.contains(uuid)) + { + return; + } + + FancyZonesDataTypes::ZoneSetData data{ .uuid = uuid, .type = FancyZonesDataTypes::ZoneSetLayoutType::Custom }; + FancyZonesDataInstance().SetActiveZoneSet(workArea->UniqueId(), data); + FancyZonesDataInstance().SaveZoneSettings(); + UpdateZoneSets(writeLock); + FlashZones(writeLock); +} + +void FancyZones::FlashZones(require_write_lock) noexcept +{ + if (m_settings->GetSettings()->flashZonesOnQuickSwitch && !m_windowMoveHandler.IsDragEnabled()) + { + for (auto [monitor, workArea] : m_workAreaHandler.GetWorkAreasByDesktopId(m_currentDesktopId)) + { + workArea->FlashZones(); + } + } +} + +std::vector FancyZones::GetMonitorsSorted() noexcept +{ + std::shared_lock readLock(m_lock); + + 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 +{ + _TRACER_; + std::shared_lock readLock(m_lock); + + std::vector> monitorInfo; + const auto& activeWorkAreaMap = m_workAreaHandler.GetWorkAreasByDesktopId(m_currentDesktopId); + for (const auto& [monitor, workArea] : activeWorkAreaMap) + { + if (workArea->ActiveZoneSet() != 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 (m_settings->GetSettings()->spanZonesAcrossMonitors) + { + return NULL; + } + else + { + return MonitorFromWindow(window, MONITOR_DEFAULTTONULL); + } +} + +winrt::com_ptr MakeFancyZones(HINSTANCE hinstance, + const winrt::com_ptr& settings, + std::function disableCallback) noexcept +{ + if (!settings) + { + return nullptr; + } + + return winrt::make_self(hinstance, settings, disableCallback); +} diff --git a/src/modules/fancyzones/lib/FancyZones.h b/src/modules/fancyzones/FancyZonesLib/FancyZones.h similarity index 100% rename from src/modules/fancyzones/lib/FancyZones.h rename to src/modules/fancyzones/FancyZonesLib/FancyZones.h diff --git a/src/modules/fancyzones/lib/FancyZonesData.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesData.cpp similarity index 99% rename from src/modules/fancyzones/lib/FancyZonesData.cpp rename to src/modules/fancyzones/FancyZonesLib/FancyZonesData.cpp index c72524ace0..1daaa03228 100644 --- a/src/modules/fancyzones/lib/FancyZonesData.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData.cpp @@ -7,7 +7,7 @@ #include "CallTracer.h" #include -#include +#include #include #include @@ -22,9 +22,9 @@ // Non-localizable strings namespace NonLocalizable { - const wchar_t FancyZonesStr[] = L"FancyZones"; const wchar_t NullStr[] = L"null"; + const wchar_t FancyZonesSettingsFile[] = L"settings.json"; const wchar_t FancyZonesDataFile[] = L"zones-settings.json"; const wchar_t FancyZonesAppZoneHistoryFile[] = L"app-zone-history.json"; const wchar_t FancyZonesEditorParametersFile[] = L"editor-parameters.json"; @@ -149,6 +149,7 @@ FancyZonesData::FancyZonesData() { std::wstring saveFolderPath = PTSettingsHelper::get_module_save_folder_location(NonLocalizable::FancyZonesStr); + settingsFileName = saveFolderPath + L"\\" + std::wstring(NonLocalizable::FancyZonesSettingsFile); zonesSettingsFileName = saveFolderPath + L"\\" + std::wstring(NonLocalizable::FancyZonesDataFile); appZoneHistoryFileName = saveFolderPath + L"\\" + std::wstring(NonLocalizable::FancyZonesAppZoneHistoryFile); editorParametersFileName = saveFolderPath + L"\\" + std::wstring(NonLocalizable::FancyZonesEditorParametersFile); diff --git a/src/modules/fancyzones/lib/FancyZonesData.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesData.h similarity index 94% rename from src/modules/fancyzones/lib/FancyZonesData.h rename to src/modules/fancyzones/FancyZonesLib/FancyZonesData.h index 34dc72b6fd..9cc23e3d94 100644 --- a/src/modules/fancyzones/lib/FancyZonesData.h +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData.h @@ -11,7 +11,13 @@ #include #include #include -#include +#include + +// Non-localizable strings +namespace NonLocalizable +{ + const wchar_t FancyZonesStr[] = L"FancyZones"; +} namespace FancyZonesDataTypes { @@ -59,6 +65,11 @@ public: return zonesSettingsFileName; } + inline const std::wstring& GetSettingsFileName() const + { + return settingsFileName; + } + bool AddDevice(const std::wstring& deviceId); void CloneDeviceInfo(const std::wstring& source, const std::wstring& destination); void UpdatePrimaryDesktopData(const std::wstring& desktopId); @@ -130,6 +141,7 @@ private: // Maps zoneset UUID with quick access keys JSONHelpers::TLayoutQuickKeysMap quickKeysMap{}; + std::wstring settingsFileName; std::wstring zonesSettingsFileName; std::wstring appZoneHistoryFileName; std::wstring editorParametersFileName; diff --git a/src/modules/fancyzones/lib/FancyZonesDataTypes.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.cpp similarity index 100% rename from src/modules/fancyzones/lib/FancyZonesDataTypes.cpp rename to src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.cpp diff --git a/src/modules/fancyzones/lib/FancyZonesDataTypes.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.h similarity index 100% rename from src/modules/fancyzones/lib/FancyZonesDataTypes.h rename to src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.h diff --git a/src/modules/fancyzones/lib/FancyZonesLib.vcxproj b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj similarity index 96% rename from src/modules/fancyzones/lib/FancyZonesLib.vcxproj rename to src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj index 060819a6dc..0ac31d0179 100644 --- a/src/modules/fancyzones/lib/FancyZonesLib.vcxproj +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj @@ -1,114 +1,117 @@ - - - - - - - - 16.0 - {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} - Win32Proj - lib - FancyZonesLib - - - - StaticLibrary - - - - - - - - - - - - - - - $(SolutionDir)$(Platform)\$(Configuration)\modules\FancyZones\ - - - - _LIB;%(PreprocessorDefinitions) - ..\;..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create - - - - - - - - - - - - - - - - {1d5be09d-78c0-4fd7-af00-ae7c1af7c525} - - - - - - {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - + + + + + + + + 16.0 + {F9C68EDF-AC74-4B77-9AF1-005D9C9F6A99} + Win32Proj + FancyZonesLib + FancyZonesLib + + + + StaticLibrary + + + + + + + + + + + + + + + $(SolutionDir)$(Platform)\$(Configuration)\modules\FancyZones\ + + + + _LIB;%(PreprocessorDefinitions) + ..\;..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + + + + + + + + + + + + + + + + {caba8dfb-823b-4bf2-93ac-3f31984150d9} + + + {1d5be09d-78c0-4fd7-af00-ae7c1af7c525} + + + + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/modules/fancyzones/lib/FancyZonesLib.vcxproj.filters b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters similarity index 96% rename from src/modules/fancyzones/lib/FancyZonesLib.vcxproj.filters rename to src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters index 460fff8c55..b3c04c2c80 100644 --- a/src/modules/fancyzones/lib/FancyZonesLib.vcxproj.filters +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters @@ -1,167 +1,167 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {093625ff-2415-4c2c-842c-0ee7fcb1d203} - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Generated Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - - Header Files - - - Resource Files - - - Resource Files - - - - - Generated Files - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {093625ff-2415-4c2c-842c-0ee7fcb1d203} + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Generated Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + + Header Files + + + Resource Files + + + Resource Files + + + + + Generated Files + + \ No newline at end of file diff --git a/src/modules/fancyzones/lib/FancyZonesWinHookEventIDs.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesWinHookEventIDs.cpp similarity index 100% rename from src/modules/fancyzones/lib/FancyZonesWinHookEventIDs.cpp rename to src/modules/fancyzones/FancyZonesLib/FancyZonesWinHookEventIDs.cpp diff --git a/src/modules/fancyzones/lib/FancyZonesWinHookEventIDs.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesWinHookEventIDs.h similarity index 100% rename from src/modules/fancyzones/lib/FancyZonesWinHookEventIDs.h rename to src/modules/fancyzones/FancyZonesLib/FancyZonesWinHookEventIDs.h diff --git a/src/modules/fancyzones/lib/FileWatcher.cpp b/src/modules/fancyzones/FancyZonesLib/FileWatcher.cpp similarity index 100% rename from src/modules/fancyzones/lib/FileWatcher.cpp rename to src/modules/fancyzones/FancyZonesLib/FileWatcher.cpp diff --git a/src/modules/fancyzones/lib/FileWatcher.h b/src/modules/fancyzones/FancyZonesLib/FileWatcher.h similarity index 100% rename from src/modules/fancyzones/lib/FileWatcher.h rename to src/modules/fancyzones/FancyZonesLib/FileWatcher.h diff --git a/src/modules/fancyzones/lib/GenericKeyHook.h b/src/modules/fancyzones/FancyZonesLib/GenericKeyHook.h similarity index 100% rename from src/modules/fancyzones/lib/GenericKeyHook.h rename to src/modules/fancyzones/FancyZonesLib/GenericKeyHook.h diff --git a/src/modules/fancyzones/lib/JsonHelpers.cpp b/src/modules/fancyzones/FancyZonesLib/JsonHelpers.cpp similarity index 100% rename from src/modules/fancyzones/lib/JsonHelpers.cpp rename to src/modules/fancyzones/FancyZonesLib/JsonHelpers.cpp diff --git a/src/modules/fancyzones/lib/JsonHelpers.h b/src/modules/fancyzones/FancyZonesLib/JsonHelpers.h similarity index 100% rename from src/modules/fancyzones/lib/JsonHelpers.h rename to src/modules/fancyzones/FancyZonesLib/JsonHelpers.h diff --git a/src/modules/fancyzones/lib/KeyState.h b/src/modules/fancyzones/FancyZonesLib/KeyState.h similarity index 100% rename from src/modules/fancyzones/lib/KeyState.h rename to src/modules/fancyzones/FancyZonesLib/KeyState.h diff --git a/src/modules/fancyzones/lib/LocProject.json b/src/modules/fancyzones/FancyZonesLib/LocProject.json similarity index 56% rename from src/modules/fancyzones/lib/LocProject.json rename to src/modules/fancyzones/FancyZonesLib/LocProject.json index 3d87019387..cc6761cef0 100644 --- a/src/modules/fancyzones/lib/LocProject.json +++ b/src/modules/fancyzones/FancyZonesLib/LocProject.json @@ -4,9 +4,9 @@ "LanguageSet": "Azure_Languages", "LocItems": [ { - "SourceFile": "src\\modules\\fancyzones\\lib\\Resources.resx", + "SourceFile": "src\\modules\\fancyzones\\FancyZonesLib\\Resources.resx", "CopyOption": "LangIDOnName", - "OutputPath": "src\\modules\\fancyzones\\lib" + "OutputPath": "src\\modules\\fancyzones\\FancyZonesLib" } ] } diff --git a/src/modules/fancyzones/lib/MonitorWorkAreaHandler.cpp b/src/modules/fancyzones/FancyZonesLib/MonitorWorkAreaHandler.cpp similarity index 100% rename from src/modules/fancyzones/lib/MonitorWorkAreaHandler.cpp rename to src/modules/fancyzones/FancyZonesLib/MonitorWorkAreaHandler.cpp diff --git a/src/modules/fancyzones/lib/MonitorWorkAreaHandler.h b/src/modules/fancyzones/FancyZonesLib/MonitorWorkAreaHandler.h similarity index 100% rename from src/modules/fancyzones/lib/MonitorWorkAreaHandler.h rename to src/modules/fancyzones/FancyZonesLib/MonitorWorkAreaHandler.h diff --git a/src/modules/fancyzones/lib/OnThreadExecutor.cpp b/src/modules/fancyzones/FancyZonesLib/OnThreadExecutor.cpp similarity index 100% rename from src/modules/fancyzones/lib/OnThreadExecutor.cpp rename to src/modules/fancyzones/FancyZonesLib/OnThreadExecutor.cpp diff --git a/src/modules/fancyzones/lib/Resources.resx b/src/modules/fancyzones/FancyZonesLib/Resources.resx similarity index 100% rename from src/modules/fancyzones/lib/Resources.resx rename to src/modules/fancyzones/FancyZonesLib/Resources.resx diff --git a/src/modules/fancyzones/lib/SecondaryMouseButtonsHook.cpp b/src/modules/fancyzones/FancyZonesLib/SecondaryMouseButtonsHook.cpp similarity index 100% rename from src/modules/fancyzones/lib/SecondaryMouseButtonsHook.cpp rename to src/modules/fancyzones/FancyZonesLib/SecondaryMouseButtonsHook.cpp diff --git a/src/modules/fancyzones/lib/SecondaryMouseButtonsHook.h b/src/modules/fancyzones/FancyZonesLib/SecondaryMouseButtonsHook.h similarity index 100% rename from src/modules/fancyzones/lib/SecondaryMouseButtonsHook.h rename to src/modules/fancyzones/FancyZonesLib/SecondaryMouseButtonsHook.h diff --git a/src/modules/fancyzones/lib/Settings.cpp b/src/modules/fancyzones/FancyZonesLib/Settings.cpp similarity index 90% rename from src/modules/fancyzones/lib/Settings.cpp rename to src/modules/fancyzones/FancyZonesLib/Settings.cpp index 111a50c317..6445c2d8ec 100644 --- a/src/modules/fancyzones/lib/Settings.cpp +++ b/src/modules/fancyzones/FancyZonesLib/Settings.cpp @@ -1,297 +1,280 @@ -#include "pch.h" -#include -#include - -#include "lib/Settings.h" -#include "lib/FancyZones.h" -#include "trace.h" - -// Non-Localizable strings -namespace NonLocalizable -{ - // FancyZones settings descriptions are localized, but underlying toggle (spinner, color picker) names are not. - const wchar_t ShiftDragID[] = L"fancyzones_shiftDrag"; - const wchar_t MouseSwitchID[] = L"fancyzones_mouseSwitch"; - const wchar_t OverrideSnapHotKeysID[] = L"fancyzones_overrideSnapHotkeys"; - const wchar_t MoveWindowAcrossMonitorsID[] = L"fancyzones_moveWindowAcrossMonitors"; - const wchar_t MoveWindowsBasedOnPositionID[] = L"fancyzones_moveWindowsBasedOnPosition"; - const wchar_t OverlappingZonesAlgorithmID[] = L"fancyzones_overlappingZonesAlgorithm"; - const wchar_t DisplayChangeMoveWindowsID[] = L"fancyzones_displayChange_moveWindows"; - const wchar_t ZoneSetChangeMoveWindowsID[] = L"fancyzones_zoneSetChange_moveWindows"; - const wchar_t AppLastZoneMoveWindowsID[] = L"fancyzones_appLastZone_moveWindows"; - const wchar_t OpenWindowOnActiveMonitorID[] = L"fancyzones_openWindowOnActiveMonitor"; - const wchar_t RestoreSizeID[] = L"fancyzones_restoreSize"; - const wchar_t QuickLayoutSwitch[] = L"fancyzones_quickLayoutSwitch"; - const wchar_t FlashZonesOnQuickSwitch[] = L"fancyzones_flashZonesOnQuickSwitch"; - const wchar_t UseCursorPosEditorStartupScreenID[] = L"use_cursorpos_editor_startupscreen"; - const wchar_t ShowOnAllMonitorsID[] = L"fancyzones_show_on_all_monitors"; - const wchar_t SpanZonesAcrossMonitorsID[] = L"fancyzones_span_zones_across_monitors"; - const wchar_t MakeDraggedWindowTransparentID[] = L"fancyzones_makeDraggedWindowTransparent"; - - const wchar_t ZoneColorID[] = L"fancyzones_zoneColor"; - const wchar_t ZoneBorderColorID[] = L"fancyzones_zoneBorderColor"; - const wchar_t ZoneHighlightColorID[] = L"fancyzones_zoneHighlightColor"; - const wchar_t EditorHotkeyID[] = L"fancyzones_editor_hotkey"; - const wchar_t ExcludedAppsID[] = L"fancyzones_excluded_apps"; - const wchar_t ZoneHighlightOpacityID[] = L"fancyzones_highlight_opacity"; - - const wchar_t ToggleEditorActionID[] = L"ToggledFZEditor"; - const wchar_t IconKeyID[] = L"pt-fancy-zones"; - const wchar_t OverviewURL[] = L"https://aka.ms/PowerToysOverview_FancyZones"; - const wchar_t VideoURL[] = L"https://youtu.be/rTtGzZYAXgY"; - const wchar_t PowerToysIssuesURL[] = L"https://aka.ms/powerToysReportBug"; -} - -struct FancyZonesSettings : winrt::implements -{ -public: - FancyZonesSettings(HINSTANCE hinstance, PCWSTR name, PCWSTR key) : - m_hinstance(hinstance), - m_moduleName(name), - m_moduleKey(key) - { - LoadSettings(name, true); - } - - IFACEMETHODIMP_(void) - SetCallback(IFancyZonesCallback* callback) { m_callback = callback; } - IFACEMETHODIMP_(void) - ResetCallback() { m_callback = nullptr; } - IFACEMETHODIMP_(bool) - GetConfig(_Out_ PWSTR buffer, _Out_ int* buffer_sizeg) noexcept; - IFACEMETHODIMP_(void) - SetConfig(PCWSTR config) noexcept; - IFACEMETHODIMP_(void) - CallCustomAction(PCWSTR action) noexcept; - IFACEMETHODIMP_(const Settings*) - GetSettings() const noexcept { return &m_settings; } - -private: - void LoadSettings(PCWSTR config, bool fromFile) noexcept; - void SaveSettings() noexcept; - - IFancyZonesCallback* m_callback{}; - const HINSTANCE m_hinstance; - PCWSTR m_moduleName{}; - PCWSTR m_moduleKey{}; - - Settings m_settings; - - struct - { - PCWSTR name; - bool* value; - int resourceId; - } m_configBools[16] = { - { NonLocalizable::ShiftDragID, &m_settings.shiftDrag, IDS_SETTING_DESCRIPTION_SHIFTDRAG }, - { NonLocalizable::MouseSwitchID, &m_settings.mouseSwitch, IDS_SETTING_DESCRIPTION_MOUSESWITCH }, - { NonLocalizable::OverrideSnapHotKeysID, &m_settings.overrideSnapHotkeys, IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS }, - { NonLocalizable::MoveWindowAcrossMonitorsID, &m_settings.moveWindowAcrossMonitors, IDS_SETTING_DESCRIPTION_MOVE_WINDOW_ACROSS_MONITORS }, - { NonLocalizable::MoveWindowsBasedOnPositionID, &m_settings.moveWindowsBasedOnPosition, IDS_SETTING_DESCRIPTION_MOVE_WINDOWS_BASED_ON_POSITION }, - { NonLocalizable::DisplayChangeMoveWindowsID, &m_settings.displayChange_moveWindows, IDS_SETTING_DESCRIPTION_DISPLAYCHANGE_MOVEWINDOWS }, - { NonLocalizable::ZoneSetChangeMoveWindowsID, &m_settings.zoneSetChange_moveWindows, IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS }, - { NonLocalizable::AppLastZoneMoveWindowsID, &m_settings.appLastZone_moveWindows, IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS }, - { NonLocalizable::OpenWindowOnActiveMonitorID, &m_settings.openWindowOnActiveMonitor, IDS_SETTING_DESCRIPTION_OPEN_WINDOW_ON_ACTIVE_MONITOR }, - { NonLocalizable::RestoreSizeID, &m_settings.restoreSize, IDS_SETTING_DESCRIPTION_RESTORESIZE }, - { NonLocalizable::QuickLayoutSwitch, &m_settings.quickLayoutSwitch, IDS_SETTING_DESCRIPTION_QUICKLAYOUTSWITCH }, - { NonLocalizable::FlashZonesOnQuickSwitch, &m_settings.flashZonesOnQuickSwitch, IDS_SETTING_DESCRIPTION_FLASHZONESONQUICKSWITCH }, - { NonLocalizable::UseCursorPosEditorStartupScreenID, &m_settings.use_cursorpos_editor_startupscreen, IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN }, - { NonLocalizable::ShowOnAllMonitorsID, &m_settings.showZonesOnAllMonitors, IDS_SETTING_DESCRIPTION_SHOW_FANCY_ZONES_ON_ALL_MONITORS }, - { NonLocalizable::SpanZonesAcrossMonitorsID, &m_settings.spanZonesAcrossMonitors, IDS_SETTING_DESCRIPTION_SPAN_ZONES_ACROSS_MONITORS }, - { NonLocalizable::MakeDraggedWindowTransparentID, &m_settings.makeDraggedWindowTransparent, IDS_SETTING_DESCRIPTION_MAKE_DRAGGED_WINDOW_TRANSPARENT }, - }; -}; - -IFACEMETHODIMP_(bool) -FancyZonesSettings::GetConfig(_Out_ PWSTR buffer, _Out_ int* buffer_size) noexcept -{ - PowerToysSettings::Settings settings(m_hinstance, m_moduleName); - - // Pass a string literal or a resource id to Settings::set_description(). - settings.set_description(IDS_SETTING_DESCRIPTION); - settings.set_icon_key(NonLocalizable::IconKeyID); - settings.set_overview_link(NonLocalizable::OverviewURL); - settings.set_video_link(NonLocalizable::VideoURL); - - // Add a custom action property. When using this settings type, the "PowertoyModuleIface::call_custom_action()" - // method should be overridden as well. - settings.add_custom_action( - NonLocalizable::ToggleEditorActionID, // action name. - IDS_SETTING_LAUNCH_EDITOR_LABEL, - IDS_SETTING_LAUNCH_EDITOR_BUTTON, - IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION); - settings.add_hotkey(NonLocalizable::EditorHotkeyID, IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL, m_settings.editorHotkey); - - for (auto const& setting : m_configBools) - { - settings.add_bool_toggle(setting.name, setting.resourceId, *setting.value); - } - - settings.add_color_picker(NonLocalizable::ZoneHighlightColorID, IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR, m_settings.zoneHighlightColor); - settings.add_color_picker(NonLocalizable::ZoneColorID, IDS_SETTING_DESCRIPTION_ZONECOLOR, m_settings.zoneColor); - settings.add_color_picker(NonLocalizable::ZoneBorderColorID, IDS_SETTING_DESCRIPTION_ZONE_BORDER_COLOR, m_settings.zoneBorderColor); - - settings.add_int_spinner(NonLocalizable::ZoneHighlightOpacityID, IDS_SETTINGS_HIGHLIGHT_OPACITY, m_settings.zoneHighlightOpacity, 0, 100, 1); - - settings.add_multiline_string(NonLocalizable::ExcludedAppsID, IDS_SETTING_EXCLUDED_APPS_DESCRIPTION, m_settings.excludedApps); - - return settings.serialize_to_buffer(buffer, buffer_size); -} - -IFACEMETHODIMP_(void) -FancyZonesSettings::SetConfig(PCWSTR serializedPowerToysSettingsJson) noexcept -{ - LoadSettings(serializedPowerToysSettingsJson, false /*fromFile*/); - SaveSettings(); - if (m_callback) - { - m_callback->SettingsChanged(); - } - Trace::SettingsChanged(m_settings); -} - -IFACEMETHODIMP_(void) -FancyZonesSettings::CallCustomAction(PCWSTR action) noexcept -{ - try - { - // Parse the action values, including name. - PowerToysSettings::CustomActionObject action_object = - PowerToysSettings::CustomActionObject::from_json_string(action); - - if (m_callback && action_object.get_name() == NonLocalizable::ToggleEditorActionID) - { - m_callback->ToggleEditor(); - } - } - catch (...) - { - // Currently only custom action coming from main PowerToys window is request to launch editor. - // If new custom action is added, error message need to be modified. - std::wstring errorMessage = GET_RESOURCE_STRING(IDS_FANCYZONES_EDITOR_LAUNCH_ERROR) + L" " + NonLocalizable::PowerToysIssuesURL; - MessageBox(NULL, - errorMessage.c_str(), - GET_RESOURCE_STRING(IDS_POWERTOYS_FANCYZONES).c_str(), - MB_OK); - } -} - -void FancyZonesSettings::LoadSettings(PCWSTR config, bool fromFile) noexcept -{ - try - { - PowerToysSettings::PowerToyValues values = fromFile ? - PowerToysSettings::PowerToyValues::load_from_settings_file(m_moduleKey) : - PowerToysSettings::PowerToyValues::from_json_string(config, m_moduleKey); - - for (auto const& setting : m_configBools) - { - if (const auto val = values.get_bool_value(setting.name)) - { - *setting.value = *val; - } - } - - if (auto val = values.get_string_value(NonLocalizable::ZoneColorID)) - { - m_settings.zoneColor = std::move(*val); - } - - if (auto val = values.get_string_value(NonLocalizable::ZoneBorderColorID)) - { - m_settings.zoneBorderColor = std::move(*val); - } - - if (auto val = values.get_string_value(NonLocalizable::ZoneHighlightColorID)) - { - m_settings.zoneHighlightColor = std::move(*val); - } - - if (const auto val = values.get_json(NonLocalizable::EditorHotkeyID)) - { - m_settings.editorHotkey = PowerToysSettings::HotkeyObject::from_json(*val); - } - - if (auto val = values.get_string_value(NonLocalizable::ExcludedAppsID)) - { - m_settings.excludedApps = std::move(*val); - m_settings.excludedAppsArray.clear(); - auto excludedUppercase = m_settings.excludedApps; - CharUpperBuffW(excludedUppercase.data(), (DWORD)excludedUppercase.length()); - std::wstring_view view(excludedUppercase); - while (view.starts_with('\n') || view.starts_with('\r')) - { - view.remove_prefix(1); - } - while (!view.empty()) - { - auto pos = (std::min)(view.find_first_of(L"\r\n"), view.length()); - m_settings.excludedAppsArray.emplace_back(view.substr(0, pos)); - view.remove_prefix(pos); - while (view.starts_with('\n') || view.starts_with('\r')) - { - view.remove_prefix(1); - } - } - } - - if (auto val = values.get_int_value(NonLocalizable::ZoneHighlightOpacityID)) - { - m_settings.zoneHighlightOpacity = *val; - } - - if (auto val = values.get_int_value(NonLocalizable::OverlappingZonesAlgorithmID)) - { - // Avoid undefined behavior - if (*val >= 0 || *val < (int)Settings::OverlappingZonesAlgorithm::EnumElements) - { - m_settings.overlappingZonesAlgorithm = (Settings::OverlappingZonesAlgorithm)*val; - } - } - } - catch (...) - { - // Failure to load settings does not break FancyZones functionality. Display error message and continue with default settings. - MessageBox(NULL, - GET_RESOURCE_STRING(IDS_FANCYZONES_SETTINGS_LOAD_ERROR).c_str(), - GET_RESOURCE_STRING(IDS_POWERTOYS_FANCYZONES).c_str(), - MB_OK); - } -} - -void FancyZonesSettings::SaveSettings() noexcept -{ - try - { - PowerToysSettings::PowerToyValues values(m_moduleName, m_moduleKey); - - for (auto const& setting : m_configBools) - { - values.add_property(setting.name, *setting.value); - } - - values.add_property(NonLocalizable::ZoneColorID, m_settings.zoneColor); - values.add_property(NonLocalizable::ZoneBorderColorID, m_settings.zoneBorderColor); - values.add_property(NonLocalizable::ZoneHighlightColorID, m_settings.zoneHighlightColor); - values.add_property(NonLocalizable::ZoneHighlightOpacityID, m_settings.zoneHighlightOpacity); - values.add_property(NonLocalizable::OverlappingZonesAlgorithmID, (int)m_settings.overlappingZonesAlgorithm); - values.add_property(NonLocalizable::EditorHotkeyID, m_settings.editorHotkey.get_json()); - values.add_property(NonLocalizable::ExcludedAppsID, m_settings.excludedApps); - - values.save_to_settings_file(); - } - catch (...) - { - // Failure to save settings does not break FancyZones functionality. Display error message and continue with currently cached settings. - std::wstring errorMessage = GET_RESOURCE_STRING(IDS_FANCYZONES_SETTINGS_LOAD_ERROR) + L" " + NonLocalizable::PowerToysIssuesURL; - MessageBox(NULL, - errorMessage.c_str(), - GET_RESOURCE_STRING(IDS_POWERTOYS_FANCYZONES).c_str(), - MB_OK); - } -} - -winrt::com_ptr MakeFancyZonesSettings(HINSTANCE hinstance, PCWSTR name, PCWSTR key) noexcept -{ - return winrt::make_self(hinstance, name, key); -} +#include "pch.h" +#include +#include + +#include "FancyZonesLib/Settings.h" +#include "FancyZonesLib/FancyZones.h" +#include "trace.h" + +// Non-Localizable strings +namespace NonLocalizable +{ + // FancyZones settings descriptions are localized, but underlying toggle (spinner, color picker) names are not. + const wchar_t ShiftDragID[] = L"fancyzones_shiftDrag"; + const wchar_t MouseSwitchID[] = L"fancyzones_mouseSwitch"; + const wchar_t OverrideSnapHotKeysID[] = L"fancyzones_overrideSnapHotkeys"; + const wchar_t MoveWindowAcrossMonitorsID[] = L"fancyzones_moveWindowAcrossMonitors"; + const wchar_t MoveWindowsBasedOnPositionID[] = L"fancyzones_moveWindowsBasedOnPosition"; + const wchar_t OverlappingZonesAlgorithmID[] = L"fancyzones_overlappingZonesAlgorithm"; + const wchar_t DisplayChangeMoveWindowsID[] = L"fancyzones_displayChange_moveWindows"; + const wchar_t ZoneSetChangeMoveWindowsID[] = L"fancyzones_zoneSetChange_moveWindows"; + const wchar_t AppLastZoneMoveWindowsID[] = L"fancyzones_appLastZone_moveWindows"; + const wchar_t OpenWindowOnActiveMonitorID[] = L"fancyzones_openWindowOnActiveMonitor"; + const wchar_t RestoreSizeID[] = L"fancyzones_restoreSize"; + const wchar_t QuickLayoutSwitch[] = L"fancyzones_quickLayoutSwitch"; + const wchar_t FlashZonesOnQuickSwitch[] = L"fancyzones_flashZonesOnQuickSwitch"; + const wchar_t UseCursorPosEditorStartupScreenID[] = L"use_cursorpos_editor_startupscreen"; + const wchar_t ShowOnAllMonitorsID[] = L"fancyzones_show_on_all_monitors"; + const wchar_t SpanZonesAcrossMonitorsID[] = L"fancyzones_span_zones_across_monitors"; + const wchar_t MakeDraggedWindowTransparentID[] = L"fancyzones_makeDraggedWindowTransparent"; + + const wchar_t ZoneColorID[] = L"fancyzones_zoneColor"; + const wchar_t ZoneBorderColorID[] = L"fancyzones_zoneBorderColor"; + const wchar_t ZoneHighlightColorID[] = L"fancyzones_zoneHighlightColor"; + const wchar_t EditorHotkeyID[] = L"fancyzones_editor_hotkey"; + const wchar_t ExcludedAppsID[] = L"fancyzones_excluded_apps"; + const wchar_t ZoneHighlightOpacityID[] = L"fancyzones_highlight_opacity"; + + const wchar_t ToggleEditorActionID[] = L"ToggledFZEditor"; + const wchar_t IconKeyID[] = L"pt-fancy-zones"; + const wchar_t OverviewURL[] = L"https://aka.ms/PowerToysOverview_FancyZones"; + const wchar_t VideoURL[] = L"https://youtu.be/rTtGzZYAXgY"; + const wchar_t PowerToysIssuesURL[] = L"https://aka.ms/powerToysReportBug"; +} + +struct FancyZonesSettings : winrt::implements +{ +public: + FancyZonesSettings(HINSTANCE hinstance, PCWSTR name, PCWSTR key) : + m_hinstance(hinstance), + m_moduleName(name), + m_moduleKey(key) + { + LoadSettings(name, true); + } + + IFACEMETHODIMP_(void) + SetCallback(IFancyZonesCallback* callback) { m_callback = callback; } + IFACEMETHODIMP_(void) + ResetCallback() { m_callback = nullptr; } + IFACEMETHODIMP_(bool) + GetConfig(_Out_ PWSTR buffer, _Out_ int* buffer_sizeg) noexcept; + IFACEMETHODIMP_(void) + SetConfig(PCWSTR config) noexcept; + IFACEMETHODIMP_(void) + ReloadSettings() noexcept; + IFACEMETHODIMP_(const Settings*) + GetSettings() const noexcept { return &m_settings; } + +private: + void LoadSettings(PCWSTR config, bool fromFile) noexcept; + void SaveSettings() noexcept; + + IFancyZonesCallback* m_callback{}; + const HINSTANCE m_hinstance; + std::wstring m_moduleName{}; + std::wstring m_moduleKey{}; + + Settings m_settings; + + struct + { + PCWSTR name; + bool* value; + int resourceId; + } m_configBools[16] = { + { NonLocalizable::ShiftDragID, &m_settings.shiftDrag, IDS_SETTING_DESCRIPTION_SHIFTDRAG }, + { NonLocalizable::MouseSwitchID, &m_settings.mouseSwitch, IDS_SETTING_DESCRIPTION_MOUSESWITCH }, + { NonLocalizable::OverrideSnapHotKeysID, &m_settings.overrideSnapHotkeys, IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS }, + { NonLocalizable::MoveWindowAcrossMonitorsID, &m_settings.moveWindowAcrossMonitors, IDS_SETTING_DESCRIPTION_MOVE_WINDOW_ACROSS_MONITORS }, + { NonLocalizable::MoveWindowsBasedOnPositionID, &m_settings.moveWindowsBasedOnPosition, IDS_SETTING_DESCRIPTION_MOVE_WINDOWS_BASED_ON_POSITION }, + { NonLocalizable::DisplayChangeMoveWindowsID, &m_settings.displayChange_moveWindows, IDS_SETTING_DESCRIPTION_DISPLAYCHANGE_MOVEWINDOWS }, + { NonLocalizable::ZoneSetChangeMoveWindowsID, &m_settings.zoneSetChange_moveWindows, IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS }, + { NonLocalizable::AppLastZoneMoveWindowsID, &m_settings.appLastZone_moveWindows, IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS }, + { NonLocalizable::OpenWindowOnActiveMonitorID, &m_settings.openWindowOnActiveMonitor, IDS_SETTING_DESCRIPTION_OPEN_WINDOW_ON_ACTIVE_MONITOR }, + { NonLocalizable::RestoreSizeID, &m_settings.restoreSize, IDS_SETTING_DESCRIPTION_RESTORESIZE }, + { NonLocalizable::QuickLayoutSwitch, &m_settings.quickLayoutSwitch, IDS_SETTING_DESCRIPTION_QUICKLAYOUTSWITCH }, + { NonLocalizable::FlashZonesOnQuickSwitch, &m_settings.flashZonesOnQuickSwitch, IDS_SETTING_DESCRIPTION_FLASHZONESONQUICKSWITCH }, + { NonLocalizable::UseCursorPosEditorStartupScreenID, &m_settings.use_cursorpos_editor_startupscreen, IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN }, + { NonLocalizable::ShowOnAllMonitorsID, &m_settings.showZonesOnAllMonitors, IDS_SETTING_DESCRIPTION_SHOW_FANCY_ZONES_ON_ALL_MONITORS }, + { NonLocalizable::SpanZonesAcrossMonitorsID, &m_settings.spanZonesAcrossMonitors, IDS_SETTING_DESCRIPTION_SPAN_ZONES_ACROSS_MONITORS }, + { NonLocalizable::MakeDraggedWindowTransparentID, &m_settings.makeDraggedWindowTransparent, IDS_SETTING_DESCRIPTION_MAKE_DRAGGED_WINDOW_TRANSPARENT }, + }; +}; + +IFACEMETHODIMP_(bool) +FancyZonesSettings::GetConfig(_Out_ PWSTR buffer, _Out_ int* buffer_size) noexcept +{ + PowerToysSettings::Settings settings(m_hinstance, m_moduleName); + + // Pass a string literal or a resource id to Settings::set_description(). + settings.set_description(IDS_SETTING_DESCRIPTION); + settings.set_icon_key(NonLocalizable::IconKeyID); + settings.set_overview_link(NonLocalizable::OverviewURL); + settings.set_video_link(NonLocalizable::VideoURL); + + // Add a custom action property. When using this settings type, the "PowertoyModuleIface::call_custom_action()" + // method should be overridden as well. + settings.add_custom_action( + NonLocalizable::ToggleEditorActionID, // action name. + IDS_SETTING_LAUNCH_EDITOR_LABEL, + IDS_SETTING_LAUNCH_EDITOR_BUTTON, + IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION); + settings.add_hotkey(NonLocalizable::EditorHotkeyID, IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL, m_settings.editorHotkey); + + for (auto const& setting : m_configBools) + { + settings.add_bool_toggle(setting.name, setting.resourceId, *setting.value); + } + + settings.add_color_picker(NonLocalizable::ZoneHighlightColorID, IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR, m_settings.zoneHighlightColor); + settings.add_color_picker(NonLocalizable::ZoneColorID, IDS_SETTING_DESCRIPTION_ZONECOLOR, m_settings.zoneColor); + settings.add_color_picker(NonLocalizable::ZoneBorderColorID, IDS_SETTING_DESCRIPTION_ZONE_BORDER_COLOR, m_settings.zoneBorderColor); + + settings.add_int_spinner(NonLocalizable::ZoneHighlightOpacityID, IDS_SETTINGS_HIGHLIGHT_OPACITY, m_settings.zoneHighlightOpacity, 0, 100, 1); + + settings.add_multiline_string(NonLocalizable::ExcludedAppsID, IDS_SETTING_EXCLUDED_APPS_DESCRIPTION, m_settings.excludedApps); + + return settings.serialize_to_buffer(buffer, buffer_size); +} + +IFACEMETHODIMP_(void) +FancyZonesSettings::SetConfig(PCWSTR serializedPowerToysSettingsJson) noexcept +{ + LoadSettings(serializedPowerToysSettingsJson, false /*fromFile*/); + SaveSettings(); + if (m_callback) + { + m_callback->SettingsChanged(); + } +} + +IFACEMETHODIMP_(void) +FancyZonesSettings::ReloadSettings() noexcept +{ + LoadSettings(m_moduleKey.c_str(), true /*fromFile*/); + if (m_callback) + { + m_callback->SettingsChanged(); + } +} + +void FancyZonesSettings::LoadSettings(PCWSTR config, bool fromFile) noexcept +{ + try + { + PowerToysSettings::PowerToyValues values = fromFile ? + PowerToysSettings::PowerToyValues::load_from_settings_file(m_moduleKey) : + PowerToysSettings::PowerToyValues::from_json_string(config, m_moduleKey); + + for (auto const& setting : m_configBools) + { + if (const auto val = values.get_bool_value(setting.name)) + { + *setting.value = *val; + } + } + + if (auto val = values.get_string_value(NonLocalizable::ZoneColorID)) + { + m_settings.zoneColor = std::move(*val); + } + + if (auto val = values.get_string_value(NonLocalizable::ZoneBorderColorID)) + { + m_settings.zoneBorderColor = std::move(*val); + } + + if (auto val = values.get_string_value(NonLocalizable::ZoneHighlightColorID)) + { + m_settings.zoneHighlightColor = std::move(*val); + } + + if (const auto val = values.get_json(NonLocalizable::EditorHotkeyID)) + { + m_settings.editorHotkey = PowerToysSettings::HotkeyObject::from_json(*val); + } + + if (auto val = values.get_string_value(NonLocalizable::ExcludedAppsID)) + { + m_settings.excludedApps = std::move(*val); + m_settings.excludedAppsArray.clear(); + auto excludedUppercase = m_settings.excludedApps; + CharUpperBuffW(excludedUppercase.data(), (DWORD)excludedUppercase.length()); + std::wstring_view view(excludedUppercase); + while (view.starts_with('\n') || view.starts_with('\r')) + { + view.remove_prefix(1); + } + while (!view.empty()) + { + auto pos = (std::min)(view.find_first_of(L"\r\n"), view.length()); + m_settings.excludedAppsArray.emplace_back(view.substr(0, pos)); + view.remove_prefix(pos); + while (view.starts_with('\n') || view.starts_with('\r')) + { + view.remove_prefix(1); + } + } + } + + if (auto val = values.get_int_value(NonLocalizable::ZoneHighlightOpacityID)) + { + m_settings.zoneHighlightOpacity = *val; + } + + if (auto val = values.get_int_value(NonLocalizable::OverlappingZonesAlgorithmID)) + { + // Avoid undefined behavior + if (*val >= 0 || *val < (int)Settings::OverlappingZonesAlgorithm::EnumElements) + { + m_settings.overlappingZonesAlgorithm = (Settings::OverlappingZonesAlgorithm)*val; + } + } + } + catch (...) + { + // Failure to load settings does not break FancyZones functionality. Display error message and continue with default settings. + MessageBox(NULL, + GET_RESOURCE_STRING(IDS_FANCYZONES_SETTINGS_LOAD_ERROR).c_str(), + GET_RESOURCE_STRING(IDS_POWERTOYS_FANCYZONES).c_str(), + MB_OK); + } +} + +void FancyZonesSettings::SaveSettings() noexcept +{ + try + { + PowerToysSettings::PowerToyValues values(m_moduleName, m_moduleKey); + + for (auto const& setting : m_configBools) + { + values.add_property(setting.name, *setting.value); + } + + values.add_property(NonLocalizable::ZoneColorID, m_settings.zoneColor); + values.add_property(NonLocalizable::ZoneBorderColorID, m_settings.zoneBorderColor); + values.add_property(NonLocalizable::ZoneHighlightColorID, m_settings.zoneHighlightColor); + values.add_property(NonLocalizable::ZoneHighlightOpacityID, m_settings.zoneHighlightOpacity); + values.add_property(NonLocalizable::OverlappingZonesAlgorithmID, (int)m_settings.overlappingZonesAlgorithm); + values.add_property(NonLocalizable::EditorHotkeyID, m_settings.editorHotkey.get_json()); + values.add_property(NonLocalizable::ExcludedAppsID, m_settings.excludedApps); + + values.save_to_settings_file(); + } + catch (...) + { + // Failure to save settings does not break FancyZones functionality. Display error message and continue with currently cached settings. + std::wstring errorMessage = GET_RESOURCE_STRING(IDS_FANCYZONES_SETTINGS_LOAD_ERROR) + L" " + NonLocalizable::PowerToysIssuesURL; + MessageBox(NULL, + errorMessage.c_str(), + GET_RESOURCE_STRING(IDS_POWERTOYS_FANCYZONES).c_str(), + MB_OK); + } +} + +winrt::com_ptr MakeFancyZonesSettings(HINSTANCE hinstance, PCWSTR name, PCWSTR key) noexcept +{ + return winrt::make_self(hinstance, name, key); +} diff --git a/src/modules/fancyzones/lib/Settings.h b/src/modules/fancyzones/FancyZonesLib/Settings.h similarity index 97% rename from src/modules/fancyzones/lib/Settings.h rename to src/modules/fancyzones/FancyZonesLib/Settings.h index da0026f137..12a9468dba 100644 --- a/src/modules/fancyzones/lib/Settings.h +++ b/src/modules/fancyzones/FancyZonesLib/Settings.h @@ -57,8 +57,8 @@ interface __declspec(uuid("{BA4E77C4-6F44-4C5D-93D3-CBDE880495C2}")) IFancyZones IFACEMETHOD_(void, SetCallback)(interface IFancyZonesCallback* callback) = 0; IFACEMETHOD_(void, ResetCallback)() = 0; IFACEMETHOD_(bool, GetConfig)(_Out_ PWSTR buffer, _Out_ int *buffer_size) = 0; - IFACEMETHOD_(void, SetConfig)(PCWSTR serializedPowerToysSettingsJson) = 0; - IFACEMETHOD_(void, CallCustomAction)(PCWSTR action) = 0; + IFACEMETHOD_(void, SetConfig)(PCWSTR serializedPowerToysSettings) = 0; + IFACEMETHOD_(void, ReloadSettings)() = 0; IFACEMETHOD_(const Settings*, GetSettings)() const = 0; }; diff --git a/src/modules/fancyzones/lib/VirtualDesktopUtils.cpp b/src/modules/fancyzones/FancyZonesLib/VirtualDesktopUtils.cpp similarity index 100% rename from src/modules/fancyzones/lib/VirtualDesktopUtils.cpp rename to src/modules/fancyzones/FancyZonesLib/VirtualDesktopUtils.cpp diff --git a/src/modules/fancyzones/lib/VirtualDesktopUtils.h b/src/modules/fancyzones/FancyZonesLib/VirtualDesktopUtils.h similarity index 100% rename from src/modules/fancyzones/lib/VirtualDesktopUtils.h rename to src/modules/fancyzones/FancyZonesLib/VirtualDesktopUtils.h diff --git a/src/modules/fancyzones/lib/WindowMoveHandler.cpp b/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.cpp similarity index 97% rename from src/modules/fancyzones/lib/WindowMoveHandler.cpp rename to src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.cpp index 55ef38ec36..1d05f306e5 100644 --- a/src/modules/fancyzones/lib/WindowMoveHandler.cpp +++ b/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.cpp @@ -1,361 +1,361 @@ -#include "pch.h" -#include "WindowMoveHandler.h" - -#include -#include -#include -#include -#include - -#include "FancyZonesData.h" -#include "Settings.h" -#include "ZoneWindow.h" -#include "util.h" - -// Non-Localizable strings -namespace NonLocalizable -{ - const wchar_t FancyZonesRunAsAdminInfoPage[] = L"https://aka.ms/powertoysDetectedElevatedHelp"; - const wchar_t ToastNotificationButtonUrl[] = L"powertoys://cant_drag_elevated_disable/"; -} - -namespace WindowMoveHandlerUtils -{ - bool IsCursorTypeIndicatingSizeEvent() - { - CURSORINFO cursorInfo = { 0 }; - cursorInfo.cbSize = sizeof(cursorInfo); - - if (::GetCursorInfo(&cursorInfo)) - { - if (::LoadCursor(NULL, IDC_SIZENS) == cursorInfo.hCursor) - { - return true; - } - if (::LoadCursor(NULL, IDC_SIZEWE) == cursorInfo.hCursor) - { - return true; - } - if (::LoadCursor(NULL, IDC_SIZENESW) == cursorInfo.hCursor) - { - return true; - } - if (::LoadCursor(NULL, IDC_SIZENWSE) == cursorInfo.hCursor) - { - return true; - } - } - return false; - } -} - -WindowMoveHandler::WindowMoveHandler(const winrt::com_ptr& settings, const std::function& keyUpdateCallback) : - m_settings(settings), - m_mouseState(false), - m_mouseHook(std::bind(&WindowMoveHandler::OnMouseDown, this)), - m_shiftKeyState(keyUpdateCallback), - m_ctrlKeyState(keyUpdateCallback), - m_keyUpdateCallback(keyUpdateCallback) -{ -} - -void WindowMoveHandler::MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen, const std::unordered_map>& zoneWindowMap) noexcept -{ - if (!FancyZonesUtils::IsCandidateForZoning(window, m_settings->GetSettings()->excludedAppsArray) || WindowMoveHandlerUtils::IsCursorTypeIndicatingSizeEvent()) - { - return; - } - - m_moveSizeWindowInfo.hasNoVisibleOwner = FancyZonesUtils::HasNoVisibleOwner(window); - m_moveSizeWindowInfo.isStandardWindow = FancyZonesUtils::IsStandardWindow(window); - m_inMoveSize = true; - - auto iter = zoneWindowMap.find(monitor); - if (iter == end(zoneWindowMap)) - { - return; - } - - m_windowMoveSize = window; - - if (m_settings->GetSettings()->mouseSwitch) - { - m_mouseHook.enable(); - } - - m_shiftKeyState.enable(); - m_ctrlKeyState.enable(); - - // This updates m_dragEnabled depending on if the shift key is being held down - UpdateDragState(); - - // Notifies user if unable to drag elevated window - WarnIfElevationIsRequired(window); - - if (m_dragEnabled) - { - m_zoneWindowMoveSize = iter->second; - SetWindowTransparency(m_windowMoveSize); - m_zoneWindowMoveSize->MoveSizeEnter(m_windowMoveSize); - if (m_settings->GetSettings()->showZonesOnAllMonitors) - { - for (auto [keyMonitor, zoneWindow] : zoneWindowMap) - { - // Skip calling ShowZoneWindow for iter->second (m_zoneWindowMoveSize) since it - // was already called in MoveSizeEnter - const bool moveSizeEnterCalled = zoneWindow == m_zoneWindowMoveSize; - if (zoneWindow && !moveSizeEnterCalled) - { - zoneWindow->ShowZoneWindow(); - } - } - } - } - else if (m_zoneWindowMoveSize) - { - ResetWindowTransparency(); - m_zoneWindowMoveSize = nullptr; - for (auto [keyMonitor, zoneWindow] : zoneWindowMap) - { - if (zoneWindow) - { - zoneWindow->HideZoneWindow(); - } - } - } -} - -void WindowMoveHandler::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, const std::unordered_map>& zoneWindowMap) noexcept -{ - if (!m_inMoveSize) - { - return; - } - - // This updates m_dragEnabled depending on if the shift key is being held down. - UpdateDragState(); - - if (m_zoneWindowMoveSize) - { - // Update the ZoneWindow already handling move/size - if (!m_dragEnabled) - { - // Drag got disabled, tell it to cancel and hide all windows - m_zoneWindowMoveSize = nullptr; - ResetWindowTransparency(); - - for (auto [keyMonitor, zoneWindow] : zoneWindowMap) - { - if (zoneWindow) - { - zoneWindow->HideZoneWindow(); - } - } - } - else - { - auto iter = zoneWindowMap.find(monitor); - if (iter != zoneWindowMap.end()) - { - if (iter->second != m_zoneWindowMoveSize) - { - // The drag has moved to a different monitor. - m_zoneWindowMoveSize->ClearSelectedZones(); - if (!m_settings->GetSettings()->showZonesOnAllMonitors) - { - m_zoneWindowMoveSize->HideZoneWindow(); - } - - m_zoneWindowMoveSize = iter->second; - m_zoneWindowMoveSize->MoveSizeEnter(m_windowMoveSize); - } - - for (auto [keyMonitor, zoneWindow] : zoneWindowMap) - { - zoneWindow->MoveSizeUpdate(ptScreen, m_dragEnabled, m_ctrlKeyState.state()); - } - } - } - } - else if (m_dragEnabled) - { - // We'll get here if the user presses/releases shift while dragging. - // Restart the drag on the ZoneWindow that m_windowMoveSize is on - MoveSizeStart(m_windowMoveSize, monitor, ptScreen, zoneWindowMap); - - // m_dragEnabled could get set to false if we're moving an elevated window. - // In that case do not proceed. - if (m_dragEnabled) - { - MoveSizeUpdate(monitor, ptScreen, zoneWindowMap); - } - } -} - -void WindowMoveHandler::MoveSizeEnd(HWND window, POINT const& ptScreen, const std::unordered_map>& zoneWindowMap) noexcept -{ - if (window != m_windowMoveSize) - { - return; - } - - m_mouseHook.disable(); - m_shiftKeyState.disable(); - m_ctrlKeyState.disable(); - - if (m_zoneWindowMoveSize) - { - auto zoneWindow = std::move(m_zoneWindowMoveSize); - ResetWindowTransparency(); - - bool hasNoVisibleOwner = FancyZonesUtils::HasNoVisibleOwner(window); - bool isStandardWindow = FancyZonesUtils::IsStandardWindow(window); - - if ((isStandardWindow == false && hasNoVisibleOwner == true && - m_moveSizeWindowInfo.isStandardWindow == true && m_moveSizeWindowInfo.hasNoVisibleOwner == true) || - FancyZonesUtils::IsWindowMaximized(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 - { - zoneWindow->MoveSizeEnd(m_windowMoveSize, ptScreen); - } - } - else - { - if (m_settings->GetSettings()->restoreSize) - { - if (WindowMoveHandlerUtils::IsCursorTypeIndicatingSizeEvent()) - { - ::RemoveProp(window, ZonedWindowProperties::PropertyRestoreSizeID); - } - else if (!FancyZonesUtils::IsWindowMaximized(window)) - { - FancyZonesUtils::RestoreWindowSize(window); - } - } - - auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL); - if (monitor) - { - auto zoneWindow = zoneWindowMap.find(monitor); - if (zoneWindow != zoneWindowMap.end()) - { - const auto zoneWindowPtr = zoneWindow->second; - const auto activeZoneSet = zoneWindowPtr->ActiveZoneSet(); - if (activeZoneSet) - { - wil::unique_cotaskmem_string guidString; - if (SUCCEEDED_LOG(StringFromCLSID(activeZoneSet->Id(), &guidString))) - { - FancyZonesDataInstance().RemoveAppLastZone(window, zoneWindowPtr->UniqueId(), guidString.get()); - } - } - } - } - ::RemoveProp(window, ZonedWindowProperties::PropertyMultipleZoneID); - } - - m_inMoveSize = false; - m_dragEnabled = false; - m_mouseState = false; - m_windowMoveSize = nullptr; - - // Also, hide all windows (regardless of settings) - for (auto [keyMonitor, zoneWindow] : zoneWindowMap) - { - if (zoneWindow) - { - zoneWindow->HideZoneWindow(); - } - } -} - -void WindowMoveHandler::MoveWindowIntoZoneByIndexSet(HWND window, const std::vector& indexSet, winrt::com_ptr zoneWindow) noexcept -{ - if (window != m_windowMoveSize) - { - zoneWindow->MoveWindowIntoZoneByIndexSet(window, indexSet); - } -} - -bool WindowMoveHandler::MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr zoneWindow) noexcept -{ - return zoneWindow && zoneWindow->MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, cycle); -} - -bool WindowMoveHandler::MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr zoneWindow) noexcept -{ - return zoneWindow && zoneWindow->MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle); -} - -bool WindowMoveHandler::ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode, winrt::com_ptr zoneWindow) noexcept -{ - return zoneWindow && zoneWindow->ExtendWindowByDirectionAndPosition(window, vkCode); -} - -void WindowMoveHandler::WarnIfElevationIsRequired(HWND window) noexcept -{ - using namespace notifications; - using namespace NonLocalizable; - using namespace FancyZonesUtils; - - static bool warning_shown = false; - if (!is_process_elevated() && IsProcessOfWindowElevated(window)) - { - m_dragEnabled = false; - if (!warning_shown && !is_toast_disabled(CantDragElevatedDontShowAgainRegistryPath, CantDragElevatedDisableIntervalInDays)) - { - std::vector actions = { - link_button{ GET_RESOURCE_STRING(IDS_CANT_DRAG_ELEVATED_LEARN_MORE), FancyZonesRunAsAdminInfoPage }, - link_button{ GET_RESOURCE_STRING(IDS_CANT_DRAG_ELEVATED_DIALOG_DONT_SHOW_AGAIN), ToastNotificationButtonUrl } - }; - show_toast_with_activations(GET_RESOURCE_STRING(IDS_CANT_DRAG_ELEVATED), - GET_RESOURCE_STRING(IDS_FANCYZONES), - {}, - std::move(actions)); - warning_shown = true; - } - } -} - -void WindowMoveHandler::UpdateDragState() noexcept -{ - if (m_settings->GetSettings()->shiftDrag) - { - m_dragEnabled = (m_shiftKeyState.state() ^ m_mouseState); - } - else - { - m_dragEnabled = !(m_shiftKeyState.state() ^ m_mouseState); - } -} - -void WindowMoveHandler::SetWindowTransparency(HWND window) noexcept -{ - if (m_settings->GetSettings()->makeDraggedWindowTransparent) - { - m_windowTransparencyProperties.draggedWindowExstyle = GetWindowLong(window, GWL_EXSTYLE); - - m_windowTransparencyProperties.draggedWindow = window; - SetWindowLong(window, - GWL_EXSTYLE, - m_windowTransparencyProperties.draggedWindowExstyle | WS_EX_LAYERED); - - GetLayeredWindowAttributes(window, &m_windowTransparencyProperties.draggedWindowCrKey, &m_windowTransparencyProperties.draggedWindowInitialAlpha, &m_windowTransparencyProperties.draggedWindowDwFlags); - - SetLayeredWindowAttributes(window, 0, (255 * 50) / 100, LWA_ALPHA); - } -} - -void WindowMoveHandler::ResetWindowTransparency() noexcept -{ - if (m_settings->GetSettings()->makeDraggedWindowTransparent && m_windowTransparencyProperties.draggedWindow != nullptr) - { - SetLayeredWindowAttributes(m_windowTransparencyProperties.draggedWindow, m_windowTransparencyProperties.draggedWindowCrKey, m_windowTransparencyProperties.draggedWindowInitialAlpha, m_windowTransparencyProperties.draggedWindowDwFlags); - SetWindowLong(m_windowTransparencyProperties.draggedWindow, GWL_EXSTYLE, m_windowTransparencyProperties.draggedWindowExstyle); - m_windowTransparencyProperties.draggedWindow = nullptr; - } -} +#include "pch.h" +#include "WindowMoveHandler.h" + +#include +#include +#include +#include +#include + +#include "FancyZonesData.h" +#include "Settings.h" +#include "ZoneWindow.h" +#include "util.h" + +// Non-Localizable strings +namespace NonLocalizable +{ + const wchar_t FancyZonesRunAsAdminInfoPage[] = L"https://aka.ms/powertoysDetectedElevatedHelp"; + const wchar_t ToastNotificationButtonUrl[] = L"powertoys://cant_drag_elevated_disable/"; +} + +namespace WindowMoveHandlerUtils +{ + bool IsCursorTypeIndicatingSizeEvent() + { + CURSORINFO cursorInfo = { 0 }; + cursorInfo.cbSize = sizeof(cursorInfo); + + if (::GetCursorInfo(&cursorInfo)) + { + if (::LoadCursor(NULL, IDC_SIZENS) == cursorInfo.hCursor) + { + return true; + } + if (::LoadCursor(NULL, IDC_SIZEWE) == cursorInfo.hCursor) + { + return true; + } + if (::LoadCursor(NULL, IDC_SIZENESW) == cursorInfo.hCursor) + { + return true; + } + if (::LoadCursor(NULL, IDC_SIZENWSE) == cursorInfo.hCursor) + { + return true; + } + } + return false; + } +} + +WindowMoveHandler::WindowMoveHandler(const winrt::com_ptr& settings, const std::function& keyUpdateCallback) : + m_settings(settings), + m_mouseState(false), + m_mouseHook(std::bind(&WindowMoveHandler::OnMouseDown, this)), + m_shiftKeyState(keyUpdateCallback), + m_ctrlKeyState(keyUpdateCallback), + m_keyUpdateCallback(keyUpdateCallback) +{ +} + +void WindowMoveHandler::MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen, const std::unordered_map>& zoneWindowMap) noexcept +{ + if (!FancyZonesUtils::IsCandidateForZoning(window, m_settings->GetSettings()->excludedAppsArray) || WindowMoveHandlerUtils::IsCursorTypeIndicatingSizeEvent()) + { + return; + } + + m_moveSizeWindowInfo.hasNoVisibleOwner = FancyZonesUtils::HasNoVisibleOwner(window); + m_moveSizeWindowInfo.isStandardWindow = FancyZonesUtils::IsStandardWindow(window); + m_inMoveSize = true; + + auto iter = zoneWindowMap.find(monitor); + if (iter == end(zoneWindowMap)) + { + return; + } + + m_windowMoveSize = window; + + if (m_settings->GetSettings()->mouseSwitch) + { + m_mouseHook.enable(); + } + + m_shiftKeyState.enable(); + m_ctrlKeyState.enable(); + + // This updates m_dragEnabled depending on if the shift key is being held down + UpdateDragState(); + + // Notifies user if unable to drag elevated window + WarnIfElevationIsRequired(window); + + if (m_dragEnabled) + { + m_zoneWindowMoveSize = iter->second; + SetWindowTransparency(m_windowMoveSize); + m_zoneWindowMoveSize->MoveSizeEnter(m_windowMoveSize); + if (m_settings->GetSettings()->showZonesOnAllMonitors) + { + for (auto [keyMonitor, zoneWindow] : zoneWindowMap) + { + // Skip calling ShowZoneWindow for iter->second (m_zoneWindowMoveSize) since it + // was already called in MoveSizeEnter + const bool moveSizeEnterCalled = zoneWindow == m_zoneWindowMoveSize; + if (zoneWindow && !moveSizeEnterCalled) + { + zoneWindow->ShowZoneWindow(); + } + } + } + } + else if (m_zoneWindowMoveSize) + { + ResetWindowTransparency(); + m_zoneWindowMoveSize = nullptr; + for (auto [keyMonitor, zoneWindow] : zoneWindowMap) + { + if (zoneWindow) + { + zoneWindow->HideZoneWindow(); + } + } + } +} + +void WindowMoveHandler::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, const std::unordered_map>& zoneWindowMap) noexcept +{ + if (!m_inMoveSize) + { + return; + } + + // This updates m_dragEnabled depending on if the shift key is being held down. + UpdateDragState(); + + if (m_zoneWindowMoveSize) + { + // Update the ZoneWindow already handling move/size + if (!m_dragEnabled) + { + // Drag got disabled, tell it to cancel and hide all windows + m_zoneWindowMoveSize = nullptr; + ResetWindowTransparency(); + + for (auto [keyMonitor, zoneWindow] : zoneWindowMap) + { + if (zoneWindow) + { + zoneWindow->HideZoneWindow(); + } + } + } + else + { + auto iter = zoneWindowMap.find(monitor); + if (iter != zoneWindowMap.end()) + { + if (iter->second != m_zoneWindowMoveSize) + { + // The drag has moved to a different monitor. + m_zoneWindowMoveSize->ClearSelectedZones(); + if (!m_settings->GetSettings()->showZonesOnAllMonitors) + { + m_zoneWindowMoveSize->HideZoneWindow(); + } + + m_zoneWindowMoveSize = iter->second; + m_zoneWindowMoveSize->MoveSizeEnter(m_windowMoveSize); + } + + for (auto [keyMonitor, zoneWindow] : zoneWindowMap) + { + zoneWindow->MoveSizeUpdate(ptScreen, m_dragEnabled, m_ctrlKeyState.state()); + } + } + } + } + else if (m_dragEnabled) + { + // We'll get here if the user presses/releases shift while dragging. + // Restart the drag on the ZoneWindow that m_windowMoveSize is on + MoveSizeStart(m_windowMoveSize, monitor, ptScreen, zoneWindowMap); + + // m_dragEnabled could get set to false if we're moving an elevated window. + // In that case do not proceed. + if (m_dragEnabled) + { + MoveSizeUpdate(monitor, ptScreen, zoneWindowMap); + } + } +} + +void WindowMoveHandler::MoveSizeEnd(HWND window, POINT const& ptScreen, const std::unordered_map>& zoneWindowMap) noexcept +{ + if (window != m_windowMoveSize) + { + return; + } + + m_mouseHook.disable(); + m_shiftKeyState.disable(); + m_ctrlKeyState.disable(); + + if (m_zoneWindowMoveSize) + { + auto zoneWindow = std::move(m_zoneWindowMoveSize); + ResetWindowTransparency(); + + bool hasNoVisibleOwner = FancyZonesUtils::HasNoVisibleOwner(window); + bool isStandardWindow = FancyZonesUtils::IsStandardWindow(window); + + if ((isStandardWindow == false && hasNoVisibleOwner == true && + m_moveSizeWindowInfo.isStandardWindow == true && m_moveSizeWindowInfo.hasNoVisibleOwner == true) || + FancyZonesUtils::IsWindowMaximized(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 + { + zoneWindow->MoveSizeEnd(m_windowMoveSize, ptScreen); + } + } + else + { + if (m_settings->GetSettings()->restoreSize) + { + if (WindowMoveHandlerUtils::IsCursorTypeIndicatingSizeEvent()) + { + ::RemoveProp(window, ZonedWindowProperties::PropertyRestoreSizeID); + } + else if (!FancyZonesUtils::IsWindowMaximized(window)) + { + FancyZonesUtils::RestoreWindowSize(window); + } + } + + auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL); + if (monitor) + { + auto zoneWindow = zoneWindowMap.find(monitor); + if (zoneWindow != zoneWindowMap.end()) + { + const auto zoneWindowPtr = zoneWindow->second; + const auto activeZoneSet = zoneWindowPtr->ActiveZoneSet(); + if (activeZoneSet) + { + wil::unique_cotaskmem_string guidString; + if (SUCCEEDED_LOG(StringFromCLSID(activeZoneSet->Id(), &guidString))) + { + FancyZonesDataInstance().RemoveAppLastZone(window, zoneWindowPtr->UniqueId(), guidString.get()); + } + } + } + } + ::RemoveProp(window, ZonedWindowProperties::PropertyMultipleZoneID); + } + + m_inMoveSize = false; + m_dragEnabled = false; + m_mouseState = false; + m_windowMoveSize = nullptr; + + // Also, hide all windows (regardless of settings) + for (auto [keyMonitor, zoneWindow] : zoneWindowMap) + { + if (zoneWindow) + { + zoneWindow->HideZoneWindow(); + } + } +} + +void WindowMoveHandler::MoveWindowIntoZoneByIndexSet(HWND window, const std::vector& indexSet, winrt::com_ptr zoneWindow) noexcept +{ + if (window != m_windowMoveSize) + { + zoneWindow->MoveWindowIntoZoneByIndexSet(window, indexSet); + } +} + +bool WindowMoveHandler::MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr zoneWindow) noexcept +{ + return zoneWindow && zoneWindow->MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, cycle); +} + +bool WindowMoveHandler::MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr zoneWindow) noexcept +{ + return zoneWindow && zoneWindow->MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle); +} + +bool WindowMoveHandler::ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode, winrt::com_ptr zoneWindow) noexcept +{ + return zoneWindow && zoneWindow->ExtendWindowByDirectionAndPosition(window, vkCode); +} + +void WindowMoveHandler::WarnIfElevationIsRequired(HWND window) noexcept +{ + using namespace notifications; + using namespace NonLocalizable; + using namespace FancyZonesUtils; + + static bool warning_shown = false; + if (!is_process_elevated() && IsProcessOfWindowElevated(window)) + { + m_dragEnabled = false; + if (!warning_shown && !is_toast_disabled(CantDragElevatedDontShowAgainRegistryPath, CantDragElevatedDisableIntervalInDays)) + { + std::vector actions = { + link_button{ GET_RESOURCE_STRING(IDS_CANT_DRAG_ELEVATED_LEARN_MORE), FancyZonesRunAsAdminInfoPage }, + link_button{ GET_RESOURCE_STRING(IDS_CANT_DRAG_ELEVATED_DIALOG_DONT_SHOW_AGAIN), ToastNotificationButtonUrl } + }; + show_toast_with_activations(GET_RESOURCE_STRING(IDS_CANT_DRAG_ELEVATED), + GET_RESOURCE_STRING(IDS_FANCYZONES), + {}, + std::move(actions)); + warning_shown = true; + } + } +} + +void WindowMoveHandler::UpdateDragState() noexcept +{ + if (m_settings->GetSettings()->shiftDrag) + { + m_dragEnabled = (m_shiftKeyState.state() ^ m_mouseState); + } + else + { + m_dragEnabled = !(m_shiftKeyState.state() ^ m_mouseState); + } +} + +void WindowMoveHandler::SetWindowTransparency(HWND window) noexcept +{ + if (m_settings->GetSettings()->makeDraggedWindowTransparent) + { + m_windowTransparencyProperties.draggedWindowExstyle = GetWindowLong(window, GWL_EXSTYLE); + + m_windowTransparencyProperties.draggedWindow = window; + SetWindowLong(window, + GWL_EXSTYLE, + m_windowTransparencyProperties.draggedWindowExstyle | WS_EX_LAYERED); + + GetLayeredWindowAttributes(window, &m_windowTransparencyProperties.draggedWindowCrKey, &m_windowTransparencyProperties.draggedWindowInitialAlpha, &m_windowTransparencyProperties.draggedWindowDwFlags); + + SetLayeredWindowAttributes(window, 0, (255 * 50) / 100, LWA_ALPHA); + } +} + +void WindowMoveHandler::ResetWindowTransparency() noexcept +{ + if (m_settings->GetSettings()->makeDraggedWindowTransparent && m_windowTransparencyProperties.draggedWindow != nullptr) + { + SetLayeredWindowAttributes(m_windowTransparencyProperties.draggedWindow, m_windowTransparencyProperties.draggedWindowCrKey, m_windowTransparencyProperties.draggedWindowInitialAlpha, m_windowTransparencyProperties.draggedWindowDwFlags); + SetWindowLong(m_windowTransparencyProperties.draggedWindow, GWL_EXSTYLE, m_windowTransparencyProperties.draggedWindowExstyle); + m_windowTransparencyProperties.draggedWindow = nullptr; + } +} diff --git a/src/modules/fancyzones/lib/WindowMoveHandler.h b/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.h similarity index 97% rename from src/modules/fancyzones/lib/WindowMoveHandler.h rename to src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.h index ea5ab62013..c81ed9fd8c 100644 --- a/src/modules/fancyzones/lib/WindowMoveHandler.h +++ b/src/modules/fancyzones/FancyZonesLib/WindowMoveHandler.h @@ -1,81 +1,81 @@ -#pragma once - -#include "KeyState.h" -#include "SecondaryMouseButtonsHook.h" - -#include - -interface IFancyZonesSettings; -interface IZoneWindow; - -class WindowMoveHandler -{ -public: - WindowMoveHandler(const winrt::com_ptr& settings, const std::function& keyUpdateCallback); - - void MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen, const std::unordered_map>& zoneWindowMap) noexcept; - void MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, const std::unordered_map>& zoneWindowMap) noexcept; - void MoveSizeEnd(HWND window, POINT const& ptScreen, const std::unordered_map>& zoneWindowMap) noexcept; - - void MoveWindowIntoZoneByIndexSet(HWND window, const std::vector& indexSet, winrt::com_ptr zoneWindow) noexcept; - bool MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr zoneWindow) noexcept; - bool MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr zoneWindow) noexcept; - bool ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode, winrt::com_ptr zoneWindow) noexcept; - - inline void OnMouseDown() noexcept - { - m_mouseState = !m_mouseState; - m_keyUpdateCallback(); - } - - inline bool IsDragEnabled() const noexcept - { - return m_dragEnabled; - } - - inline bool InMoveSize() const noexcept - { - return m_inMoveSize; - } - -private: - struct WindowTransparencyProperties - { - HWND draggedWindow = nullptr; - long draggedWindowExstyle = 0; - COLORREF draggedWindowCrKey = RGB(0, 0, 0); - DWORD draggedWindowDwFlags = 0; - BYTE draggedWindowInitialAlpha = 0; - }; - - // MoveSize related window properties - struct MoveSizeWindowInfo - { - // True if from the styles the window looks like a standard window - bool isStandardWindow = false; - // True if the window is a top-level window that does not have a visible owner - bool hasNoVisibleOwner = false; - }; - - void WarnIfElevationIsRequired(HWND window) noexcept; - void UpdateDragState() noexcept; - - void SetWindowTransparency(HWND window) noexcept; - void ResetWindowTransparency() noexcept; - - winrt::com_ptr m_settings{}; - - HWND m_windowMoveSize{}; // The window that is being moved/sized - bool m_inMoveSize{}; // Whether or not a move/size operation is currently active - MoveSizeWindowInfo m_moveSizeWindowInfo; // MoveSizeWindowInfo of the window at the moment when dragging started - winrt::com_ptr m_zoneWindowMoveSize; // "Active" ZoneWindow, where the move/size is happening. Will update as drag moves between monitors. - bool m_dragEnabled{}; // True if we should be showing zone hints while dragging - - WindowTransparencyProperties m_windowTransparencyProperties; - - std::atomic m_mouseState; - SecondaryMouseButtonsHook m_mouseHook; - KeyState m_shiftKeyState; - KeyState m_ctrlKeyState; - std::function m_keyUpdateCallback; -}; +#pragma once + +#include "KeyState.h" +#include "SecondaryMouseButtonsHook.h" + +#include + +interface IFancyZonesSettings; +interface IZoneWindow; + +class WindowMoveHandler +{ +public: + WindowMoveHandler(const winrt::com_ptr& settings, const std::function& keyUpdateCallback); + + void MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen, const std::unordered_map>& zoneWindowMap) noexcept; + void MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, const std::unordered_map>& zoneWindowMap) noexcept; + void MoveSizeEnd(HWND window, POINT const& ptScreen, const std::unordered_map>& zoneWindowMap) noexcept; + + void MoveWindowIntoZoneByIndexSet(HWND window, const std::vector& indexSet, winrt::com_ptr zoneWindow) noexcept; + bool MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr zoneWindow) noexcept; + bool MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle, winrt::com_ptr zoneWindow) noexcept; + bool ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode, winrt::com_ptr zoneWindow) noexcept; + + inline void OnMouseDown() noexcept + { + m_mouseState = !m_mouseState; + m_keyUpdateCallback(); + } + + inline bool IsDragEnabled() const noexcept + { + return m_dragEnabled; + } + + inline bool InMoveSize() const noexcept + { + return m_inMoveSize; + } + +private: + struct WindowTransparencyProperties + { + HWND draggedWindow = nullptr; + long draggedWindowExstyle = 0; + COLORREF draggedWindowCrKey = RGB(0, 0, 0); + DWORD draggedWindowDwFlags = 0; + BYTE draggedWindowInitialAlpha = 0; + }; + + // MoveSize related window properties + struct MoveSizeWindowInfo + { + // True if from the styles the window looks like a standard window + bool isStandardWindow = false; + // True if the window is a top-level window that does not have a visible owner + bool hasNoVisibleOwner = false; + }; + + void WarnIfElevationIsRequired(HWND window) noexcept; + void UpdateDragState() noexcept; + + void SetWindowTransparency(HWND window) noexcept; + void ResetWindowTransparency() noexcept; + + winrt::com_ptr m_settings{}; + + HWND m_windowMoveSize{}; // The window that is being moved/sized + bool m_inMoveSize{}; // Whether or not a move/size operation is currently active + MoveSizeWindowInfo m_moveSizeWindowInfo; // MoveSizeWindowInfo of the window at the moment when dragging started + winrt::com_ptr m_zoneWindowMoveSize; // "Active" ZoneWindow, where the move/size is happening. Will update as drag moves between monitors. + bool m_dragEnabled{}; // True if we should be showing zone hints while dragging + + WindowTransparencyProperties m_windowTransparencyProperties; + + std::atomic m_mouseState; + SecondaryMouseButtonsHook m_mouseHook; + KeyState m_shiftKeyState; + KeyState m_ctrlKeyState; + std::function m_keyUpdateCallback; +}; diff --git a/src/modules/fancyzones/lib/Zone.cpp b/src/modules/fancyzones/FancyZonesLib/Zone.cpp similarity index 100% rename from src/modules/fancyzones/lib/Zone.cpp rename to src/modules/fancyzones/FancyZonesLib/Zone.cpp diff --git a/src/modules/fancyzones/lib/Zone.h b/src/modules/fancyzones/FancyZonesLib/Zone.h similarity index 100% rename from src/modules/fancyzones/lib/Zone.h rename to src/modules/fancyzones/FancyZonesLib/Zone.h diff --git a/src/modules/fancyzones/lib/ZoneSet.cpp b/src/modules/fancyzones/FancyZonesLib/ZoneSet.cpp similarity index 100% rename from src/modules/fancyzones/lib/ZoneSet.cpp rename to src/modules/fancyzones/FancyZonesLib/ZoneSet.cpp diff --git a/src/modules/fancyzones/lib/ZoneSet.h b/src/modules/fancyzones/FancyZonesLib/ZoneSet.h similarity index 100% rename from src/modules/fancyzones/lib/ZoneSet.h rename to src/modules/fancyzones/FancyZonesLib/ZoneSet.h diff --git a/src/modules/fancyzones/lib/ZoneWindow.cpp b/src/modules/fancyzones/FancyZonesLib/ZoneWindow.cpp similarity index 96% rename from src/modules/fancyzones/lib/ZoneWindow.cpp rename to src/modules/fancyzones/FancyZonesLib/ZoneWindow.cpp index 4754430dac..0b515c111e 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.cpp +++ b/src/modules/fancyzones/FancyZonesLib/ZoneWindow.cpp @@ -1,595 +1,595 @@ -#include "pch.h" - -#include - -#include "FancyZonesData.h" -#include "FancyZonesDataTypes.h" -#include "ZoneWindow.h" -#include "ZoneWindowDrawing.h" -#include "trace.h" -#include "util.h" -#include "on_thread_executor.h" -#include "Settings.h" -#include "CallTracer.h" - -#include -#include -#include - -#include - -// Non-Localizable strings -namespace NonLocalizable -{ - const wchar_t ToolWindowClassName[] = L"SuperFancyZones_ZoneWindow"; -} - -using namespace FancyZonesUtils; - -struct ZoneWindow; - -namespace -{ - // The reason for using this class is the need to call ShowWindow(window, SW_SHOWNORMAL); on each - // newly created window for it to be displayed properly. The call sometimes has side effects when - // a fullscreen app is running, and happens when the resolution change event is triggered - // (e.g. when running some games). - // This class will serve as a pool of windows for which this call was already done. - class WindowPool - { - std::vector m_pool; - std::mutex m_mutex; - - HWND ExtractWindow() - { - _TRACER_; - std::unique_lock lock(m_mutex); - - if (m_pool.empty()) - { - return NULL; - } - - HWND window = m_pool.back(); - m_pool.pop_back(); - return window; - } - - public: - - HWND NewZoneWindow(Rect position, HINSTANCE hinstance, ZoneWindow* owner) - { - HWND windowFromPool = ExtractWindow(); - if (windowFromPool == NULL) - { - HWND window = CreateWindowExW(WS_EX_TOOLWINDOW, NonLocalizable::ToolWindowClassName, L"", WS_POPUP, position.left(), position.top(), position.width(), position.height(), nullptr, nullptr, hinstance, owner); - Logger::info("Creating new zone window, hWnd = {}", (void*)window); - MakeWindowTransparent(window); - - // According to ShowWindow docs, we must call it with SW_SHOWNORMAL the first time - ShowWindow(window, SW_SHOWNORMAL); - ShowWindow(window, SW_HIDE); - return window; - } - else - { - Logger::info("Reusing zone window from pool, hWnd = {}", (void*)windowFromPool); - SetWindowLongPtrW(windowFromPool, GWLP_USERDATA, reinterpret_cast(owner)); - MoveWindow(windowFromPool, position.left(), position.top(), position.width(), position.height(), TRUE); - return windowFromPool; - } - } - - void FreeZoneWindow(HWND window) - { - _TRACER_; - Logger::info("Freeing zone window, hWnd = {}", (void*)window); - SetWindowLongPtrW(window, GWLP_USERDATA, 0); - ShowWindow(window, SW_HIDE); - - std::unique_lock lock(m_mutex); - m_pool.push_back(window); - } - - ~WindowPool() - { - for (HWND window : m_pool) - { - DestroyWindow(window); - } - } - }; - - WindowPool windowPool; -} - -struct ZoneWindow : public winrt::implements -{ -public: - ZoneWindow(HINSTANCE hinstance); - ~ZoneWindow(); - - bool Init(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, const std::wstring& uniqueId, const std::wstring& parentUniqueId); - - IFACEMETHODIMP MoveSizeEnter(HWND window) noexcept; - IFACEMETHODIMP MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool selectManyZones) noexcept; - IFACEMETHODIMP MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept; - IFACEMETHODIMP_(void) - MoveWindowIntoZoneByIndex(HWND window, size_t index) noexcept; - IFACEMETHODIMP_(void) - MoveWindowIntoZoneByIndexSet(HWND window, const std::vector& indexSet) noexcept; - IFACEMETHODIMP_(bool) - MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle) noexcept; - IFACEMETHODIMP_(bool) - MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle) noexcept; - IFACEMETHODIMP_(bool) - ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode) noexcept; - IFACEMETHODIMP_(std::wstring) - UniqueId() noexcept { return { m_uniqueId }; } - IFACEMETHODIMP_(void) - SaveWindowProcessToZoneIndex(HWND window) noexcept; - IFACEMETHODIMP_(IZoneSet*) - ActiveZoneSet() noexcept { return m_activeZoneSet.get(); } - IFACEMETHODIMP_(void) - ShowZoneWindow() noexcept; - IFACEMETHODIMP_(void) - HideZoneWindow() noexcept; - IFACEMETHODIMP_(void) - UpdateActiveZoneSet() noexcept; - IFACEMETHODIMP_(void) - ClearSelectedZones() noexcept; - IFACEMETHODIMP_(void) - FlashZones() noexcept; - -protected: - static LRESULT CALLBACK s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept; - -private: - void InitializeZoneSets(const std::wstring& parentUniqueId) noexcept; - void CalculateZoneSet() noexcept; - void UpdateActiveZoneSet(_In_opt_ IZoneSet* zoneSet) noexcept; - LRESULT WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept; - std::vector ZonesFromPoint(POINT pt) noexcept; - void SetAsTopmostWindow() noexcept; - - winrt::com_ptr m_host; - HMONITOR m_monitor{}; - std::wstring m_uniqueId; // Parsed deviceId + resolution + virtualDesktopId - HWND m_window{}; // Hidden tool window used to represent current monitor desktop work area. - HWND m_windowMoveSize{}; - winrt::com_ptr m_activeZoneSet; - std::vector> m_zoneSets; - std::vector m_initialHighlightZone; - std::vector m_highlightZone; - WPARAM m_keyLast{}; - size_t m_keyCycle{}; - std::unique_ptr m_zoneWindowDrawing; -}; - -ZoneWindow::ZoneWindow(HINSTANCE hinstance) -{ - WNDCLASSEXW wcex{}; - wcex.cbSize = sizeof(WNDCLASSEX); - wcex.lpfnWndProc = s_WndProc; - wcex.hInstance = hinstance; - wcex.lpszClassName = NonLocalizable::ToolWindowClassName; - wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW); - RegisterClassExW(&wcex); -} - -ZoneWindow::~ZoneWindow() -{ - windowPool.FreeZoneWindow(m_window); -} - -bool ZoneWindow::Init(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, const std::wstring& uniqueId, const std::wstring& parentUniqueId) -{ - m_host.copy_from(host); - - Rect workAreaRect; - m_monitor = monitor; - if (monitor) - { - MONITORINFO mi{}; - mi.cbSize = sizeof(mi); - if (!GetMonitorInfoW(monitor, &mi)) - { - return false; - } - workAreaRect = Rect(mi.rcWork); - } - else - { - workAreaRect = GetAllMonitorsCombinedRect<&MONITORINFO::rcWork>(); - } - - m_uniqueId = uniqueId; - InitializeZoneSets(parentUniqueId); - - m_window = windowPool.NewZoneWindow(workAreaRect, hinstance, this); - - if (!m_window) - { - return false; - } - - m_zoneWindowDrawing = std::make_unique(m_window); - - return true; -} - -IFACEMETHODIMP ZoneWindow::MoveSizeEnter(HWND window) noexcept -{ - m_windowMoveSize = window; - m_highlightZone = {}; - m_initialHighlightZone = {}; - ShowZoneWindow(); - return S_OK; -} - -IFACEMETHODIMP ZoneWindow::MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool selectManyZones) noexcept -{ - bool redraw = false; - POINT ptClient = ptScreen; - MapWindowPoints(nullptr, m_window, &ptClient, 1); - - if (dragEnabled) - { - auto highlightZone = ZonesFromPoint(ptClient); - - if (selectManyZones) - { - if (m_initialHighlightZone.empty()) - { - // first time - m_initialHighlightZone = highlightZone; - } - else - { - highlightZone = m_activeZoneSet->GetCombinedZoneRange(m_initialHighlightZone, highlightZone); - } - } - else - { - m_initialHighlightZone = {}; - } - - redraw = (highlightZone != m_highlightZone); - m_highlightZone = std::move(highlightZone); - } - else if (m_highlightZone.size()) - { - m_highlightZone = {}; - redraw = true; - } - - if (redraw) - { - m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), m_highlightZone, m_host); - } - - return S_OK; -} - -IFACEMETHODIMP ZoneWindow::MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept -{ - if (m_windowMoveSize != window) - { - return E_INVALIDARG; - } - - if (m_activeZoneSet) - { - POINT ptClient = ptScreen; - MapWindowPoints(nullptr, m_window, &ptClient, 1); - m_activeZoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, m_highlightZone); - - if (FancyZonesUtils::HasNoVisibleOwner(window)) - { - SaveWindowProcessToZoneIndex(window); - } - } - Trace::ZoneWindow::MoveSizeEnd(m_activeZoneSet); - - HideZoneWindow(); - m_windowMoveSize = nullptr; - return S_OK; -} - -IFACEMETHODIMP_(void) -ZoneWindow::MoveWindowIntoZoneByIndex(HWND window, size_t index) noexcept -{ - MoveWindowIntoZoneByIndexSet(window, { index }); -} - -IFACEMETHODIMP_(void) -ZoneWindow::MoveWindowIntoZoneByIndexSet(HWND window, const std::vector& indexSet) noexcept -{ - if (m_activeZoneSet) - { - m_activeZoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, indexSet); - } -} - -IFACEMETHODIMP_(bool) -ZoneWindow::MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle) noexcept -{ - if (m_activeZoneSet) - { - if (m_activeZoneSet->MoveWindowIntoZoneByDirectionAndIndex(window, m_window, vkCode, cycle)) - { - if (FancyZonesUtils::HasNoVisibleOwner(window)) - { - SaveWindowProcessToZoneIndex(window); - } - return true; - } - } - return false; -} - -IFACEMETHODIMP_(bool) -ZoneWindow::MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle) noexcept -{ - if (m_activeZoneSet) - { - if (m_activeZoneSet->MoveWindowIntoZoneByDirectionAndPosition(window, m_window, vkCode, cycle)) - { - SaveWindowProcessToZoneIndex(window); - return true; - } - } - return false; -} - -IFACEMETHODIMP_(bool) -ZoneWindow::ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode) noexcept -{ - if (m_activeZoneSet) - { - if (m_activeZoneSet->ExtendWindowByDirectionAndPosition(window, m_window, vkCode)) - { - SaveWindowProcessToZoneIndex(window); - return true; - } - } - return false; -} - -IFACEMETHODIMP_(void) -ZoneWindow::SaveWindowProcessToZoneIndex(HWND window) noexcept -{ - if (m_activeZoneSet) - { - auto zoneIndexSet = m_activeZoneSet->GetZoneIndexSetFromWindow(window); - if (zoneIndexSet.size()) - { - OLECHAR* guidString; - if (StringFromCLSID(m_activeZoneSet->Id(), &guidString) == S_OK) - { - FancyZonesDataInstance().SetAppLastZones(window, m_uniqueId, guidString, zoneIndexSet); - } - - CoTaskMemFree(guidString); - } - } -} - -IFACEMETHODIMP_(void) -ZoneWindow::ShowZoneWindow() noexcept -{ - if (m_window) - { - SetAsTopmostWindow(); - m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), m_highlightZone, m_host); - m_zoneWindowDrawing->Show(); - } -} - -IFACEMETHODIMP_(void) -ZoneWindow::HideZoneWindow() noexcept -{ - if (m_window) - { - m_zoneWindowDrawing->Hide(); - m_keyLast = 0; - m_windowMoveSize = nullptr; - m_highlightZone = {}; - } -} - -IFACEMETHODIMP_(void) -ZoneWindow::UpdateActiveZoneSet() noexcept -{ - CalculateZoneSet(); - if (m_window) - { - m_highlightZone.clear(); - m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), m_highlightZone, m_host); - } -} - -IFACEMETHODIMP_(void) -ZoneWindow::ClearSelectedZones() noexcept -{ - if (m_highlightZone.size()) - { - m_highlightZone.clear(); - m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), m_highlightZone, m_host); - } -} - -IFACEMETHODIMP_(void) -ZoneWindow::FlashZones() noexcept -{ - if (m_window) - { - SetAsTopmostWindow(); - m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), {}, m_host); - m_zoneWindowDrawing->Flash(); - } -} - -#pragma region private - -void ZoneWindow::InitializeZoneSets(const std::wstring& parentUniqueId) noexcept -{ - bool deviceAdded = FancyZonesDataInstance().AddDevice(m_uniqueId); - // If the device has been added, check if it should inherit the parent's layout - if (deviceAdded && !parentUniqueId.empty()) - { - FancyZonesDataInstance().CloneDeviceInfo(parentUniqueId, m_uniqueId); - } - CalculateZoneSet(); -} - -void ZoneWindow::CalculateZoneSet() noexcept -{ - const auto& fancyZonesData = FancyZonesDataInstance(); - const auto deviceInfoData = fancyZonesData.FindDeviceInfo(m_uniqueId); - - if (!deviceInfoData.has_value()) - { - return; - } - - const auto& activeZoneSet = deviceInfoData->activeZoneSet; - - if (activeZoneSet.uuid.empty()) - { - return; - } - - GUID zoneSetId; - if (SUCCEEDED_LOG(CLSIDFromString(activeZoneSet.uuid.c_str(), &zoneSetId))) - { - int sensitivityRadius = deviceInfoData->sensitivityRadius; - - auto zoneSet = MakeZoneSet(ZoneSetConfig( - zoneSetId, - activeZoneSet.type, - m_monitor, - sensitivityRadius, - m_host->GetOverlappingZonesAlgorithm())); - - RECT workArea; - if (m_monitor) - { - MONITORINFO monitorInfo{}; - monitorInfo.cbSize = sizeof(monitorInfo); - if (GetMonitorInfoW(m_monitor, &monitorInfo)) - { - workArea = monitorInfo.rcWork; - } - else - { - return; - } - } - else - { - workArea = GetAllMonitorsCombinedRect<&MONITORINFO::rcWork>(); - } - - bool showSpacing = deviceInfoData->showSpacing; - int spacing = showSpacing ? deviceInfoData->spacing : 0; - int zoneCount = deviceInfoData->zoneCount; - - zoneSet->CalculateZones(workArea, zoneCount, spacing); - UpdateActiveZoneSet(zoneSet.get()); - } -} - -void ZoneWindow::UpdateActiveZoneSet(_In_opt_ IZoneSet* zoneSet) noexcept -{ - m_activeZoneSet.copy_from(zoneSet); - - if (m_activeZoneSet) - { - wil::unique_cotaskmem_string zoneSetId; - if (SUCCEEDED_LOG(StringFromCLSID(m_activeZoneSet->Id(), &zoneSetId))) - { - FancyZonesDataTypes::ZoneSetData data{ - .uuid = zoneSetId.get(), - .type = m_activeZoneSet->LayoutType() - }; - FancyZonesDataInstance().SetActiveZoneSet(m_uniqueId, data); - } - } -} - -LRESULT ZoneWindow::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept -{ - switch (message) - { - case WM_NCDESTROY: - { - ::DefWindowProc(m_window, message, wparam, lparam); - SetWindowLongPtr(m_window, GWLP_USERDATA, 0); - } - break; - - case WM_ERASEBKGND: - return 1; - - default: - { - return DefWindowProc(m_window, message, wparam, lparam); - } - } - return 0; -} - -std::vector ZoneWindow::ZonesFromPoint(POINT pt) noexcept -{ - if (m_activeZoneSet) - { - return m_activeZoneSet->ZonesFromPoint(pt); - } - return {}; -} - -void ZoneWindow::SetAsTopmostWindow() noexcept -{ - if (!m_window) - { - return; - } - - UINT flags = SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE; - - HWND windowInsertAfter = m_windowMoveSize; - if (windowInsertAfter == nullptr) - { - windowInsertAfter = HWND_TOPMOST; - } - - SetWindowPos(m_window, windowInsertAfter, 0, 0, 0, 0, flags); -} - -#pragma endregion - -LRESULT CALLBACK ZoneWindow::s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept -{ - auto thisRef = reinterpret_cast(GetWindowLongPtr(window, GWLP_USERDATA)); - if ((thisRef == nullptr) && (message == WM_CREATE)) - { - auto createStruct = reinterpret_cast(lparam); - thisRef = reinterpret_cast(createStruct->lpCreateParams); - SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(thisRef)); - } - - return (thisRef != nullptr) ? thisRef->WndProc(message, wparam, lparam) : - DefWindowProc(window, message, wparam, lparam); -} - -winrt::com_ptr MakeZoneWindow(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, const std::wstring& uniqueId, const std::wstring& parentUniqueId) noexcept -{ - auto self = winrt::make_self(hinstance); - if (self->Init(host, hinstance, monitor, uniqueId, parentUniqueId)) - { - return self; - } - - return nullptr; -} +#include "pch.h" + +#include + +#include "FancyZonesData.h" +#include "FancyZonesDataTypes.h" +#include "ZoneWindow.h" +#include "ZoneWindowDrawing.h" +#include "trace.h" +#include "util.h" +#include "on_thread_executor.h" +#include "Settings.h" +#include "CallTracer.h" + +#include +#include +#include + +#include + +// Non-Localizable strings +namespace NonLocalizable +{ + const wchar_t ToolWindowClassName[] = L"SuperFancyZones_ZoneWindow"; +} + +using namespace FancyZonesUtils; + +struct ZoneWindow; + +namespace +{ + // The reason for using this class is the need to call ShowWindow(window, SW_SHOWNORMAL); on each + // newly created window for it to be displayed properly. The call sometimes has side effects when + // a fullscreen app is running, and happens when the resolution change event is triggered + // (e.g. when running some games). + // This class will serve as a pool of windows for which this call was already done. + class WindowPool + { + std::vector m_pool; + std::mutex m_mutex; + + HWND ExtractWindow() + { + _TRACER_; + std::unique_lock lock(m_mutex); + + if (m_pool.empty()) + { + return NULL; + } + + HWND window = m_pool.back(); + m_pool.pop_back(); + return window; + } + + public: + + HWND NewZoneWindow(Rect position, HINSTANCE hinstance, ZoneWindow* owner) + { + HWND windowFromPool = ExtractWindow(); + if (windowFromPool == NULL) + { + HWND window = CreateWindowExW(WS_EX_TOOLWINDOW, NonLocalizable::ToolWindowClassName, L"", WS_POPUP, position.left(), position.top(), position.width(), position.height(), nullptr, nullptr, hinstance, owner); + Logger::info("Creating new zone window, hWnd = {}", (void*)window); + MakeWindowTransparent(window); + + // According to ShowWindow docs, we must call it with SW_SHOWNORMAL the first time + ShowWindow(window, SW_SHOWNORMAL); + ShowWindow(window, SW_HIDE); + return window; + } + else + { + Logger::info("Reusing zone window from pool, hWnd = {}", (void*)windowFromPool); + SetWindowLongPtrW(windowFromPool, GWLP_USERDATA, reinterpret_cast(owner)); + MoveWindow(windowFromPool, position.left(), position.top(), position.width(), position.height(), TRUE); + return windowFromPool; + } + } + + void FreeZoneWindow(HWND window) + { + _TRACER_; + Logger::info("Freeing zone window, hWnd = {}", (void*)window); + SetWindowLongPtrW(window, GWLP_USERDATA, 0); + ShowWindow(window, SW_HIDE); + + std::unique_lock lock(m_mutex); + m_pool.push_back(window); + } + + ~WindowPool() + { + for (HWND window : m_pool) + { + DestroyWindow(window); + } + } + }; + + WindowPool windowPool; +} + +struct ZoneWindow : public winrt::implements +{ +public: + ZoneWindow(HINSTANCE hinstance); + ~ZoneWindow(); + + bool Init(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, const std::wstring& uniqueId, const std::wstring& parentUniqueId); + + IFACEMETHODIMP MoveSizeEnter(HWND window) noexcept; + IFACEMETHODIMP MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool selectManyZones) noexcept; + IFACEMETHODIMP MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept; + IFACEMETHODIMP_(void) + MoveWindowIntoZoneByIndex(HWND window, size_t index) noexcept; + IFACEMETHODIMP_(void) + MoveWindowIntoZoneByIndexSet(HWND window, const std::vector& indexSet) noexcept; + IFACEMETHODIMP_(bool) + MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle) noexcept; + IFACEMETHODIMP_(bool) + MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle) noexcept; + IFACEMETHODIMP_(bool) + ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode) noexcept; + IFACEMETHODIMP_(std::wstring) + UniqueId() noexcept { return { m_uniqueId }; } + IFACEMETHODIMP_(void) + SaveWindowProcessToZoneIndex(HWND window) noexcept; + IFACEMETHODIMP_(IZoneSet*) + ActiveZoneSet() noexcept { return m_activeZoneSet.get(); } + IFACEMETHODIMP_(void) + ShowZoneWindow() noexcept; + IFACEMETHODIMP_(void) + HideZoneWindow() noexcept; + IFACEMETHODIMP_(void) + UpdateActiveZoneSet() noexcept; + IFACEMETHODIMP_(void) + ClearSelectedZones() noexcept; + IFACEMETHODIMP_(void) + FlashZones() noexcept; + +protected: + static LRESULT CALLBACK s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept; + +private: + void InitializeZoneSets(const std::wstring& parentUniqueId) noexcept; + void CalculateZoneSet() noexcept; + void UpdateActiveZoneSet(_In_opt_ IZoneSet* zoneSet) noexcept; + LRESULT WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept; + std::vector ZonesFromPoint(POINT pt) noexcept; + void SetAsTopmostWindow() noexcept; + + winrt::com_ptr m_host; + HMONITOR m_monitor{}; + std::wstring m_uniqueId; // Parsed deviceId + resolution + virtualDesktopId + HWND m_window{}; // Hidden tool window used to represent current monitor desktop work area. + HWND m_windowMoveSize{}; + winrt::com_ptr m_activeZoneSet; + std::vector> m_zoneSets; + std::vector m_initialHighlightZone; + std::vector m_highlightZone; + WPARAM m_keyLast{}; + size_t m_keyCycle{}; + std::unique_ptr m_zoneWindowDrawing; +}; + +ZoneWindow::ZoneWindow(HINSTANCE hinstance) +{ + WNDCLASSEXW wcex{}; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.lpfnWndProc = s_WndProc; + wcex.hInstance = hinstance; + wcex.lpszClassName = NonLocalizable::ToolWindowClassName; + wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW); + RegisterClassExW(&wcex); +} + +ZoneWindow::~ZoneWindow() +{ + windowPool.FreeZoneWindow(m_window); +} + +bool ZoneWindow::Init(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, const std::wstring& uniqueId, const std::wstring& parentUniqueId) +{ + m_host.copy_from(host); + + Rect workAreaRect; + m_monitor = monitor; + if (monitor) + { + MONITORINFO mi{}; + mi.cbSize = sizeof(mi); + if (!GetMonitorInfoW(monitor, &mi)) + { + return false; + } + workAreaRect = Rect(mi.rcWork); + } + else + { + workAreaRect = GetAllMonitorsCombinedRect<&MONITORINFO::rcWork>(); + } + + m_uniqueId = uniqueId; + InitializeZoneSets(parentUniqueId); + + m_window = windowPool.NewZoneWindow(workAreaRect, hinstance, this); + + if (!m_window) + { + return false; + } + + m_zoneWindowDrawing = std::make_unique(m_window); + + return true; +} + +IFACEMETHODIMP ZoneWindow::MoveSizeEnter(HWND window) noexcept +{ + m_windowMoveSize = window; + m_highlightZone = {}; + m_initialHighlightZone = {}; + ShowZoneWindow(); + return S_OK; +} + +IFACEMETHODIMP ZoneWindow::MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool selectManyZones) noexcept +{ + bool redraw = false; + POINT ptClient = ptScreen; + MapWindowPoints(nullptr, m_window, &ptClient, 1); + + if (dragEnabled) + { + auto highlightZone = ZonesFromPoint(ptClient); + + if (selectManyZones) + { + if (m_initialHighlightZone.empty()) + { + // first time + m_initialHighlightZone = highlightZone; + } + else + { + highlightZone = m_activeZoneSet->GetCombinedZoneRange(m_initialHighlightZone, highlightZone); + } + } + else + { + m_initialHighlightZone = {}; + } + + redraw = (highlightZone != m_highlightZone); + m_highlightZone = std::move(highlightZone); + } + else if (m_highlightZone.size()) + { + m_highlightZone = {}; + redraw = true; + } + + if (redraw) + { + m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), m_highlightZone, m_host); + } + + return S_OK; +} + +IFACEMETHODIMP ZoneWindow::MoveSizeEnd(HWND window, POINT const& ptScreen) noexcept +{ + if (m_windowMoveSize != window) + { + return E_INVALIDARG; + } + + if (m_activeZoneSet) + { + POINT ptClient = ptScreen; + MapWindowPoints(nullptr, m_window, &ptClient, 1); + m_activeZoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, m_highlightZone); + + if (FancyZonesUtils::HasNoVisibleOwner(window)) + { + SaveWindowProcessToZoneIndex(window); + } + } + Trace::ZoneWindow::MoveSizeEnd(m_activeZoneSet); + + HideZoneWindow(); + m_windowMoveSize = nullptr; + return S_OK; +} + +IFACEMETHODIMP_(void) +ZoneWindow::MoveWindowIntoZoneByIndex(HWND window, size_t index) noexcept +{ + MoveWindowIntoZoneByIndexSet(window, { index }); +} + +IFACEMETHODIMP_(void) +ZoneWindow::MoveWindowIntoZoneByIndexSet(HWND window, const std::vector& indexSet) noexcept +{ + if (m_activeZoneSet) + { + m_activeZoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, indexSet); + } +} + +IFACEMETHODIMP_(bool) +ZoneWindow::MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle) noexcept +{ + if (m_activeZoneSet) + { + if (m_activeZoneSet->MoveWindowIntoZoneByDirectionAndIndex(window, m_window, vkCode, cycle)) + { + if (FancyZonesUtils::HasNoVisibleOwner(window)) + { + SaveWindowProcessToZoneIndex(window); + } + return true; + } + } + return false; +} + +IFACEMETHODIMP_(bool) +ZoneWindow::MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle) noexcept +{ + if (m_activeZoneSet) + { + if (m_activeZoneSet->MoveWindowIntoZoneByDirectionAndPosition(window, m_window, vkCode, cycle)) + { + SaveWindowProcessToZoneIndex(window); + return true; + } + } + return false; +} + +IFACEMETHODIMP_(bool) +ZoneWindow::ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode) noexcept +{ + if (m_activeZoneSet) + { + if (m_activeZoneSet->ExtendWindowByDirectionAndPosition(window, m_window, vkCode)) + { + SaveWindowProcessToZoneIndex(window); + return true; + } + } + return false; +} + +IFACEMETHODIMP_(void) +ZoneWindow::SaveWindowProcessToZoneIndex(HWND window) noexcept +{ + if (m_activeZoneSet) + { + auto zoneIndexSet = m_activeZoneSet->GetZoneIndexSetFromWindow(window); + if (zoneIndexSet.size()) + { + OLECHAR* guidString; + if (StringFromCLSID(m_activeZoneSet->Id(), &guidString) == S_OK) + { + FancyZonesDataInstance().SetAppLastZones(window, m_uniqueId, guidString, zoneIndexSet); + } + + CoTaskMemFree(guidString); + } + } +} + +IFACEMETHODIMP_(void) +ZoneWindow::ShowZoneWindow() noexcept +{ + if (m_window) + { + SetAsTopmostWindow(); + m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), m_highlightZone, m_host); + m_zoneWindowDrawing->Show(); + } +} + +IFACEMETHODIMP_(void) +ZoneWindow::HideZoneWindow() noexcept +{ + if (m_window) + { + m_zoneWindowDrawing->Hide(); + m_keyLast = 0; + m_windowMoveSize = nullptr; + m_highlightZone = {}; + } +} + +IFACEMETHODIMP_(void) +ZoneWindow::UpdateActiveZoneSet() noexcept +{ + CalculateZoneSet(); + if (m_window) + { + m_highlightZone.clear(); + m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), m_highlightZone, m_host); + } +} + +IFACEMETHODIMP_(void) +ZoneWindow::ClearSelectedZones() noexcept +{ + if (m_highlightZone.size()) + { + m_highlightZone.clear(); + m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), m_highlightZone, m_host); + } +} + +IFACEMETHODIMP_(void) +ZoneWindow::FlashZones() noexcept +{ + if (m_window) + { + SetAsTopmostWindow(); + m_zoneWindowDrawing->DrawActiveZoneSet(m_activeZoneSet->GetZones(), {}, m_host); + m_zoneWindowDrawing->Flash(); + } +} + +#pragma region private + +void ZoneWindow::InitializeZoneSets(const std::wstring& parentUniqueId) noexcept +{ + bool deviceAdded = FancyZonesDataInstance().AddDevice(m_uniqueId); + // If the device has been added, check if it should inherit the parent's layout + if (deviceAdded && !parentUniqueId.empty()) + { + FancyZonesDataInstance().CloneDeviceInfo(parentUniqueId, m_uniqueId); + } + CalculateZoneSet(); +} + +void ZoneWindow::CalculateZoneSet() noexcept +{ + const auto& fancyZonesData = FancyZonesDataInstance(); + const auto deviceInfoData = fancyZonesData.FindDeviceInfo(m_uniqueId); + + if (!deviceInfoData.has_value()) + { + return; + } + + const auto& activeZoneSet = deviceInfoData->activeZoneSet; + + if (activeZoneSet.uuid.empty()) + { + return; + } + + GUID zoneSetId; + if (SUCCEEDED_LOG(CLSIDFromString(activeZoneSet.uuid.c_str(), &zoneSetId))) + { + int sensitivityRadius = deviceInfoData->sensitivityRadius; + + auto zoneSet = MakeZoneSet(ZoneSetConfig( + zoneSetId, + activeZoneSet.type, + m_monitor, + sensitivityRadius, + m_host->GetOverlappingZonesAlgorithm())); + + RECT workArea; + if (m_monitor) + { + MONITORINFO monitorInfo{}; + monitorInfo.cbSize = sizeof(monitorInfo); + if (GetMonitorInfoW(m_monitor, &monitorInfo)) + { + workArea = monitorInfo.rcWork; + } + else + { + return; + } + } + else + { + workArea = GetAllMonitorsCombinedRect<&MONITORINFO::rcWork>(); + } + + bool showSpacing = deviceInfoData->showSpacing; + int spacing = showSpacing ? deviceInfoData->spacing : 0; + int zoneCount = deviceInfoData->zoneCount; + + zoneSet->CalculateZones(workArea, zoneCount, spacing); + UpdateActiveZoneSet(zoneSet.get()); + } +} + +void ZoneWindow::UpdateActiveZoneSet(_In_opt_ IZoneSet* zoneSet) noexcept +{ + m_activeZoneSet.copy_from(zoneSet); + + if (m_activeZoneSet) + { + wil::unique_cotaskmem_string zoneSetId; + if (SUCCEEDED_LOG(StringFromCLSID(m_activeZoneSet->Id(), &zoneSetId))) + { + FancyZonesDataTypes::ZoneSetData data{ + .uuid = zoneSetId.get(), + .type = m_activeZoneSet->LayoutType() + }; + FancyZonesDataInstance().SetActiveZoneSet(m_uniqueId, data); + } + } +} + +LRESULT ZoneWindow::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept +{ + switch (message) + { + case WM_NCDESTROY: + { + ::DefWindowProc(m_window, message, wparam, lparam); + SetWindowLongPtr(m_window, GWLP_USERDATA, 0); + } + break; + + case WM_ERASEBKGND: + return 1; + + default: + { + return DefWindowProc(m_window, message, wparam, lparam); + } + } + return 0; +} + +std::vector ZoneWindow::ZonesFromPoint(POINT pt) noexcept +{ + if (m_activeZoneSet) + { + return m_activeZoneSet->ZonesFromPoint(pt); + } + return {}; +} + +void ZoneWindow::SetAsTopmostWindow() noexcept +{ + if (!m_window) + { + return; + } + + UINT flags = SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE; + + HWND windowInsertAfter = m_windowMoveSize; + if (windowInsertAfter == nullptr) + { + windowInsertAfter = HWND_TOPMOST; + } + + SetWindowPos(m_window, windowInsertAfter, 0, 0, 0, 0, flags); +} + +#pragma endregion + +LRESULT CALLBACK ZoneWindow::s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept +{ + auto thisRef = reinterpret_cast(GetWindowLongPtr(window, GWLP_USERDATA)); + if ((thisRef == nullptr) && (message == WM_CREATE)) + { + auto createStruct = reinterpret_cast(lparam); + thisRef = reinterpret_cast(createStruct->lpCreateParams); + SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(thisRef)); + } + + return (thisRef != nullptr) ? thisRef->WndProc(message, wparam, lparam) : + DefWindowProc(window, message, wparam, lparam); +} + +winrt::com_ptr MakeZoneWindow(IZoneWindowHost* host, HINSTANCE hinstance, HMONITOR monitor, const std::wstring& uniqueId, const std::wstring& parentUniqueId) noexcept +{ + auto self = winrt::make_self(hinstance); + if (self->Init(host, hinstance, monitor, uniqueId, parentUniqueId)) + { + return self; + } + + return nullptr; +} diff --git a/src/modules/fancyzones/lib/ZoneWindow.h b/src/modules/fancyzones/FancyZonesLib/ZoneWindow.h similarity index 99% rename from src/modules/fancyzones/lib/ZoneWindow.h rename to src/modules/fancyzones/FancyZonesLib/ZoneWindow.h index 708f4bd3c2..4e882637f7 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.h +++ b/src/modules/fancyzones/FancyZonesLib/ZoneWindow.h @@ -1,6 +1,6 @@ #pragma once #include "FancyZones.h" -#include "lib/ZoneSet.h" +#include "FancyZonesLib/ZoneSet.h" /** * Class representing single work area, which is defined by monitor and virtual desktop. diff --git a/src/modules/fancyzones/lib/ZoneWindowDrawing.cpp b/src/modules/fancyzones/FancyZonesLib/ZoneWindowDrawing.cpp similarity index 100% rename from src/modules/fancyzones/lib/ZoneWindowDrawing.cpp rename to src/modules/fancyzones/FancyZonesLib/ZoneWindowDrawing.cpp diff --git a/src/modules/fancyzones/lib/ZoneWindowDrawing.h b/src/modules/fancyzones/FancyZonesLib/ZoneWindowDrawing.h similarity index 100% rename from src/modules/fancyzones/lib/ZoneWindowDrawing.h rename to src/modules/fancyzones/FancyZonesLib/ZoneWindowDrawing.h diff --git a/src/modules/fancyzones/lib/fancyzones.base.rc b/src/modules/fancyzones/FancyZonesLib/fancyzones.base.rc similarity index 100% rename from src/modules/fancyzones/lib/fancyzones.base.rc rename to src/modules/fancyzones/FancyZonesLib/fancyzones.base.rc diff --git a/src/modules/fancyzones/lib/loc/cs/src/modules/fancyzones/lib/Resources.resx.lcl b/src/modules/fancyzones/FancyZonesLib/loc/cs/src/modules/fancyzones/lib/Resources.resx.lcl similarity index 98% rename from src/modules/fancyzones/lib/loc/cs/src/modules/fancyzones/lib/Resources.resx.lcl rename to src/modules/fancyzones/FancyZonesLib/loc/cs/src/modules/fancyzones/lib/Resources.resx.lcl index 7c7c3cd948..b64cc0eb70 100644 --- a/src/modules/fancyzones/lib/loc/cs/src/modules/fancyzones/lib/Resources.resx.lcl +++ b/src/modules/fancyzones/FancyZonesLib/loc/cs/src/modules/fancyzones/lib/Resources.resx.lcl @@ -1,5 +1,5 @@  - + diff --git a/src/modules/fancyzones/lib/loc/de/src/modules/fancyzones/lib/Resources.resx.lcl b/src/modules/fancyzones/FancyZonesLib/loc/de/src/modules/fancyzones/lib/Resources.resx.lcl similarity index 98% rename from src/modules/fancyzones/lib/loc/de/src/modules/fancyzones/lib/Resources.resx.lcl rename to src/modules/fancyzones/FancyZonesLib/loc/de/src/modules/fancyzones/lib/Resources.resx.lcl index 5d550dbef3..57760e9204 100644 --- a/src/modules/fancyzones/lib/loc/de/src/modules/fancyzones/lib/Resources.resx.lcl +++ b/src/modules/fancyzones/FancyZonesLib/loc/de/src/modules/fancyzones/lib/Resources.resx.lcl @@ -1,5 +1,5 @@  - + diff --git a/src/modules/fancyzones/lib/loc/es/src/modules/fancyzones/lib/Resources.resx.lcl b/src/modules/fancyzones/FancyZonesLib/loc/es/src/modules/fancyzones/lib/Resources.resx.lcl similarity index 98% rename from src/modules/fancyzones/lib/loc/es/src/modules/fancyzones/lib/Resources.resx.lcl rename to src/modules/fancyzones/FancyZonesLib/loc/es/src/modules/fancyzones/lib/Resources.resx.lcl index 3eb55b568f..76f8067460 100644 --- a/src/modules/fancyzones/lib/loc/es/src/modules/fancyzones/lib/Resources.resx.lcl +++ b/src/modules/fancyzones/FancyZonesLib/loc/es/src/modules/fancyzones/lib/Resources.resx.lcl @@ -1,5 +1,5 @@  - + diff --git a/src/modules/fancyzones/lib/loc/fr/src/modules/fancyzones/lib/Resources.resx.lcl b/src/modules/fancyzones/FancyZonesLib/loc/fr/src/modules/fancyzones/lib/Resources.resx.lcl similarity index 98% rename from src/modules/fancyzones/lib/loc/fr/src/modules/fancyzones/lib/Resources.resx.lcl rename to src/modules/fancyzones/FancyZonesLib/loc/fr/src/modules/fancyzones/lib/Resources.resx.lcl index 6d0c70cafc..7a5c346a6a 100644 --- a/src/modules/fancyzones/lib/loc/fr/src/modules/fancyzones/lib/Resources.resx.lcl +++ b/src/modules/fancyzones/FancyZonesLib/loc/fr/src/modules/fancyzones/lib/Resources.resx.lcl @@ -1,5 +1,5 @@  - + diff --git a/src/modules/fancyzones/lib/loc/hu/src/modules/fancyzones/lib/Resources.resx.lcl b/src/modules/fancyzones/FancyZonesLib/loc/hu/src/modules/fancyzones/lib/Resources.resx.lcl similarity index 98% rename from src/modules/fancyzones/lib/loc/hu/src/modules/fancyzones/lib/Resources.resx.lcl rename to src/modules/fancyzones/FancyZonesLib/loc/hu/src/modules/fancyzones/lib/Resources.resx.lcl index a891523e1f..7bd8671080 100644 --- a/src/modules/fancyzones/lib/loc/hu/src/modules/fancyzones/lib/Resources.resx.lcl +++ b/src/modules/fancyzones/FancyZonesLib/loc/hu/src/modules/fancyzones/lib/Resources.resx.lcl @@ -1,5 +1,5 @@  - + diff --git a/src/modules/fancyzones/lib/loc/it/src/modules/fancyzones/lib/Resources.resx.lcl b/src/modules/fancyzones/FancyZonesLib/loc/it/src/modules/fancyzones/lib/Resources.resx.lcl similarity index 98% rename from src/modules/fancyzones/lib/loc/it/src/modules/fancyzones/lib/Resources.resx.lcl rename to src/modules/fancyzones/FancyZonesLib/loc/it/src/modules/fancyzones/lib/Resources.resx.lcl index 7111677bde..541e58d9ad 100644 --- a/src/modules/fancyzones/lib/loc/it/src/modules/fancyzones/lib/Resources.resx.lcl +++ b/src/modules/fancyzones/FancyZonesLib/loc/it/src/modules/fancyzones/lib/Resources.resx.lcl @@ -1,5 +1,5 @@  - + diff --git a/src/modules/fancyzones/lib/loc/ja/src/modules/fancyzones/lib/Resources.resx.lcl b/src/modules/fancyzones/FancyZonesLib/loc/ja/src/modules/fancyzones/lib/Resources.resx.lcl similarity index 98% rename from src/modules/fancyzones/lib/loc/ja/src/modules/fancyzones/lib/Resources.resx.lcl rename to src/modules/fancyzones/FancyZonesLib/loc/ja/src/modules/fancyzones/lib/Resources.resx.lcl index 46fe35c449..72eefb9d31 100644 --- a/src/modules/fancyzones/lib/loc/ja/src/modules/fancyzones/lib/Resources.resx.lcl +++ b/src/modules/fancyzones/FancyZonesLib/loc/ja/src/modules/fancyzones/lib/Resources.resx.lcl @@ -1,5 +1,5 @@  - + diff --git a/src/modules/fancyzones/lib/loc/ko/src/modules/fancyzones/lib/Resources.resx.lcl b/src/modules/fancyzones/FancyZonesLib/loc/ko/src/modules/fancyzones/lib/Resources.resx.lcl similarity index 98% rename from src/modules/fancyzones/lib/loc/ko/src/modules/fancyzones/lib/Resources.resx.lcl rename to src/modules/fancyzones/FancyZonesLib/loc/ko/src/modules/fancyzones/lib/Resources.resx.lcl index 78b5a2d157..adfbe185a3 100644 --- a/src/modules/fancyzones/lib/loc/ko/src/modules/fancyzones/lib/Resources.resx.lcl +++ b/src/modules/fancyzones/FancyZonesLib/loc/ko/src/modules/fancyzones/lib/Resources.resx.lcl @@ -1,5 +1,5 @@  - + diff --git a/src/modules/fancyzones/lib/loc/nl/src/modules/fancyzones/lib/Resources.resx.lcl b/src/modules/fancyzones/FancyZonesLib/loc/nl/src/modules/fancyzones/lib/Resources.resx.lcl similarity index 98% rename from src/modules/fancyzones/lib/loc/nl/src/modules/fancyzones/lib/Resources.resx.lcl rename to src/modules/fancyzones/FancyZonesLib/loc/nl/src/modules/fancyzones/lib/Resources.resx.lcl index d832516ad5..bb243e4c7d 100644 --- a/src/modules/fancyzones/lib/loc/nl/src/modules/fancyzones/lib/Resources.resx.lcl +++ b/src/modules/fancyzones/FancyZonesLib/loc/nl/src/modules/fancyzones/lib/Resources.resx.lcl @@ -1,5 +1,5 @@  - + diff --git a/src/modules/fancyzones/lib/loc/pl/src/modules/fancyzones/lib/Resources.resx.lcl b/src/modules/fancyzones/FancyZonesLib/loc/pl/src/modules/fancyzones/lib/Resources.resx.lcl similarity index 98% rename from src/modules/fancyzones/lib/loc/pl/src/modules/fancyzones/lib/Resources.resx.lcl rename to src/modules/fancyzones/FancyZonesLib/loc/pl/src/modules/fancyzones/lib/Resources.resx.lcl index 4e224ee2a0..6041832a39 100644 --- a/src/modules/fancyzones/lib/loc/pl/src/modules/fancyzones/lib/Resources.resx.lcl +++ b/src/modules/fancyzones/FancyZonesLib/loc/pl/src/modules/fancyzones/lib/Resources.resx.lcl @@ -1,5 +1,5 @@  - + diff --git a/src/modules/fancyzones/lib/loc/pt-BR/src/modules/fancyzones/lib/Resources.resx.lcl b/src/modules/fancyzones/FancyZonesLib/loc/pt-BR/src/modules/fancyzones/lib/Resources.resx.lcl similarity index 98% rename from src/modules/fancyzones/lib/loc/pt-BR/src/modules/fancyzones/lib/Resources.resx.lcl rename to src/modules/fancyzones/FancyZonesLib/loc/pt-BR/src/modules/fancyzones/lib/Resources.resx.lcl index f480bdca7c..a9c0be53df 100644 --- a/src/modules/fancyzones/lib/loc/pt-BR/src/modules/fancyzones/lib/Resources.resx.lcl +++ b/src/modules/fancyzones/FancyZonesLib/loc/pt-BR/src/modules/fancyzones/lib/Resources.resx.lcl @@ -1,5 +1,5 @@  - + diff --git a/src/modules/fancyzones/lib/loc/pt-PT/src/modules/fancyzones/lib/Resources.resx.lcl b/src/modules/fancyzones/FancyZonesLib/loc/pt-PT/src/modules/fancyzones/lib/Resources.resx.lcl similarity index 98% rename from src/modules/fancyzones/lib/loc/pt-PT/src/modules/fancyzones/lib/Resources.resx.lcl rename to src/modules/fancyzones/FancyZonesLib/loc/pt-PT/src/modules/fancyzones/lib/Resources.resx.lcl index 66033a4f6c..410155324b 100644 --- a/src/modules/fancyzones/lib/loc/pt-PT/src/modules/fancyzones/lib/Resources.resx.lcl +++ b/src/modules/fancyzones/FancyZonesLib/loc/pt-PT/src/modules/fancyzones/lib/Resources.resx.lcl @@ -1,5 +1,5 @@  - + diff --git a/src/modules/fancyzones/lib/loc/ru/src/modules/fancyzones/lib/Resources.resx.lcl b/src/modules/fancyzones/FancyZonesLib/loc/ru/src/modules/fancyzones/lib/Resources.resx.lcl similarity index 99% rename from src/modules/fancyzones/lib/loc/ru/src/modules/fancyzones/lib/Resources.resx.lcl rename to src/modules/fancyzones/FancyZonesLib/loc/ru/src/modules/fancyzones/lib/Resources.resx.lcl index 447e0958a0..bdcba98ceb 100644 --- a/src/modules/fancyzones/lib/loc/ru/src/modules/fancyzones/lib/Resources.resx.lcl +++ b/src/modules/fancyzones/FancyZonesLib/loc/ru/src/modules/fancyzones/lib/Resources.resx.lcl @@ -1,5 +1,5 @@  - + diff --git a/src/modules/fancyzones/lib/loc/sv/src/modules/fancyzones/lib/Resources.resx.lcl b/src/modules/fancyzones/FancyZonesLib/loc/sv/src/modules/fancyzones/lib/Resources.resx.lcl similarity index 98% rename from src/modules/fancyzones/lib/loc/sv/src/modules/fancyzones/lib/Resources.resx.lcl rename to src/modules/fancyzones/FancyZonesLib/loc/sv/src/modules/fancyzones/lib/Resources.resx.lcl index 6a781f8931..4516fd8469 100644 --- a/src/modules/fancyzones/lib/loc/sv/src/modules/fancyzones/lib/Resources.resx.lcl +++ b/src/modules/fancyzones/FancyZonesLib/loc/sv/src/modules/fancyzones/lib/Resources.resx.lcl @@ -1,5 +1,5 @@  - + diff --git a/src/modules/fancyzones/lib/loc/tr/src/modules/fancyzones/lib/Resources.resx.lcl b/src/modules/fancyzones/FancyZonesLib/loc/tr/src/modules/fancyzones/lib/Resources.resx.lcl similarity index 98% rename from src/modules/fancyzones/lib/loc/tr/src/modules/fancyzones/lib/Resources.resx.lcl rename to src/modules/fancyzones/FancyZonesLib/loc/tr/src/modules/fancyzones/lib/Resources.resx.lcl index 7f1e322548..3ae211c8f1 100644 --- a/src/modules/fancyzones/lib/loc/tr/src/modules/fancyzones/lib/Resources.resx.lcl +++ b/src/modules/fancyzones/FancyZonesLib/loc/tr/src/modules/fancyzones/lib/Resources.resx.lcl @@ -1,5 +1,5 @@  - + diff --git a/src/modules/fancyzones/lib/loc/zh-Hans/src/modules/fancyzones/lib/Resources.resx.lcl b/src/modules/fancyzones/FancyZonesLib/loc/zh-Hans/src/modules/fancyzones/lib/Resources.resx.lcl similarity index 98% rename from src/modules/fancyzones/lib/loc/zh-Hans/src/modules/fancyzones/lib/Resources.resx.lcl rename to src/modules/fancyzones/FancyZonesLib/loc/zh-Hans/src/modules/fancyzones/lib/Resources.resx.lcl index 2dcfeb939a..667bca7ce8 100644 --- a/src/modules/fancyzones/lib/loc/zh-Hans/src/modules/fancyzones/lib/Resources.resx.lcl +++ b/src/modules/fancyzones/FancyZonesLib/loc/zh-Hans/src/modules/fancyzones/lib/Resources.resx.lcl @@ -1,5 +1,5 @@  - + diff --git a/src/modules/fancyzones/lib/loc/zh-Hant/src/modules/fancyzones/lib/Resources.resx.lcl b/src/modules/fancyzones/FancyZonesLib/loc/zh-Hant/src/modules/fancyzones/lib/Resources.resx.lcl similarity index 98% rename from src/modules/fancyzones/lib/loc/zh-Hant/src/modules/fancyzones/lib/Resources.resx.lcl rename to src/modules/fancyzones/FancyZonesLib/loc/zh-Hant/src/modules/fancyzones/lib/Resources.resx.lcl index 8606e7247d..5920b05162 100644 --- a/src/modules/fancyzones/lib/loc/zh-Hant/src/modules/fancyzones/lib/Resources.resx.lcl +++ b/src/modules/fancyzones/FancyZonesLib/loc/zh-Hant/src/modules/fancyzones/lib/Resources.resx.lcl @@ -1,5 +1,5 @@  - + diff --git a/src/modules/fancyzones/lib/on_thread_executor.h b/src/modules/fancyzones/FancyZonesLib/on_thread_executor.h similarity index 100% rename from src/modules/fancyzones/lib/on_thread_executor.h rename to src/modules/fancyzones/FancyZonesLib/on_thread_executor.h diff --git a/src/modules/fancyzones/tests/UnitTests/packages.config b/src/modules/fancyzones/FancyZonesLib/packages.config similarity index 100% rename from src/modules/fancyzones/tests/UnitTests/packages.config rename to src/modules/fancyzones/FancyZonesLib/packages.config diff --git a/src/modules/fancyzones/lib/pch.cpp b/src/modules/fancyzones/FancyZonesLib/pch.cpp similarity index 97% rename from src/modules/fancyzones/lib/pch.cpp rename to src/modules/fancyzones/FancyZonesLib/pch.cpp index 91c22df2a1..64b7eef6d6 100644 --- a/src/modules/fancyzones/lib/pch.cpp +++ b/src/modules/fancyzones/FancyZonesLib/pch.cpp @@ -1,5 +1,5 @@ -// pch.cpp: source file corresponding to the pre-compiled header - -#include "pch.h" - -// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/src/modules/fancyzones/lib/pch.h b/src/modules/fancyzones/FancyZonesLib/pch.h similarity index 95% rename from src/modules/fancyzones/lib/pch.h rename to src/modules/fancyzones/FancyZonesLib/pch.h index 1d1d5796a1..db5d09a09f 100644 --- a/src/modules/fancyzones/lib/pch.h +++ b/src/modules/fancyzones/FancyZonesLib/pch.h @@ -1,29 +1,29 @@ -#pragma once -#include "Generated Files/resource.h" - -#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace winrt -{ - using namespace ::winrt; +#pragma once +#include "Generated Files/resource.h" + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace winrt +{ + using namespace ::winrt; } \ No newline at end of file diff --git a/src/modules/fancyzones/lib/resource.base.h b/src/modules/fancyzones/FancyZonesLib/resource.base.h similarity index 73% rename from src/modules/fancyzones/lib/resource.base.h rename to src/modules/fancyzones/FancyZonesLib/resource.base.h index 75649eba85..65fce5dd59 100644 --- a/src/modules/fancyzones/lib/resource.base.h +++ b/src/modules/fancyzones/FancyZonesLib/resource.base.h @@ -6,8 +6,8 @@ // Non-localizable #define FILE_DESCRIPTION "PowerToys FancyZones" -#define INTERNAL_NAME "fancyzones" -#define ORIGINAL_FILENAME "fancyzones.dll" +#define INTERNAL_NAME "FancyZones" +#define ORIGINAL_FILENAME "PowerToys.FancyZones.exe" // Non-localizable ////////////////////////////// diff --git a/src/modules/fancyzones/lib/trace.cpp b/src/modules/fancyzones/FancyZonesLib/trace.cpp similarity index 97% rename from src/modules/fancyzones/lib/trace.cpp rename to src/modules/fancyzones/FancyZonesLib/trace.cpp index e1265f58b1..49365f2eda 100644 --- a/src/modules/fancyzones/lib/trace.cpp +++ b/src/modules/fancyzones/FancyZonesLib/trace.cpp @@ -1,9 +1,9 @@ #include "pch.h" #include "trace.h" -#include "lib/ZoneSet.h" -#include "lib/Settings.h" -#include "lib/FancyZonesData.h" -#include "lib/FancyZonesDataTypes.h" +#include "FancyZonesLib/ZoneSet.h" +#include "FancyZonesLib/Settings.h" +#include "FancyZonesLib/FancyZonesData.h" +#include "FancyZonesLib/FancyZonesDataTypes.h" // Telemetry strings should not be localized. #define LoggingProviderKey "Microsoft.PowerToys" @@ -12,7 +12,7 @@ #define EventKeyDownKey "FancyZones_OnKeyDown" #define EventZoneSettingsChangedKey "FancyZones_ZoneSettingsChanged" #define EventEditorLaunchKey "FancyZones_EditorLaunch" -#define EventSettingsChangedKey "FancyZones_SettingsChanged" +#define EventSettingsKey "FancyZones_Settings" #define EventDesktopChangedKey "FancyZones_VirtualDesktopChanged" #define EventZoneWindowKeyUpKey "FancyZones_ZoneWindowKeyUp" #define EventMoveSizeEndKey "FancyZones_MoveSizeEnd" @@ -244,7 +244,7 @@ void Trace::FancyZones::QuickLayoutSwitched(bool shortcutUsed) noexcept TraceLoggingBoolean(shortcutUsed, QuickLayoutSwitchedWithShortcutUsed)); } -void Trace::SettingsChanged(const Settings& settings) noexcept +void Trace::SettingsTelemetry(const Settings& settings) noexcept { const auto& editorHotkey = settings.editorHotkey; std::wstring hotkeyStr = L"alt:" + std::to_wstring(editorHotkey.alt_pressed()) @@ -256,7 +256,7 @@ void Trace::SettingsChanged(const Settings& settings) noexcept TraceLoggingWrite( g_hProvider, - EventSettingsChangedKey, + EventSettingsKey, ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), TraceLoggingBoolean(settings.shiftDrag, ShiftDragKey), diff --git a/src/modules/fancyzones/lib/trace.h b/src/modules/fancyzones/FancyZonesLib/trace.h similarity index 93% rename from src/modules/fancyzones/lib/trace.h rename to src/modules/fancyzones/FancyZonesLib/trace.h index 59d42f0f25..ceca7c088b 100644 --- a/src/modules/fancyzones/lib/trace.h +++ b/src/modules/fancyzones/FancyZonesLib/trace.h @@ -20,7 +20,7 @@ public: static void QuickLayoutSwitched(bool shortcutUsed) noexcept; }; - static void SettingsChanged(const Settings& settings) noexcept; + static void SettingsTelemetry(const Settings& settings) noexcept; static void VirtualDesktopChanged() noexcept; class ZoneWindow diff --git a/src/modules/fancyzones/lib/util.cpp b/src/modules/fancyzones/FancyZonesLib/util.cpp similarity index 99% rename from src/modules/fancyzones/lib/util.cpp rename to src/modules/fancyzones/FancyZonesLib/util.cpp index f2998a2650..438f036bfd 100644 --- a/src/modules/fancyzones/lib/util.cpp +++ b/src/modules/fancyzones/FancyZonesLib/util.cpp @@ -11,7 +11,7 @@ #include #include -#include +#include // Non-Localizable strings namespace NonLocalizable diff --git a/src/modules/fancyzones/lib/util.h b/src/modules/fancyzones/FancyZonesLib/util.h similarity index 100% rename from src/modules/fancyzones/lib/util.h rename to src/modules/fancyzones/FancyZonesLib/util.h diff --git a/src/modules/fancyzones/dll/FancyZonesModule.vcxproj b/src/modules/fancyzones/FancyZonesModuleInterface/FancyZonesModuleInterface.vcxproj similarity index 94% rename from src/modules/fancyzones/dll/FancyZonesModule.vcxproj rename to src/modules/fancyzones/FancyZonesModuleInterface/FancyZonesModuleInterface.vcxproj index a65361c3b7..1bbc279586 100644 --- a/src/modules/fancyzones/dll/FancyZonesModule.vcxproj +++ b/src/modules/fancyzones/FancyZonesModuleInterface/FancyZonesModuleInterface.vcxproj @@ -1,79 +1,76 @@ - - - - - 15.0 - {48804216-2A0E-4168-A6D8-9CD068D14227} - Win32Proj - fancyzones - fancyzones - - - - DynamicLibrary - - - - - - - - - - - - - - - - - $(SolutionDir)$(Platform)\$(Configuration)\modules\FancyZones\ - - - - FANCYZONES_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - ..\;..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories) - - - $(OutDir)$(TargetName)$(TargetExt) - gdiplus.lib;dwmapi.lib;shlwapi.lib;uxtheme.lib;shcore.lib;%(AdditionalDependencies) - - - - - - - - - - Create - - - - - {caba8dfb-823b-4bf2-93ac-3f31984150d9} - - - {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} - - - {f9c68edf-ac74-4b77-9af1-005d9c9f6a99} - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - + + + + + 15.0 + {48804216-2A0E-4168-A6D8-9CD068D14227} + Win32Proj + fancyzones + FancyZonesModuleInterface + + + + DynamicLibrary + + + + + + + + + + + + + + + + + $(SolutionDir)$(Platform)\$(Configuration)\modules\FancyZones\ + + + + FANCYZONES_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + ..\;..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories) + + + $(OutDir)$(TargetName)$(TargetExt) + gdiplus.lib;dwmapi.lib;shlwapi.lib;uxtheme.lib;shcore.lib;%(AdditionalDependencies) + + + + + + + + + + Create + + + + + {caba8dfb-823b-4bf2-93ac-3f31984150d9} + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + + {f9c68edf-ac74-4b77-9af1-005d9c9f6a99} + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/modules/fancyzones/dll/FancyZonesModule.vcxproj.filters b/src/modules/fancyzones/FancyZonesModuleInterface/FancyZonesModuleInterface.vcxproj.filters similarity index 88% rename from src/modules/fancyzones/dll/FancyZonesModule.vcxproj.filters rename to src/modules/fancyzones/FancyZonesModuleInterface/FancyZonesModuleInterface.vcxproj.filters index 6c6aa5fa65..e55fab1e5f 100644 --- a/src/modules/fancyzones/dll/FancyZonesModule.vcxproj.filters +++ b/src/modules/fancyzones/FancyZonesModuleInterface/FancyZonesModuleInterface.vcxproj.filters @@ -1,39 +1,36 @@ - - - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - - - {21926bf1-03b3-482d-8f60-8bc4fbfc6564} - - - {2f10207d-d8d1-4a42-8027-8ca597b3cb23} - - - {a4241930-ecae-44e2-be82-25eff2499fcd} - - - {8d479404-964b-4eb1-8fe8-554be3e68c9b} - - - - - - - - + + + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + + + {21926bf1-03b3-482d-8f60-8bc4fbfc6564} + + + {2f10207d-d8d1-4a42-8027-8ca597b3cb23} + + + {a4241930-ecae-44e2-be82-25eff2499fcd} + + + {8d479404-964b-4eb1-8fe8-554be3e68c9b} + + + + + \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZonesModuleInterface/dllmain.cpp b/src/modules/fancyzones/FancyZonesModuleInterface/dllmain.cpp new file mode 100644 index 0000000000..0516a364d9 --- /dev/null +++ b/src/modules/fancyzones/FancyZonesModuleInterface/dllmain.cpp @@ -0,0 +1,187 @@ +#include "pch.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +// Non-localizable +const std::wstring fancyZonesPath = L"modules\\FancyZones\\PowerToys.FancyZones.exe"; + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + Trace::RegisterProvider(); + break; + + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + + case DLL_PROCESS_DETACH: + Trace::UnregisterProvider(); + break; + } + return TRUE; +} + +class FancyZonesModuleInterface : public PowertoyModuleIface +{ +public: + // Return the localized display name of the powertoy + virtual PCWSTR get_name() override + { + return app_name.c_str(); + } + + // Return the non localized key of the powertoy, this will be cached by the runner + virtual const wchar_t* get_key() override + { + return app_key.c_str(); + } + + // Return JSON with the configuration options. + // These are the settings shown on the settings page along with their current values. + virtual bool get_config(_Out_ PWSTR buffer, _Out_ int* buffer_size) override + { + return m_settings->GetConfig(buffer, buffer_size); + } + + // Passes JSON with the configuration settings for the powertoy. + // This is called when the user hits Save on the settings page. + virtual void set_config(PCWSTR config) override + { + m_settings->SetConfig(config); + } + + // Signal from the Settings editor to call a custom action. + // This can be used to spawn more complex editors. + virtual void call_custom_action(const wchar_t* action) override + { + SetEvent(m_toggleEditorEvent); + } + + // Enable the powertoy + virtual void enable() + { + Logger::info("FancyZones enabling"); + + Enable(); + } + + // Disable the powertoy + virtual void disable() + { + Logger::info("FancyZones disabling"); + + Disable(true); + } + + // Returns if the powertoy is enabled + virtual bool is_enabled() override + { + return m_enabled; + } + + // Destroy the powertoy and free memory + virtual void destroy() override + { + Disable(false); + delete this; + } + + virtual void send_settings_telemetry() override + { + Logger::info("Send settings telemetry"); + Trace::SettingsTelemetry(*m_settings->GetSettings()); + } + + FancyZonesModuleInterface() + { + app_name = GET_RESOURCE_STRING(IDS_FANCYZONES); + app_key = NonLocalizable::FancyZonesStr; + m_settings = MakeFancyZonesSettings(reinterpret_cast(&__ImageBase), FancyZonesModuleInterface::get_name(), FancyZonesModuleInterface::get_key()); + + m_toggleEditorEvent = CreateDefaultEvent(CommonSharedConstants::FANCY_ZONES_EDITOR_TOGGLE_EVENT); + } + +private: + void Enable() + { + m_enabled = true; + + // Log telemetry + Trace::FancyZones::EnableFancyZones(true); + + unsigned long powertoys_pid = GetCurrentProcessId(); + std::wstring executable_args = L""; + executable_args.append(std::to_wstring(powertoys_pid)); + + SHELLEXECUTEINFOW sei{ sizeof(sei) }; + sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI }; + sei.lpFile = fancyZonesPath.c_str(); + sei.nShow = SW_SHOWNORMAL; + sei.lpParameters = executable_args.data(); + if (ShellExecuteExW(&sei) == false) + { + Logger::error(L"Failed to start FancyZones"); + auto message = get_last_error_message(GetLastError()); + if (message.has_value()) + { + Logger::error(message.value()); + } + } + else + { + m_hProcess = sei.hProcess; + } + } + + void Disable(bool const traceEvent) + { + m_enabled = false; + // Log telemetry + + if (traceEvent) + { + Trace::FancyZones::EnableFancyZones(false); + } + + ResetEvent(m_toggleEditorEvent); + CloseHandle(m_toggleEditorEvent); + + if (m_hProcess) + { + TerminateProcess(m_hProcess, 0); + m_hProcess = nullptr; + } + } + + std::wstring app_name; + //contains the non localized key of the powertoy + std::wstring app_key; + + bool m_enabled = false; + HANDLE m_hProcess = nullptr; + + // Handle to event used to invoke FancyZones Editor + HANDLE m_toggleEditorEvent; + + winrt::com_ptr m_settings; +}; + +extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() +{ + return new FancyZonesModuleInterface(); +} diff --git a/src/modules/fancyzones/lib/packages.config b/src/modules/fancyzones/FancyZonesModuleInterface/packages.config similarity index 94% rename from src/modules/fancyzones/lib/packages.config rename to src/modules/fancyzones/FancyZonesModuleInterface/packages.config index f0ed7a6f44..fda08e21c1 100644 --- a/src/modules/fancyzones/lib/packages.config +++ b/src/modules/fancyzones/FancyZonesModuleInterface/packages.config @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/src/modules/fancyzones/dll/pch.cpp b/src/modules/fancyzones/FancyZonesModuleInterface/pch.cpp similarity index 94% rename from src/modules/fancyzones/dll/pch.cpp rename to src/modules/fancyzones/FancyZonesModuleInterface/pch.cpp index 9e6b2e073e..1d9f38c57d 100644 --- a/src/modules/fancyzones/dll/pch.cpp +++ b/src/modules/fancyzones/FancyZonesModuleInterface/pch.cpp @@ -1 +1 @@ -#include "pch.h" +#include "pch.h" diff --git a/src/modules/fancyzones/dll/pch.h b/src/modules/fancyzones/FancyZonesModuleInterface/pch.h similarity index 95% rename from src/modules/fancyzones/dll/pch.h rename to src/modules/fancyzones/FancyZonesModuleInterface/pch.h index 8b7ac0aeae..3be2007bf0 100644 --- a/src/modules/fancyzones/dll/pch.h +++ b/src/modules/fancyzones/FancyZonesModuleInterface/pch.h @@ -1,17 +1,17 @@ -#pragma once - -#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers -#include -#include -#include -#include -#include -#include -#include -#include - - -namespace winrt -{ - using namespace ::winrt; +#pragma once + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace winrt +{ + using namespace ::winrt; } \ No newline at end of file diff --git a/src/modules/fancyzones/dll/targetver.h b/src/modules/fancyzones/FancyZonesModuleInterface/targetver.h similarity index 100% rename from src/modules/fancyzones/dll/targetver.h rename to src/modules/fancyzones/FancyZonesModuleInterface/targetver.h diff --git a/src/modules/fancyzones/tests/UnitTests/FancyZones.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/FancyZones.Spec.cpp similarity index 99% rename from src/modules/fancyzones/tests/UnitTests/FancyZones.Spec.cpp rename to src/modules/fancyzones/FancyZonesTests/UnitTests/FancyZones.Spec.cpp index b8077c768b..10de1465b8 100644 --- a/src/modules/fancyzones/tests/UnitTests/FancyZones.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/FancyZones.Spec.cpp @@ -2,9 +2,9 @@ #include -#include -#include -#include +#include +#include +#include #include "util.h" diff --git a/src/modules/fancyzones/tests/UnitTests/FancyZonesSettings.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/FancyZonesSettings.Spec.cpp similarity index 97% rename from src/modules/fancyzones/tests/UnitTests/FancyZonesSettings.Spec.cpp rename to src/modules/fancyzones/FancyZonesTests/UnitTests/FancyZonesSettings.Spec.cpp index ef5c050ab4..0fc8c21773 100644 --- a/src/modules/fancyzones/tests/UnitTests/FancyZonesSettings.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/FancyZonesSettings.Spec.cpp @@ -2,8 +2,8 @@ #include #include -#include -#include +#include +#include #include using namespace Microsoft::VisualStudio::CppUnitTestFramework; @@ -524,34 +524,6 @@ namespace FancyZonesUnitTests Assert::IsTrue(flag); } - TEST_METHOD (CallbackCallCustomAction) - { - bool flag = false; - winrt::com_ptr callback = winrt::make_self(&flag); - - json::JsonObject action{}; - action.SetNamedValue(L"action_name", json::JsonValue::CreateStringValue(L"ToggledFZEditor")); - - m_settings->SetCallback(callback.get()); - m_settings->CallCustomAction(action.Stringify().c_str()); - - Assert::IsTrue(flag); - } - - TEST_METHOD (CallbackCallCustomActionNotToggle) - { - bool flag = false; - winrt::com_ptr callback = winrt::make_self(&flag); - - json::JsonObject action{}; - action.SetNamedValue(L"action_name", json::JsonValue::CreateStringValue(L"NOT_ToggledFZEditor")); - - m_settings->SetCallback(callback.get()); - m_settings->CallCustomAction(action.Stringify().c_str()); - - Assert::IsFalse(flag); - } - TEST_METHOD (CallbackGetConfig) { bool flag = false; diff --git a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/JsonHelpers.Tests.cpp similarity index 99% rename from src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp rename to src/modules/fancyzones/FancyZonesTests/UnitTests/JsonHelpers.Tests.cpp index 76aaadeb7f..323da84ca3 100644 --- a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/JsonHelpers.Tests.cpp @@ -3,10 +3,10 @@ #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include "util.h" diff --git a/src/modules/fancyzones/tests/UnitTests/UnitTests-FancyZones.rc b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests-FancyZones.rc similarity index 100% rename from src/modules/fancyzones/tests/UnitTests/UnitTests-FancyZones.rc rename to src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests-FancyZones.rc diff --git a/src/modules/fancyzones/tests/UnitTests/UnitTests.vcxproj b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj similarity index 96% rename from src/modules/fancyzones/tests/UnitTests/UnitTests.vcxproj rename to src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj index a84dee3a95..3bd886d1b2 100644 --- a/src/modules/fancyzones/tests/UnitTests/UnitTests.vcxproj +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj @@ -1,87 +1,88 @@ - - - - - 16.0 - {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9} - Win32Proj - UnitTests - NativeUnitTestProject - UnitTests-FancyZones - - - - DynamicLibrary - false - - - - - - - - - - - - - - - $(SolutionDir)$(Platform)\$(Configuration)\modules\FancyZones\ - - - - ..\..\..\..\common\Telemetry;..\..\..\..\;..\..\;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) - UNIT_TESTS;%(PreprocessorDefinitions) - - - $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) - gdiplus.lib;dwmapi.lib;shlwapi.lib;uxtheme.lib;shcore.lib;%(AdditionalDependencies) - - - - - - - - Create - - - - - - - - - - - - - - - {caba8dfb-823b-4bf2-93ac-3f31984150d9} - - - {f9c68edf-ac74-4b77-9af1-005d9c9f6a99} - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - + + + + + 16.0 + {9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9} + Win32Proj + UnitTests + NativeUnitTestProject + UnitTests-FancyZones + 10.0.17134.0 + + + + DynamicLibrary + false + + + + + + + + + + + + + + + $(SolutionDir)$(Platform)\$(Configuration)\modules\FancyZones\$(ProjectName)\ + + + + ..\..\..\..\common\Telemetry;..\..\..\..\;..\..\;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + UNIT_TESTS;%(PreprocessorDefinitions) + + + $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) + gdiplus.lib;dwmapi.lib;shlwapi.lib;uxtheme.lib;shcore.lib;%(AdditionalDependencies) + + + + + + + + Create + + + + + + + + + + + + + + + {caba8dfb-823b-4bf2-93ac-3f31984150d9} + + + {f9c68edf-ac74-4b77-9af1-005d9c9f6a99} + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/modules/fancyzones/tests/UnitTests/UnitTests.vcxproj.filters b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters similarity index 96% rename from src/modules/fancyzones/tests/UnitTests/UnitTests.vcxproj.filters rename to src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters index abf9b1e1de..3b1df00974 100644 --- a/src/modules/fancyzones/tests/UnitTests/UnitTests.vcxproj.filters +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters @@ -1,67 +1,67 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - - - Source Files - - - - - Resource Files - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + + + Resource Files + + \ No newline at end of file diff --git a/src/modules/fancyzones/tests/UnitTests/Util.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/Util.Spec.cpp similarity index 99% rename from src/modules/fancyzones/tests/UnitTests/Util.Spec.cpp rename to src/modules/fancyzones/FancyZonesTests/UnitTests/Util.Spec.cpp index 1ef608d6db..6347c390b0 100644 --- a/src/modules/fancyzones/tests/UnitTests/Util.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/Util.Spec.cpp @@ -1,6 +1,6 @@ #include "pch.h" #include "Util.h" -#include "lib\util.h" +#include "FancyZonesLib\util.h" using namespace Microsoft::VisualStudio::CppUnitTestFramework; diff --git a/src/modules/fancyzones/tests/UnitTests/Util.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/Util.cpp similarity index 100% rename from src/modules/fancyzones/tests/UnitTests/Util.cpp rename to src/modules/fancyzones/FancyZonesTests/UnitTests/Util.cpp diff --git a/src/modules/fancyzones/tests/UnitTests/Util.h b/src/modules/fancyzones/FancyZonesTests/UnitTests/Util.h similarity index 94% rename from src/modules/fancyzones/tests/UnitTests/Util.h rename to src/modules/fancyzones/FancyZonesTests/UnitTests/Util.h index 4696fc5927..b7956a2f83 100644 --- a/src/modules/fancyzones/tests/UnitTests/Util.h +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/Util.h @@ -1,77 +1,77 @@ -#pragma once - -#include "lib/FancyZonesDataTypes.h" - -namespace CustomAssert -{ - static void AreEqual(const RECT& r1, const RECT& r2) - { - const bool equal = ((r1.left == r2.left) && (r1.right == r2.right) && (r1.top == r2.top) && (r1.bottom == r2.bottom)); - Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(equal); - } - - static void AreEqual(GUID g1, GUID g2) - { - Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(g1 == g2); - } - - static void AreEqual(FancyZonesDataTypes::ZoneSetLayoutType t1, FancyZonesDataTypes::ZoneSetLayoutType t2) - { - Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(t1 == t2); - } - - static void AreEqual(const std::vector>& a1, const std::vector>& a2) - { - Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(a1.size() == a2.size()); - for (size_t i = 0; i < a1.size(); i++) - { - Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(a1[i].first == a2[i].first); - } - } -} - -namespace Mocks -{ - static HWND Window() - { - static UINT_PTR s_nextWindow = 0; - return reinterpret_cast(++s_nextWindow); - } - - static HMONITOR Monitor() - { - static UINT_PTR s_nextMonitor = 0; - return reinterpret_cast(++s_nextMonitor); - } - - static HINSTANCE Instance() - { - static UINT_PTR s_nextInstance = 0; - return reinterpret_cast(++s_nextInstance); - } - - HWND WindowCreate(HINSTANCE hInst); -} - -namespace Helpers -{ - std::wstring GuidToString(const GUID& guid); - std::wstring CreateGuidString(); - std::optional StringToGuid(const std::wstring& str); -} - -template<> -std::wstring Microsoft::VisualStudio::CppUnitTestFramework::ToString(const std::vector& vec) -{ - std::wstring str = L"{"; - for (size_t i = 0; i < vec.size(); i++) - { - str += std::to_wstring(vec[i]); - if (i != vec.size() - 1) - { - str += L","; - } - } - str += L"}"; - return str; -} +#pragma once + +#include "FancyZonesLib/FancyZonesDataTypes.h" + +namespace CustomAssert +{ + static void AreEqual(const RECT& r1, const RECT& r2) + { + const bool equal = ((r1.left == r2.left) && (r1.right == r2.right) && (r1.top == r2.top) && (r1.bottom == r2.bottom)); + Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(equal); + } + + static void AreEqual(GUID g1, GUID g2) + { + Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(g1 == g2); + } + + static void AreEqual(FancyZonesDataTypes::ZoneSetLayoutType t1, FancyZonesDataTypes::ZoneSetLayoutType t2) + { + Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(t1 == t2); + } + + static void AreEqual(const std::vector>& a1, const std::vector>& a2) + { + Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(a1.size() == a2.size()); + for (size_t i = 0; i < a1.size(); i++) + { + Microsoft::VisualStudio::CppUnitTestFramework::Assert::IsTrue(a1[i].first == a2[i].first); + } + } +} + +namespace Mocks +{ + static HWND Window() + { + static UINT_PTR s_nextWindow = 0; + return reinterpret_cast(++s_nextWindow); + } + + static HMONITOR Monitor() + { + static UINT_PTR s_nextMonitor = 0; + return reinterpret_cast(++s_nextMonitor); + } + + static HINSTANCE Instance() + { + static UINT_PTR s_nextInstance = 0; + return reinterpret_cast(++s_nextInstance); + } + + HWND WindowCreate(HINSTANCE hInst); +} + +namespace Helpers +{ + std::wstring GuidToString(const GUID& guid); + std::wstring CreateGuidString(); + std::optional StringToGuid(const std::wstring& str); +} + +template<> +std::wstring Microsoft::VisualStudio::CppUnitTestFramework::ToString(const std::vector& vec) +{ + std::wstring str = L"{"; + for (size_t i = 0; i < vec.size(); i++) + { + str += std::to_wstring(vec[i]); + if (i != vec.size() - 1) + { + str += L","; + } + } + str += L"}"; + return str; +} diff --git a/src/modules/fancyzones/tests/UnitTests/Zone.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/Zone.Spec.cpp similarity index 92% rename from src/modules/fancyzones/tests/UnitTests/Zone.Spec.cpp rename to src/modules/fancyzones/FancyZonesTests/UnitTests/Zone.Spec.cpp index b2b5750429..f66f27b2b4 100644 --- a/src/modules/fancyzones/tests/UnitTests/Zone.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/Zone.Spec.cpp @@ -1,46 +1,46 @@ -#include "pch.h" -#include "lib\Zone.h" -#include "lib\Settings.h" - -#include "Util.h" - -using namespace Microsoft::VisualStudio::CppUnitTestFramework; - -namespace FancyZonesUnitTests -{ - TEST_CLASS(ZoneUnitTests) - { - private: - RECT m_zoneRect{ 10, 10, 200, 200 }; - HINSTANCE m_hInst{}; - - TEST_METHOD_INITIALIZE(Init) - { - m_hInst = (HINSTANCE)GetModuleHandleW(nullptr); - } - - public: - TEST_METHOD(TestCreateZone) - { - winrt::com_ptr zone = MakeZone(m_zoneRect, 1); - Assert::IsNotNull(&zone); - CustomAssert::AreEqual(m_zoneRect, zone->GetZoneRect()); - } - - TEST_METHOD(TestCreateZoneZeroRect) - { - RECT zoneRect{ 0, 0, 0, 0 }; - winrt::com_ptr zone = MakeZone(zoneRect, 1); - Assert::IsNotNull(&zone); - CustomAssert::AreEqual(zoneRect, zone->GetZoneRect()); - } - - TEST_METHOD(GetSetId) - { - constexpr size_t zoneId = 123; - winrt::com_ptr zone = MakeZone(m_zoneRect, zoneId); - - Assert::AreEqual(zone->Id(), zoneId); - } - }; -} +#include "pch.h" +#include "FancyZonesLib\Zone.h" +#include "FancyZonesLib\Settings.h" + +#include "Util.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +namespace FancyZonesUnitTests +{ + TEST_CLASS(ZoneUnitTests) + { + private: + RECT m_zoneRect{ 10, 10, 200, 200 }; + HINSTANCE m_hInst{}; + + TEST_METHOD_INITIALIZE(Init) + { + m_hInst = (HINSTANCE)GetModuleHandleW(nullptr); + } + + public: + TEST_METHOD(TestCreateZone) + { + winrt::com_ptr zone = MakeZone(m_zoneRect, 1); + Assert::IsNotNull(&zone); + CustomAssert::AreEqual(m_zoneRect, zone->GetZoneRect()); + } + + TEST_METHOD(TestCreateZoneZeroRect) + { + RECT zoneRect{ 0, 0, 0, 0 }; + winrt::com_ptr zone = MakeZone(zoneRect, 1); + Assert::IsNotNull(&zone); + CustomAssert::AreEqual(zoneRect, zone->GetZoneRect()); + } + + TEST_METHOD(GetSetId) + { + constexpr size_t zoneId = 123; + winrt::com_ptr zone = MakeZone(m_zoneRect, zoneId); + + Assert::AreEqual(zone->Id(), zoneId); + } + }; +} diff --git a/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/ZoneSet.Spec.cpp similarity index 97% rename from src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp rename to src/modules/fancyzones/FancyZonesTests/UnitTests/ZoneSet.Spec.cpp index fa8dad8d9e..958b7f840b 100644 --- a/src/modules/fancyzones/tests/UnitTests/ZoneSet.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/ZoneSet.Spec.cpp @@ -1,1128 +1,1127 @@ -#include "pch.h" -#include "lib\FancyZonesData.h" -#include "lib\FancyZonesDataTypes.h" -#include "lib\JsonHelpers.h" -#include "lib\VirtualDesktopUtils.h" -#include "lib\ZoneSet.h" - -#include - -#include "Util.h" -#include -#include - -using namespace Microsoft::VisualStudio::CppUnitTestFramework; -using namespace FancyZonesDataTypes; - -namespace FancyZonesUnitTests -{ - TEST_CLASS (ZoneSetUnitTests) - { - GUID m_id; - const ZoneSetLayoutType m_layoutType = ZoneSetLayoutType::Custom; - - winrt::com_ptr m_set; - - TEST_METHOD_INITIALIZE(Init) - { - auto hres = CoCreateGuid(&m_id); - Assert::AreEqual(S_OK, hres); - - ZoneSetConfig m_config = ZoneSetConfig(m_id, m_layoutType, Mocks::Monitor(), DefaultValues::SensitivityRadius, Settings::OverlappingZonesAlgorithm::Smallest); - m_set = MakeZoneSet(m_config); - } - - void compareZones(const winrt::com_ptr& expected, const winrt::com_ptr& actual) - { - Assert::AreEqual(expected->Id(), actual->Id()); - Assert::AreEqual(expected->GetZoneRect().left, actual->GetZoneRect().left); - Assert::AreEqual(expected->GetZoneRect().right, actual->GetZoneRect().right); - Assert::AreEqual(expected->GetZoneRect().top, actual->GetZoneRect().top); - Assert::AreEqual(expected->GetZoneRect().bottom, actual->GetZoneRect().bottom); - } - - public: - TEST_METHOD (TestCreateZoneSet) - { - Assert::IsNotNull(&m_set); - CustomAssert::AreEqual(m_set->Id(), m_id); - CustomAssert::AreEqual(m_set->LayoutType(), m_layoutType); - } - - TEST_METHOD (TestCreateZoneSetGuidEmpty) - { - GUID zoneSetId{}; - ZoneSetConfig config(zoneSetId, m_layoutType, Mocks::Monitor(), DefaultValues::SensitivityRadius); - winrt::com_ptr set = MakeZoneSet(config); - - Assert::IsNotNull(&set); - CustomAssert::AreEqual(set->Id(), zoneSetId); - CustomAssert::AreEqual(set->LayoutType(), m_layoutType); - } - - TEST_METHOD (TestCreateZoneSetMonitorEmpty) - { - ZoneSetConfig config(m_id, m_layoutType, nullptr, DefaultValues::SensitivityRadius); - winrt::com_ptr set = MakeZoneSet(config); - Assert::IsNotNull(&set); - CustomAssert::AreEqual(set->Id(), m_id); - CustomAssert::AreEqual(set->LayoutType(), m_layoutType); - } - - TEST_METHOD (TestCreateZoneSetKeyEmpty) - { - ZoneSetConfig config(m_id, m_layoutType, Mocks::Monitor(), DefaultValues::SensitivityRadius); - winrt::com_ptr set = MakeZoneSet(config); - Assert::IsNotNull(&set); - CustomAssert::AreEqual(set->Id(), m_id); - CustomAssert::AreEqual(set->LayoutType(), m_layoutType); - } - - TEST_METHOD (EmptyZones) - { - auto zones = m_set->GetZones(); - Assert::AreEqual((size_t)0, zones.size()); - } - - TEST_METHOD (AddOne) - { - constexpr size_t zoneId = 0; - winrt::com_ptr zone = MakeZone({ 0, 0, 100, 100 }, zoneId); - Assert::IsNotNull(zone.get()); - m_set->AddZone(zone); - auto zones = m_set->GetZones(); - Assert::AreEqual((size_t)1, zones.size()); - compareZones(zone, zones[zoneId]); - Assert::AreEqual(zoneId, zones[zoneId]->Id()); - } - - TEST_METHOD (AddManyEqual) - { - for (size_t i = 0; i < 1024; i++) - { - size_t zoneId = i; - winrt::com_ptr zone = MakeZone({ 0, 0, 100, 100 }, zoneId); - Assert::IsNotNull(zone.get()); - m_set->AddZone(zone); - auto zones = m_set->GetZones(); - Assert::AreEqual(i + 1, zones.size()); - compareZones(zone, zones[zoneId]); - Assert::AreEqual(zoneId, zones[zoneId]->Id()); - } - } - - TEST_METHOD (AddManyDifferent) - { - for (size_t i = 0; i < 1024; i++) - { - size_t zoneId = i; - int left = rand() % 10; - int top = rand() % 10; - int right = left + 1 + rand() % 100; - int bottom = top + 1 + rand() % 100; - winrt::com_ptr zone = MakeZone({ left, top, right, bottom }, zoneId); - Assert::IsNotNull(zone.get()); - m_set->AddZone(zone); - auto zones = m_set->GetZones(); - Assert::AreEqual(i + 1, zones.size()); - compareZones(zone, zones[zoneId]); - Assert::AreEqual(zoneId, zones[zoneId]->Id()); - } - } - - TEST_METHOD (MakeZoneFromZeroRect) - { - winrt::com_ptr zone = MakeZone({ 0, 0, 0, 0 }, 1); - Assert::IsNotNull(zone.get()); - } - - TEST_METHOD (MakeZoneFromInvalidRectWidth) - { - winrt::com_ptr zone = MakeZone({ 100, 100, 99, 101 }, 1); - Assert::IsNull(zone.get()); - } - - TEST_METHOD (MakeZoneFromInvalidRectHeight) - { - winrt::com_ptr zone = MakeZone({ 100, 100, 101, 99 }, 1); - Assert::IsNull(zone.get()); - } - - TEST_METHOD (MakeZoneFromInvalidRectCoords) - { - const int invalid = ZoneConstants::MAX_NEGATIVE_SPACING - 1; - winrt::com_ptr zone = MakeZone({ invalid, invalid, invalid, invalid }, 1); - Assert::IsNull(zone.get()); - } - - TEST_METHOD (ZoneFromPointEmpty) - { - auto actual = m_set->ZonesFromPoint(POINT{ 0, 0 }); - Assert::IsTrue(actual.size() == 0); - } - - TEST_METHOD (ZoneFromPointInner) - { - const int left = 0, top = 0, right = 100, bottom = 100; - winrt::com_ptr expected = MakeZone({ left, top, right, bottom }, 1); - m_set->AddZone(expected); - - for (int i = left + 1; i < right; i++) - { - for (int j = top + 1; j < bottom; j++) - { - auto actual = m_set->ZonesFromPoint(POINT{ i, j }); - Assert::IsTrue(actual.size() == 1); - compareZones(expected, m_set->GetZones()[actual[0]]); - } - } - } - - TEST_METHOD (ZoneFromPointBorder) - { - const int left = 0, top = 0, right = 100, bottom = 100; - winrt::com_ptr expected = MakeZone({ left, top, right, bottom }, 1); - m_set->AddZone(expected); - - for (int i = left; i < right; i++) - { - auto actual = m_set->ZonesFromPoint(POINT{ i, top }); - Assert::IsTrue(actual.size() == 1); - compareZones(expected, m_set->GetZones()[actual[0]]); - } - - for (int i = top; i < bottom; i++) - { - auto actual = m_set->ZonesFromPoint(POINT{ left, i }); - Assert::IsTrue(actual.size() == 1); - compareZones(expected, m_set->GetZones()[actual[0]]); - } - - //bottom and right borders considered to be outside - for (int i = left; i < right; i++) - { - auto actual = m_set->ZonesFromPoint(POINT{ i, bottom }); - Assert::IsTrue(actual.size() == 0); - } - - for (int i = top; i < bottom; i++) - { - auto actual = m_set->ZonesFromPoint(POINT{ right, i }); - Assert::IsTrue(actual.size() == 0); - } - } - - TEST_METHOD (ZoneFromPointOuter) - { - const int left = 0, top = 0, right = 100, bottom = 100; - winrt::com_ptr zone = MakeZone({ left, top, right, bottom }, 1); - m_set->AddZone(zone); - - auto actual = m_set->ZonesFromPoint(POINT{ 200, 200 }); - Assert::IsTrue(actual.size() == 0); - } - - TEST_METHOD (ZoneFromPointOverlapping) - { - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 1); - m_set->AddZone(zone1); - winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }, 2); - m_set->AddZone(zone2); - winrt::com_ptr zone3 = MakeZone({ 10, 10, 150, 150 }, 3); - m_set->AddZone(zone3); - winrt::com_ptr zone4 = MakeZone({ 10, 10, 50, 50 }, 4); - m_set->AddZone(zone4); - - // zone4 is expected because it's the smallest one, and it's considered to be inside - // since Multizones support - - auto actual = m_set->ZonesFromPoint(POINT{ 50, 50 }); - Assert::IsTrue(actual.size() == 1); - compareZones(zone4, m_set->GetZones()[actual[0]]); - } - - TEST_METHOD (ZoneFromPointMultizoneHorizontal) - { - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 1); - m_set->AddZone(zone1); - winrt::com_ptr zone2 = MakeZone({ 100, 0, 200, 100 }, 2); - m_set->AddZone(zone2); - winrt::com_ptr zone3 = MakeZone({ 0, 100, 100, 200 }, 3); - m_set->AddZone(zone3); - winrt::com_ptr zone4 = MakeZone({ 100, 100, 200, 200 }, 4); - m_set->AddZone(zone4); - - auto actual = m_set->ZonesFromPoint(POINT{ 50, 100 }); - Assert::IsTrue(actual.size() == 2); - compareZones(zone1, m_set->GetZones()[actual[0]]); - compareZones(zone3, m_set->GetZones()[actual[1]]); - } - - TEST_METHOD (ZoneFromPointMultizoneVertical) - { - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 1); - m_set->AddZone(zone1); - winrt::com_ptr zone2 = MakeZone({ 100, 0, 200, 100 }, 2); - m_set->AddZone(zone2); - winrt::com_ptr zone3 = MakeZone({ 0, 100, 100, 200 }, 3); - m_set->AddZone(zone3); - winrt::com_ptr zone4 = MakeZone({ 100, 100, 200, 200 }, 4); - m_set->AddZone(zone4); - - auto actual = m_set->ZonesFromPoint(POINT{ 100, 50 }); - Assert::IsTrue(actual.size() == 2); - compareZones(zone1, m_set->GetZones()[actual[0]]); - compareZones(zone2, m_set->GetZones()[actual[1]]); - } - - TEST_METHOD (ZoneFromPointMultizoneQuad) - { - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 1); - m_set->AddZone(zone1); - winrt::com_ptr zone2 = MakeZone({ 100, 0, 200, 100 }, 2); - m_set->AddZone(zone2); - winrt::com_ptr zone3 = MakeZone({ 0, 100, 100, 200 }, 3); - m_set->AddZone(zone3); - winrt::com_ptr zone4 = MakeZone({ 100, 100, 200, 200 }, 4); - m_set->AddZone(zone4); - - auto actual = m_set->ZonesFromPoint(POINT{ 100, 100 }); - Assert::IsTrue(actual.size() == 4); - compareZones(zone1, m_set->GetZones()[actual[0]]); - compareZones(zone2, m_set->GetZones()[actual[1]]); - compareZones(zone3, m_set->GetZones()[actual[2]]); - compareZones(zone4, m_set->GetZones()[actual[3]]); - } - - TEST_METHOD (ZoneIndexFromWindowUnknown) - { - winrt::com_ptr zone = MakeZone({ 0, 0, 100, 100 }, 1); - HWND window = Mocks::Window(); - HWND zoneWindow = Mocks::Window(); - m_set->AddZone(zone); - m_set->MoveWindowIntoZoneByIndexSet(window, zoneWindow, { 0 }); - - auto actual = m_set->GetZoneIndexSetFromWindow(Mocks::Window()); - Assert::IsTrue(std::vector{} == actual); - } - - TEST_METHOD (ZoneIndexFromWindowNull) - { - winrt::com_ptr zone = MakeZone({ 0, 0, 100, 100 }, 1); - HWND window = Mocks::Window(); - HWND zoneWindow = Mocks::Window(); - m_set->AddZone(zone); - m_set->MoveWindowIntoZoneByIndexSet(window, zoneWindow, { 0 }); - - auto actual = m_set->GetZoneIndexSetFromWindow(nullptr); - Assert::IsTrue(std::vector{} == actual); - } - - TEST_METHOD (MoveWindowIntoZoneByIndex) - { - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 1); - winrt::com_ptr zone2 = MakeZone({ 0, 0, 100, 100 }, 2); - winrt::com_ptr zone3 = MakeZone({ 0, 0, 100, 100 }, 3); - m_set->AddZone(zone1); - m_set->AddZone(zone2); - m_set->AddZone(zone3); - - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 1); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveWindowIntoZoneByIndexWithNoZones) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); - } - - TEST_METHOD (MoveWindowIntoZoneByIndexWithInvalidIndex) - { - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 1); - winrt::com_ptr zone2 = MakeZone({ 0, 0, 100, 100 }, 2); - winrt::com_ptr zone3 = MakeZone({ 0, 0, 100, 100 }, 3); - m_set->AddZone(zone1); - m_set->AddZone(zone2); - m_set->AddZone(zone3); - - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 100); - Assert::IsTrue(std::vector{} == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveWindowIntoZoneByIndexSeveralTimesSameWindow) - { - // Add a couple of zones. - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); - winrt::com_ptr zone2 = MakeZone({ 1, 1, 101, 101 }, 1); - winrt::com_ptr zone3 = MakeZone({ 2, 2, 102, 102 }, 2); - m_set->AddZone(zone1); - m_set->AddZone(zone2); - m_set->AddZone(zone3); - - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); - - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 1); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); - - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 2); - Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveWindowIntoZoneByIndexSeveralTimesSameIndex) - { - // Add a couple of zones. - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); - winrt::com_ptr zone2 = MakeZone({ 1, 1, 101, 101 }, 1); - winrt::com_ptr zone3 = MakeZone({ 2, 2, 102, 102 }, 2); - m_set->AddZone(zone1); - m_set->AddZone(zone2); - m_set->AddZone(zone3); - - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveWindowIntoZoneByPointEmpty) - { - m_set->MoveWindowIntoZoneByPoint(Mocks::Window(), Mocks::Window(), POINT{ 0, 0 }); - } - - TEST_METHOD (MoveWindowIntoZoneByPointOuterPoint) - { - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 1); - m_set->AddZone(zone1); - - auto window = Mocks::Window(); - m_set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 200, 200 }); - - Assert::IsTrue(std::vector{} == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveWindowIntoZoneByPointInnerPoint) - { - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); - m_set->AddZone(zone1); - - auto window = Mocks::Window(); - m_set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 50, 50 }); - - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveWindowIntoZoneByPointInnerPointOverlappingZones) - { - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); - winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }, 1); - m_set->AddZone(zone1); - m_set->AddZone(zone2); - - auto window = Mocks::Window(); - m_set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 50, 50 }); - - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveWindowIntoZoneByPointDropAddWindow) - { - const auto window = Mocks::Window(); - const auto zoneWindow = Mocks::Window(); - - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); - winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }, 1); - - m_set->AddZone(zone1); - m_set->AddZone(zone2); - - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); - - m_set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 50, 50 }); - - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveWindowIntoZoneByPointDropAddWindowToSameZone) - { - const auto window = Mocks::Window(); - const auto zoneWindow = Mocks::Window(); - - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); - winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }, 1); - - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 1); - - m_set->AddZone(zone1); - m_set->AddZone(zone2); - - m_set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 50, 50 }); - - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveWindowIntoZoneByPointSeveralZonesWithSameWindow) - { - const auto window = Mocks::Window(); - const auto zoneWindow = Mocks::Window(); - - winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); - winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }, 1); - winrt::com_ptr zone3 = MakeZone({ 20, 20, 80, 80 }, 2); - - m_set->AddZone(zone1); - m_set->AddZone(zone2); - m_set->AddZone(zone3); - - m_set->MoveWindowIntoZoneByIndexSet(window, Mocks::Window(), { 0, 1, 2 }); - - m_set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 50, 50 }); - - Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); - } - }; - - // MoveWindowIntoZoneByDirectionAndIndex is complicated enough to warrant it's own test class - TEST_CLASS (ZoneSetsMoveWindowIntoZoneByDirectionUnitTests) - { - winrt::com_ptr m_set; - winrt::com_ptr m_zone1; - winrt::com_ptr m_zone2; - winrt::com_ptr m_zone3; - - TEST_METHOD_INITIALIZE(Initialize) - { - ZoneSetConfig config({}, ZoneSetLayoutType::Custom, Mocks::Monitor(), DefaultValues::SensitivityRadius); - m_set = MakeZoneSet(config); - - // Add a couple of zones. - m_zone1 = MakeZone({ 0, 0, 100, 100 }, 0); - m_zone2 = MakeZone({ 0, 0, 100, 100 }, 1); - m_zone3 = MakeZone({ 0, 0, 100, 100 }, 2); - m_set->AddZone(m_zone1); - m_set->AddZone(m_zone2); - m_set->AddZone(m_zone3); - } - - TEST_METHOD (EmptyZonesLeft) - { - ZoneSetConfig config({}, ZoneSetLayoutType::Custom, Mocks::Monitor(), DefaultValues::SensitivityRadius); - auto set = MakeZoneSet(config); - - set->MoveWindowIntoZoneByDirectionAndIndex(Mocks::Window(), Mocks::Window(), VK_LEFT, true); - } - - TEST_METHOD (EmptyZonesRight) - { - ZoneSetConfig config({}, ZoneSetLayoutType::Custom, Mocks::Monitor(), DefaultValues::SensitivityRadius); - auto set = MakeZoneSet(config); - - set->MoveWindowIntoZoneByDirectionAndIndex(Mocks::Window(), Mocks::Window(), VK_RIGHT, true); - } - - TEST_METHOD (MoveRightNoZones) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveLeftNoZones) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); - Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveRightTwice) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveLeftTwice) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveRightMoreThanZonesCount) - { - HWND window = Mocks::Window(); - for (int i = 0; i <= m_set->GetZones().size(); i++) - { - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); - } - - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveLeftMoreThanZonesCount) - { - HWND window = Mocks::Window(); - for (int i = 0; i <= m_set->GetZones().size(); i++) - { - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); - } - - Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveWindowIntoZoneByDirectionRight) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); - - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); - Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveRightWithSameWindowAdded) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndexSet(window, Mocks::Window(), { 0, 1 }); - - Assert::IsTrue(std::vector{ 0, 1 } == m_set->GetZoneIndexSetFromWindow(window)); - - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); - - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); - Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveRightWithDifferentWindowsAdded) - { - HWND window1 = Mocks::Window(); - HWND window2 = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window1, Mocks::Window(), { 0 }); - m_set->MoveWindowIntoZoneByIndex(window2, Mocks::Window(), { 1 }); - - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window1)); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window2)); - - m_set->MoveWindowIntoZoneByDirectionAndIndex(window1, Mocks::Window(), VK_RIGHT, true); - - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window1)); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window2)); - - m_set->MoveWindowIntoZoneByDirectionAndIndex(window1, Mocks::Window(), VK_RIGHT, true); - - Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window1)); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window2)); - } - - TEST_METHOD (MoveWindowIntoZoneByDirectionLeft) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 2); - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); - - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveLeftWithSameWindowAdded) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndexSet(window, Mocks::Window(), { 1, 2 }); - - Assert::IsTrue(std::vector{ 1, 2 } == m_set->GetZoneIndexSetFromWindow(window)); - - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); - - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); - Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveLeftWithDifferentWindowsAdded) - { - HWND window1 = Mocks::Window(); - HWND window2 = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window1, Mocks::Window(), 1); - m_set->MoveWindowIntoZoneByIndex(window2, Mocks::Window(), 2); - - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window1)); - Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window2)); - - m_set->MoveWindowIntoZoneByDirectionAndIndex(window2, Mocks::Window(), VK_LEFT, true); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window1)); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window2)); - - m_set->MoveWindowIntoZoneByDirectionAndIndex(window2, Mocks::Window(), VK_LEFT, true); - Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window1)); - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window2)); - } - - TEST_METHOD (MoveWindowIntoZoneByDirectionWrapAroundRight) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 2); - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveWindowIntoZoneByDirectionWrapAroundLeft) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); - Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); - } - - TEST_METHOD (MoveSecondWindowIntoSameZone) - { - HWND window1 = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window1, Mocks::Window(), 0); - - HWND window2 = Mocks::Window(); - m_set->MoveWindowIntoZoneByDirectionAndIndex(window2, Mocks::Window(), VK_RIGHT, true); - - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window1)); - Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window2)); - } - - TEST_METHOD (MoveRightMoreThanZoneCountReturnsFalse) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); - for (size_t i = 0; i < m_set->GetZones().size() - 1; ++i) - { - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, false); - } - bool moreZonesInLayout = m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, false); - Assert::IsFalse(moreZonesInLayout); - } - - TEST_METHOD (MoveLeftMoreThanZoneCountReturnsFalse) - { - HWND window = Mocks::Window(); - m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 2); - for (size_t i = 0; i < m_set->GetZones().size() - 1; ++i) - { - m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, false); - } - bool moreZonesInLayout = m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, false); - Assert::IsFalse(moreZonesInLayout); - } - }; - - TEST_CLASS (ZoneSetCalculateZonesUnitTests) - { - GUID m_id; - const ZoneSetLayoutType m_layoutType = ZoneSetLayoutType::Custom; - const PCWSTR m_resolutionKey = L"WorkAreaIn"; - winrt::com_ptr m_set; - - HMONITOR m_monitor; - const std::array m_popularMonitors{ - MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1024, .bottom = 768 } }, - MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1280, .bottom = 720 } }, - MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1280, .bottom = 800 } }, - MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1280, .bottom = 1024 } }, - MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1366, .bottom = 768 } }, - MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1440, .bottom = 900 } }, - MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1536, .bottom = 864 } }, - MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1600, .bottom = 900 } }, - MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1920, .bottom = 1080 } } - }; - - MONITORINFO m_monitorInfo; - - const std::wstring m_path = PTSettingsHelper::get_module_save_folder_location(L"FancyZones") + L"\\" + std::wstring(L"testzones.json"); - - TEST_METHOD_INITIALIZE(Init) - { - auto hres = CoCreateGuid(&m_id); - Assert::AreEqual(S_OK, hres); - - m_monitor = MonitorFromPoint(POINT{ 0, 0 }, MONITOR_DEFAULTTOPRIMARY); - - ZoneSetConfig m_config = ZoneSetConfig(m_id, m_layoutType, m_monitor, DefaultValues::SensitivityRadius); - m_set = MakeZoneSet(m_config); - } - - TEST_METHOD_CLEANUP(Cleanup) - { - std::filesystem::remove(m_path); - } - - void checkZones(const winrt::com_ptr& set, ZoneSetLayoutType type, size_t expectedCount, MONITORINFO monitorInfo) - { - auto zones = set->GetZones(); - Assert::AreEqual(expectedCount, zones.size()); - - int zoneId = 0; - for (const auto& zone : zones) - { - Assert::IsTrue(set->IsZoneEmpty(zoneId)); - - const auto& zoneRect = zone.second->GetZoneRect(); - Assert::IsTrue(zoneRect.left >= 0, L"left border is less than zero"); - Assert::IsTrue(zoneRect.top >= 0, L"top border is less than zero"); - - Assert::IsTrue(zoneRect.left < zoneRect.right, L"rect.left >= rect.right"); - Assert::IsTrue(zoneRect.top < zoneRect.bottom, L"rect.top >= rect.bottom"); - - if (type != ZoneSetLayoutType::Focus) - { - Assert::IsTrue(zoneRect.right <= monitorInfo.rcWork.right, L"right border is bigger than monitor work space"); - Assert::IsTrue(zoneRect.bottom <= monitorInfo.rcWork.bottom, L"bottom border is bigger than monitor work space"); - } - - zoneId++; - } - } - - public: - TEST_METHOD (ValidValues) - { - const int spacing = 10; - const int zoneCount = 10; - - for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) - { - ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); - - for (const auto& monitorInfo : m_popularMonitors) - { - auto set = MakeZoneSet(m_config); - auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); - Assert::IsTrue(result); - checkZones(set, static_cast(type), zoneCount, monitorInfo); - } - } - } - TEST_METHOD (InvalidMonitorInfo) - { - const int spacing = 10; - const int zoneCount = 10; - - for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) - { - ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); - auto set = MakeZoneSet(m_config); - - MONITORINFO info{}; - auto result = set->CalculateZones(info.rcWork, zoneCount, spacing); - Assert::IsFalse(result); - } - } - - TEST_METHOD (ZeroSpacing) - { - const int spacing = 0; - const int zoneCount = 10; - - for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) - { - ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); - - for (const auto& monitorInfo : m_popularMonitors) - { - auto set = MakeZoneSet(m_config); - auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); - Assert::IsTrue(result); - checkZones(set, static_cast(type), zoneCount, monitorInfo); - } - } - } - - TEST_METHOD (LargeNegativeSpacing) - { - const int spacing = ZoneConstants::MAX_NEGATIVE_SPACING - 1; - const int zoneCount = 10; - - for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) - { - ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); - auto set = MakeZoneSet(m_config); - - for (const auto& monitorInfo : m_popularMonitors) - { - auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); - if (type == static_cast(ZoneSetLayoutType::Focus)) - { - //Focus doesn't depends on spacing - Assert::IsTrue(result); - } - else - { - Assert::IsFalse(result); - } - } - } - } - - TEST_METHOD (HorizontallyBigSpacing) - { - const int zoneCount = 10; - - for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) - { - ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); - auto set = MakeZoneSet(m_config); - - for (const auto& monitorInfo : m_popularMonitors) - { - const int spacing = monitorInfo.rcWork.right; - auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); - if (type == static_cast(ZoneSetLayoutType::Focus)) - { - //Focus doesn't depends on spacing - Assert::IsTrue(result); - } - else - { - Assert::IsFalse(result); - } - } - } - } - - TEST_METHOD (VerticallyBigSpacing) - { - const int zoneCount = 10; - - for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) - { - ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); - auto set = MakeZoneSet(m_config); - - for (const auto& monitorInfo : m_popularMonitors) - { - const int spacing = monitorInfo.rcWork.bottom; - auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); - if (type == static_cast(ZoneSetLayoutType::Focus)) - { - //Focus doesn't depends on spacing - Assert::IsTrue(result); - } - else - { - Assert::IsFalse(result); - } - } - } - } - - TEST_METHOD (ZeroZoneCount) - { - const int spacing = 10; - const int zoneCount = 0; - - for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) - { - ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); - auto set = MakeZoneSet(m_config); - - for (const auto& monitorInfo : m_popularMonitors) - { - auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); - Assert::IsFalse(result); - } - } - } - - TEST_METHOD (BigZoneCount) - { - const int spacing = 1; - - for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) - { - const int spacing = 10; - const int zoneCount = 40; //editor limit - - ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); - - for (const auto& monitorInfo : m_popularMonitors) - { - auto set = MakeZoneSet(m_config); - auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); - Assert::IsTrue(result); - checkZones(set, static_cast(type), zoneCount, monitorInfo); - } - } - } - - TEST_METHOD (CustomZonesFromNonexistentFile) - { - const int spacing = 10; - const int zoneCount = 0; - - //be sure that file does not exist - if (std::filesystem::exists(m_path)) - { - std::filesystem::remove(m_path); - } - - ZoneSetConfig m_config = ZoneSetConfig(m_id, ZoneSetLayoutType::Custom, m_monitor, DefaultValues::SensitivityRadius); - auto set = MakeZoneSet(m_config); - - for (const auto& monitorInfo : m_popularMonitors) - { - auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); - Assert::IsFalse(result); - } - } - - TEST_METHOD (CustomZoneFromEmptyFile) - { - const int spacing = 10; - const int zoneCount = 0; - - Assert::IsTrue(std::filesystem::create_directories(m_path)); - Assert::IsTrue(std::filesystem::exists(m_path)); - - ZoneSetConfig m_config = ZoneSetConfig(m_id, ZoneSetLayoutType::Custom, m_monitor, DefaultValues::SensitivityRadius); - auto set = MakeZoneSet(m_config); - - for (const auto& monitorInfo : m_popularMonitors) - { - auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); - Assert::IsFalse(result); - } - } - - TEST_METHOD (CustomZoneFromInvalidCanvasLayoutInfo) - { - const std::wstring uuid = L"uuid"; - const CanvasLayoutInfo info{ -1, 100, { CanvasLayoutInfo::Rect{ -10, -10, 100, 100 }, CanvasLayoutInfo::Rect{ 50, 50, 150, 150 } } }; - JSONHelpers::CustomZoneSetJSON expected{ uuid, CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info } }; - json::to_file(m_path, JSONHelpers::CustomZoneSetJSON::ToJson(expected)); - Assert::IsTrue(std::filesystem::exists(m_path)); - - const int spacing = 10; - const int zoneCount = static_cast(info.zones.size()); - - ZoneSetConfig m_config = ZoneSetConfig(m_id, ZoneSetLayoutType::Custom, m_monitor, DefaultValues::SensitivityRadius); - auto set = MakeZoneSet(m_config); - - for (const auto& monitorInfo : m_popularMonitors) - { - auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); - Assert::IsFalse(result); - } - } - - TEST_METHOD (CustomZoneFromInvalidGridLayoutInfo) - { - const std::wstring uuid = L"uuid"; - const GridLayoutInfo grid(GridLayoutInfo(GridLayoutInfo::Full{ - .rows = 1, - .columns = 3, - .rowsPercents = { -100 }, //rows percents are negative - .columnsPercents = { 2500, 2500 }, //column percents count is invalid - .cellChildMap = { { 0, 1, 2 } } })); - JSONHelpers::CustomZoneSetJSON expected{ uuid, CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } }; - json::to_file(m_path, JSONHelpers::CustomZoneSetJSON::ToJson(expected)); - Assert::IsTrue(std::filesystem::exists(m_path)); - - const int spacing = 0; - const int zoneCount = grid.rows() * grid.columns(); - - ZoneSetConfig m_config = ZoneSetConfig(m_id, ZoneSetLayoutType::Custom, m_monitor, DefaultValues::SensitivityRadius); - auto set = MakeZoneSet(m_config); - - for (const auto& monitorInfo : m_popularMonitors) - { - auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); - Assert::IsFalse(result); - } - } - - TEST_METHOD (CustomZoneFromValidCanvasLayoutInfo) - { - //prepare device data - const std::wstring zoneUuid = L"default_device_id"; - FancyZonesDataInstance().SetDeviceInfo(zoneUuid, DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 }); - - //prepare expected data - wil::unique_cotaskmem_string uuid; - Assert::AreEqual(S_OK, StringFromCLSID(m_id, &uuid)); - const CanvasLayoutInfo info{ 123, 321, { CanvasLayoutInfo::Rect{ 0, 0, 100, 100 }, CanvasLayoutInfo::Rect{ 50, 50, 150, 150 } } }; - CustomZoneSetData zoneSetData{ L"name", CustomLayoutType::Canvas, info }; - FancyZonesDataInstance().SetCustomZonesets(uuid.get(), zoneSetData); - - //test - const int spacing = 10; - const int zoneCount = static_cast(info.zones.size()); - ZoneSetConfig m_config = ZoneSetConfig(m_id, ZoneSetLayoutType::Custom, m_monitor, DefaultValues::SensitivityRadius); - for (const auto& monitorInfo : m_popularMonitors) - { - auto set = MakeZoneSet(m_config); - auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); - Assert::IsTrue(result); - checkZones(set, ZoneSetLayoutType::Custom, zoneCount, monitorInfo); - } - } - - TEST_METHOD (CustomZoneFromValidGridFullLayoutInfo) - { - //prepare device data - const std::wstring zoneUuid = L"default_device_id"; - FancyZonesDataInstance().SetDeviceInfo(zoneUuid, DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 }); - - //prepare expected data - wil::unique_cotaskmem_string uuid; - Assert::AreEqual(S_OK, StringFromCLSID(m_id, &uuid)); - const GridLayoutInfo grid(GridLayoutInfo(GridLayoutInfo::Full{ - .rows = 1, - .columns = 3, - .rowsPercents = { 10000 }, - .columnsPercents = { 2500, 5000, 2500 }, - .cellChildMap = { { 0, 1, 2 } } })); - CustomZoneSetData zoneSetData{ L"name", CustomLayoutType::Grid, grid }; - FancyZonesDataInstance().SetCustomZonesets(uuid.get(), zoneSetData); - - const int spacing = 10; - const int zoneCount = grid.rows() * grid.columns(); - - ZoneSetConfig m_config = ZoneSetConfig(m_id, ZoneSetLayoutType::Custom, m_monitor, DefaultValues::SensitivityRadius); - - for (const auto& monitorInfo : m_popularMonitors) - { - auto set = MakeZoneSet(m_config); - auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); - Assert::IsTrue(result); - checkZones(set, ZoneSetLayoutType::Custom, zoneCount, monitorInfo); - } - } - - TEST_METHOD (CustomZoneFromValidGridMinimalLayoutInfo) - { - const std::wstring uuid = L"uuid"; - const GridLayoutInfo grid(GridLayoutInfo(GridLayoutInfo::Minimal{ - .rows = 1, - .columns = 3 })); - JSONHelpers::CustomZoneSetJSON expected{ uuid, CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } }; - json::to_file(m_path, JSONHelpers::CustomZoneSetJSON::ToJson(expected)); - Assert::IsTrue(std::filesystem::exists(m_path)); - - const int spacing = 0; - const int zoneCount = grid.rows() * grid.columns(); - - ZoneSetConfig m_config = ZoneSetConfig(m_id, ZoneSetLayoutType::Custom, m_monitor, DefaultValues::SensitivityRadius); - auto set = MakeZoneSet(m_config); - - for (const auto& monitorInfo : m_popularMonitors) - { - auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); - Assert::IsFalse(result); - } - } - }; -} +#include "pch.h" +#include "FancyZonesLib\FancyZonesData.h" +#include "FancyZonesLib\FancyZonesDataTypes.h" +#include "FancyZonesLib\JsonHelpers.h" +#include "FancyZonesLib\VirtualDesktopUtils.h" +#include "FancyZonesLib\ZoneSet.h" + +#include + +#include "Util.h" +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace FancyZonesDataTypes; + +namespace FancyZonesUnitTests +{ + TEST_CLASS (ZoneSetUnitTests) + { + GUID m_id; + const ZoneSetLayoutType m_layoutType = ZoneSetLayoutType::Custom; + + winrt::com_ptr m_set; + + TEST_METHOD_INITIALIZE(Init) + { + auto hres = CoCreateGuid(&m_id); + Assert::AreEqual(S_OK, hres); + + ZoneSetConfig m_config = ZoneSetConfig(m_id, m_layoutType, Mocks::Monitor(), DefaultValues::SensitivityRadius, Settings::OverlappingZonesAlgorithm::Smallest); + m_set = MakeZoneSet(m_config); + } + + void compareZones(const winrt::com_ptr& expected, const winrt::com_ptr& actual) + { + Assert::AreEqual(expected->Id(), actual->Id()); + Assert::AreEqual(expected->GetZoneRect().left, actual->GetZoneRect().left); + Assert::AreEqual(expected->GetZoneRect().right, actual->GetZoneRect().right); + Assert::AreEqual(expected->GetZoneRect().top, actual->GetZoneRect().top); + Assert::AreEqual(expected->GetZoneRect().bottom, actual->GetZoneRect().bottom); + } + + public: + TEST_METHOD (TestCreateZoneSet) + { + Assert::IsNotNull(&m_set); + CustomAssert::AreEqual(m_set->Id(), m_id); + CustomAssert::AreEqual(m_set->LayoutType(), m_layoutType); + } + + TEST_METHOD (TestCreateZoneSetGuidEmpty) + { + GUID zoneSetId{}; + ZoneSetConfig config(zoneSetId, m_layoutType, Mocks::Monitor(), DefaultValues::SensitivityRadius); + winrt::com_ptr set = MakeZoneSet(config); + + Assert::IsNotNull(&set); + CustomAssert::AreEqual(set->Id(), zoneSetId); + CustomAssert::AreEqual(set->LayoutType(), m_layoutType); + } + + TEST_METHOD (TestCreateZoneSetMonitorEmpty) + { + ZoneSetConfig config(m_id, m_layoutType, nullptr, DefaultValues::SensitivityRadius); + winrt::com_ptr set = MakeZoneSet(config); + Assert::IsNotNull(&set); + CustomAssert::AreEqual(set->Id(), m_id); + CustomAssert::AreEqual(set->LayoutType(), m_layoutType); + } + + TEST_METHOD (TestCreateZoneSetKeyEmpty) + { + ZoneSetConfig config(m_id, m_layoutType, Mocks::Monitor(), DefaultValues::SensitivityRadius); + winrt::com_ptr set = MakeZoneSet(config); + Assert::IsNotNull(&set); + CustomAssert::AreEqual(set->Id(), m_id); + CustomAssert::AreEqual(set->LayoutType(), m_layoutType); + } + + TEST_METHOD (EmptyZones) + { + auto zones = m_set->GetZones(); + Assert::AreEqual((size_t)0, zones.size()); + } + + TEST_METHOD (AddOne) + { + constexpr size_t zoneId = 0; + winrt::com_ptr zone = MakeZone({ 0, 0, 100, 100 }, zoneId); + Assert::IsNotNull(zone.get()); + m_set->AddZone(zone); + auto zones = m_set->GetZones(); + Assert::AreEqual((size_t)1, zones.size()); + compareZones(zone, zones[zoneId]); + Assert::AreEqual(zoneId, zones[zoneId]->Id()); + } + + TEST_METHOD (AddManyEqual) + { + for (size_t i = 0; i < 1024; i++) + { + size_t zoneId = i; + winrt::com_ptr zone = MakeZone({ 0, 0, 100, 100 }, zoneId); + Assert::IsNotNull(zone.get()); + m_set->AddZone(zone); + auto zones = m_set->GetZones(); + Assert::AreEqual(i + 1, zones.size()); + compareZones(zone, zones[zoneId]); + Assert::AreEqual(zoneId, zones[zoneId]->Id()); + } + } + + TEST_METHOD (AddManyDifferent) + { + for (size_t i = 0; i < 1024; i++) + { + size_t zoneId = i; + int left = rand() % 10; + int top = rand() % 10; + int right = left + 1 + rand() % 100; + int bottom = top + 1 + rand() % 100; + winrt::com_ptr zone = MakeZone({ left, top, right, bottom }, zoneId); + Assert::IsNotNull(zone.get()); + m_set->AddZone(zone); + auto zones = m_set->GetZones(); + Assert::AreEqual(i + 1, zones.size()); + compareZones(zone, zones[zoneId]); + Assert::AreEqual(zoneId, zones[zoneId]->Id()); + } + } + + TEST_METHOD (MakeZoneFromZeroRect) + { + winrt::com_ptr zone = MakeZone({ 0, 0, 0, 0 }, 1); + Assert::IsNotNull(zone.get()); + } + + TEST_METHOD (MakeZoneFromInvalidRectWidth) + { + winrt::com_ptr zone = MakeZone({ 100, 100, 99, 101 }, 1); + Assert::IsNull(zone.get()); + } + + TEST_METHOD (MakeZoneFromInvalidRectHeight) + { + winrt::com_ptr zone = MakeZone({ 100, 100, 101, 99 }, 1); + Assert::IsNull(zone.get()); + } + + TEST_METHOD (MakeZoneFromInvalidRectCoords) + { + const int invalid = ZoneConstants::MAX_NEGATIVE_SPACING - 1; + winrt::com_ptr zone = MakeZone({ invalid, invalid, invalid, invalid }, 1); + Assert::IsNull(zone.get()); + } + + TEST_METHOD (ZoneFromPointEmpty) + { + auto actual = m_set->ZonesFromPoint(POINT{ 0, 0 }); + Assert::IsTrue(actual.size() == 0); + } + + TEST_METHOD (ZoneFromPointInner) + { + const int left = 0, top = 0, right = 100, bottom = 100; + winrt::com_ptr expected = MakeZone({ left, top, right, bottom }, 1); + m_set->AddZone(expected); + + for (int i = left + 1; i < right; i++) + { + for (int j = top + 1; j < bottom; j++) + { + auto actual = m_set->ZonesFromPoint(POINT{ i, j }); + Assert::IsTrue(actual.size() == 1); + compareZones(expected, m_set->GetZones()[actual[0]]); + } + } + } + + TEST_METHOD (ZoneFromPointBorder) + { + const int left = 0, top = 0, right = 100, bottom = 100; + winrt::com_ptr expected = MakeZone({ left, top, right, bottom }, 1); + m_set->AddZone(expected); + + for (int i = left; i < right; i++) + { + auto actual = m_set->ZonesFromPoint(POINT{ i, top }); + Assert::IsTrue(actual.size() == 1); + compareZones(expected, m_set->GetZones()[actual[0]]); + } + + for (int i = top; i < bottom; i++) + { + auto actual = m_set->ZonesFromPoint(POINT{ left, i }); + Assert::IsTrue(actual.size() == 1); + compareZones(expected, m_set->GetZones()[actual[0]]); + } + + //bottom and right borders considered to be outside + for (int i = left; i < right; i++) + { + auto actual = m_set->ZonesFromPoint(POINT{ i, bottom }); + Assert::IsTrue(actual.size() == 0); + } + + for (int i = top; i < bottom; i++) + { + auto actual = m_set->ZonesFromPoint(POINT{ right, i }); + Assert::IsTrue(actual.size() == 0); + } + } + + TEST_METHOD (ZoneFromPointOuter) + { + const int left = 0, top = 0, right = 100, bottom = 100; + winrt::com_ptr zone = MakeZone({ left, top, right, bottom }, 1); + m_set->AddZone(zone); + + auto actual = m_set->ZonesFromPoint(POINT{ 200, 200 }); + Assert::IsTrue(actual.size() == 0); + } + + TEST_METHOD (ZoneFromPointOverlapping) + { + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 1); + m_set->AddZone(zone1); + winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }, 2); + m_set->AddZone(zone2); + winrt::com_ptr zone3 = MakeZone({ 10, 10, 150, 150 }, 3); + m_set->AddZone(zone3); + winrt::com_ptr zone4 = MakeZone({ 10, 10, 50, 50 }, 4); + m_set->AddZone(zone4); + + // zone4 is expected because it's the smallest one, and it's considered to be inside + // since Multizones support + + auto actual = m_set->ZonesFromPoint(POINT{ 50, 50 }); + Assert::IsTrue(actual.size() == 1); + compareZones(zone4, m_set->GetZones()[actual[0]]); + } + + TEST_METHOD (ZoneFromPointMultizoneHorizontal) + { + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 1); + m_set->AddZone(zone1); + winrt::com_ptr zone2 = MakeZone({ 100, 0, 200, 100 }, 2); + m_set->AddZone(zone2); + winrt::com_ptr zone3 = MakeZone({ 0, 100, 100, 200 }, 3); + m_set->AddZone(zone3); + winrt::com_ptr zone4 = MakeZone({ 100, 100, 200, 200 }, 4); + m_set->AddZone(zone4); + + auto actual = m_set->ZonesFromPoint(POINT{ 50, 100 }); + Assert::IsTrue(actual.size() == 2); + compareZones(zone1, m_set->GetZones()[actual[0]]); + compareZones(zone3, m_set->GetZones()[actual[1]]); + } + + TEST_METHOD (ZoneFromPointMultizoneVertical) + { + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 1); + m_set->AddZone(zone1); + winrt::com_ptr zone2 = MakeZone({ 100, 0, 200, 100 }, 2); + m_set->AddZone(zone2); + winrt::com_ptr zone3 = MakeZone({ 0, 100, 100, 200 }, 3); + m_set->AddZone(zone3); + winrt::com_ptr zone4 = MakeZone({ 100, 100, 200, 200 }, 4); + m_set->AddZone(zone4); + + auto actual = m_set->ZonesFromPoint(POINT{ 100, 50 }); + Assert::IsTrue(actual.size() == 2); + compareZones(zone1, m_set->GetZones()[actual[0]]); + compareZones(zone2, m_set->GetZones()[actual[1]]); + } + + TEST_METHOD (ZoneFromPointMultizoneQuad) + { + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 1); + m_set->AddZone(zone1); + winrt::com_ptr zone2 = MakeZone({ 100, 0, 200, 100 }, 2); + m_set->AddZone(zone2); + winrt::com_ptr zone3 = MakeZone({ 0, 100, 100, 200 }, 3); + m_set->AddZone(zone3); + winrt::com_ptr zone4 = MakeZone({ 100, 100, 200, 200 }, 4); + m_set->AddZone(zone4); + + auto actual = m_set->ZonesFromPoint(POINT{ 100, 100 }); + Assert::IsTrue(actual.size() == 4); + compareZones(zone1, m_set->GetZones()[actual[0]]); + compareZones(zone2, m_set->GetZones()[actual[1]]); + compareZones(zone3, m_set->GetZones()[actual[2]]); + compareZones(zone4, m_set->GetZones()[actual[3]]); + } + + TEST_METHOD (ZoneIndexFromWindowUnknown) + { + winrt::com_ptr zone = MakeZone({ 0, 0, 100, 100 }, 1); + HWND window = Mocks::Window(); + HWND zoneWindow = Mocks::Window(); + m_set->AddZone(zone); + m_set->MoveWindowIntoZoneByIndexSet(window, zoneWindow, { 0 }); + + auto actual = m_set->GetZoneIndexSetFromWindow(Mocks::Window()); + Assert::IsTrue(std::vector{} == actual); + } + + TEST_METHOD (ZoneIndexFromWindowNull) + { + winrt::com_ptr zone = MakeZone({ 0, 0, 100, 100 }, 1); + HWND window = Mocks::Window(); + HWND zoneWindow = Mocks::Window(); + m_set->AddZone(zone); + m_set->MoveWindowIntoZoneByIndexSet(window, zoneWindow, { 0 }); + + auto actual = m_set->GetZoneIndexSetFromWindow(nullptr); + Assert::IsTrue(std::vector{} == actual); + } + + TEST_METHOD (MoveWindowIntoZoneByIndex) + { + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 1); + winrt::com_ptr zone2 = MakeZone({ 0, 0, 100, 100 }, 2); + winrt::com_ptr zone3 = MakeZone({ 0, 0, 100, 100 }, 3); + m_set->AddZone(zone1); + m_set->AddZone(zone2); + m_set->AddZone(zone3); + + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 1); + Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (MoveWindowIntoZoneByIndexWithNoZones) + { + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); + } + + TEST_METHOD (MoveWindowIntoZoneByIndexWithInvalidIndex) + { + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 1); + winrt::com_ptr zone2 = MakeZone({ 0, 0, 100, 100 }, 2); + winrt::com_ptr zone3 = MakeZone({ 0, 0, 100, 100 }, 3); + m_set->AddZone(zone1); + m_set->AddZone(zone2); + m_set->AddZone(zone3); + + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 100); + Assert::IsTrue(std::vector{} == m_set->GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (MoveWindowIntoZoneByIndexSeveralTimesSameWindow) + { + // Add a couple of zones. + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); + winrt::com_ptr zone2 = MakeZone({ 1, 1, 101, 101 }, 1); + winrt::com_ptr zone3 = MakeZone({ 2, 2, 102, 102 }, 2); + m_set->AddZone(zone1); + m_set->AddZone(zone2); + m_set->AddZone(zone3); + + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); + Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); + + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 1); + Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); + + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 2); + Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (MoveWindowIntoZoneByIndexSeveralTimesSameIndex) + { + // Add a couple of zones. + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); + winrt::com_ptr zone2 = MakeZone({ 1, 1, 101, 101 }, 1); + winrt::com_ptr zone3 = MakeZone({ 2, 2, 102, 102 }, 2); + m_set->AddZone(zone1); + m_set->AddZone(zone2); + m_set->AddZone(zone3); + + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); + Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (MoveWindowIntoZoneByPointEmpty) + { + m_set->MoveWindowIntoZoneByPoint(Mocks::Window(), Mocks::Window(), POINT{ 0, 0 }); + } + + TEST_METHOD (MoveWindowIntoZoneByPointOuterPoint) + { + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 1); + m_set->AddZone(zone1); + + auto window = Mocks::Window(); + m_set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 200, 200 }); + + Assert::IsTrue(std::vector{} == m_set->GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (MoveWindowIntoZoneByPointInnerPoint) + { + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); + m_set->AddZone(zone1); + + auto window = Mocks::Window(); + m_set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 50, 50 }); + + Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (MoveWindowIntoZoneByPointInnerPointOverlappingZones) + { + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); + winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }, 1); + m_set->AddZone(zone1); + m_set->AddZone(zone2); + + auto window = Mocks::Window(); + m_set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 50, 50 }); + + Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (MoveWindowIntoZoneByPointDropAddWindow) + { + const auto window = Mocks::Window(); + const auto zoneWindow = Mocks::Window(); + + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); + winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }, 1); + + m_set->AddZone(zone1); + m_set->AddZone(zone2); + + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); + + m_set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 50, 50 }); + + Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (MoveWindowIntoZoneByPointDropAddWindowToSameZone) + { + const auto window = Mocks::Window(); + const auto zoneWindow = Mocks::Window(); + + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); + winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }, 1); + + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 1); + + m_set->AddZone(zone1); + m_set->AddZone(zone2); + + m_set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 50, 50 }); + + Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (MoveWindowIntoZoneByPointSeveralZonesWithSameWindow) + { + const auto window = Mocks::Window(); + const auto zoneWindow = Mocks::Window(); + + winrt::com_ptr zone1 = MakeZone({ 0, 0, 100, 100 }, 0); + winrt::com_ptr zone2 = MakeZone({ 10, 10, 90, 90 }, 1); + winrt::com_ptr zone3 = MakeZone({ 20, 20, 80, 80 }, 2); + + m_set->AddZone(zone1); + m_set->AddZone(zone2); + m_set->AddZone(zone3); + + m_set->MoveWindowIntoZoneByIndexSet(window, Mocks::Window(), { 0, 1, 2 }); + + m_set->MoveWindowIntoZoneByPoint(window, Mocks::Window(), POINT{ 50, 50 }); + + Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); + } + }; + + // MoveWindowIntoZoneByDirectionAndIndex is complicated enough to warrant it's own test class + TEST_CLASS (ZoneSetsMoveWindowIntoZoneByDirectionUnitTests) + { + winrt::com_ptr m_set; + winrt::com_ptr m_zone1; + winrt::com_ptr m_zone2; + winrt::com_ptr m_zone3; + + TEST_METHOD_INITIALIZE(Initialize) + { + ZoneSetConfig config({}, ZoneSetLayoutType::Custom, Mocks::Monitor(), DefaultValues::SensitivityRadius); + m_set = MakeZoneSet(config); + + // Add a couple of zones. + m_zone1 = MakeZone({ 0, 0, 100, 100 }, 0); + m_zone2 = MakeZone({ 0, 0, 100, 100 }, 1); + m_zone3 = MakeZone({ 0, 0, 100, 100 }, 2); + m_set->AddZone(m_zone1); + m_set->AddZone(m_zone2); + m_set->AddZone(m_zone3); + } + + TEST_METHOD (EmptyZonesLeft) + { + ZoneSetConfig config({}, ZoneSetLayoutType::Custom, Mocks::Monitor(), DefaultValues::SensitivityRadius); + auto set = MakeZoneSet(config); + + set->MoveWindowIntoZoneByDirectionAndIndex(Mocks::Window(), Mocks::Window(), VK_LEFT, true); + } + + TEST_METHOD (EmptyZonesRight) + { + ZoneSetConfig config({}, ZoneSetLayoutType::Custom, Mocks::Monitor(), DefaultValues::SensitivityRadius); + auto set = MakeZoneSet(config); + + set->MoveWindowIntoZoneByDirectionAndIndex(Mocks::Window(), Mocks::Window(), VK_RIGHT, true); + } + + TEST_METHOD (MoveRightNoZones) + { + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); + Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (MoveLeftNoZones) + { + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); + Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (MoveRightTwice) + { + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); + m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); + Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (MoveLeftTwice) + { + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); + m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); + Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (MoveRightMoreThanZonesCount) + { + HWND window = Mocks::Window(); + for (int i = 0; i <= m_set->GetZones().size(); i++) + { + m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); + } + + Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (MoveLeftMoreThanZonesCount) + { + HWND window = Mocks::Window(); + for (int i = 0; i <= m_set->GetZones().size(); i++) + { + m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); + } + + Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (MoveWindowIntoZoneByDirectionRight) + { + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); + m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); + Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); + + m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); + Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (MoveRightWithSameWindowAdded) + { + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByIndexSet(window, Mocks::Window(), { 0, 1 }); + + Assert::IsTrue(std::vector{ 0, 1 } == m_set->GetZoneIndexSetFromWindow(window)); + + m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); + Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); + + m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); + Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (MoveRightWithDifferentWindowsAdded) + { + HWND window1 = Mocks::Window(); + HWND window2 = Mocks::Window(); + m_set->MoveWindowIntoZoneByIndex(window1, Mocks::Window(), { 0 }); + m_set->MoveWindowIntoZoneByIndex(window2, Mocks::Window(), { 1 }); + + Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window1)); + Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window2)); + + m_set->MoveWindowIntoZoneByDirectionAndIndex(window1, Mocks::Window(), VK_RIGHT, true); + + Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window1)); + Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window2)); + + m_set->MoveWindowIntoZoneByDirectionAndIndex(window1, Mocks::Window(), VK_RIGHT, true); + + Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window1)); + Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window2)); + } + + TEST_METHOD (MoveWindowIntoZoneByDirectionLeft) + { + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 2); + m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); + Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window)); + + m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); + Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (MoveLeftWithSameWindowAdded) + { + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByIndexSet(window, Mocks::Window(), { 1, 2 }); + + Assert::IsTrue(std::vector{ 1, 2 } == m_set->GetZoneIndexSetFromWindow(window)); + + m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); + Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); + + m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); + Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (MoveLeftWithDifferentWindowsAdded) + { + HWND window1 = Mocks::Window(); + HWND window2 = Mocks::Window(); + m_set->MoveWindowIntoZoneByIndex(window1, Mocks::Window(), 1); + m_set->MoveWindowIntoZoneByIndex(window2, Mocks::Window(), 2); + + Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window1)); + Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window2)); + + m_set->MoveWindowIntoZoneByDirectionAndIndex(window2, Mocks::Window(), VK_LEFT, true); + Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window1)); + Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window2)); + + m_set->MoveWindowIntoZoneByDirectionAndIndex(window2, Mocks::Window(), VK_LEFT, true); + Assert::IsTrue(std::vector{ 1 } == m_set->GetZoneIndexSetFromWindow(window1)); + Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window2)); + } + + TEST_METHOD (MoveWindowIntoZoneByDirectionWrapAroundRight) + { + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 2); + m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, true); + Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (MoveWindowIntoZoneByDirectionWrapAroundLeft) + { + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); + m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, true); + Assert::IsTrue(std::vector{ 2 } == m_set->GetZoneIndexSetFromWindow(window)); + } + + TEST_METHOD (MoveSecondWindowIntoSameZone) + { + HWND window1 = Mocks::Window(); + m_set->MoveWindowIntoZoneByIndex(window1, Mocks::Window(), 0); + + HWND window2 = Mocks::Window(); + m_set->MoveWindowIntoZoneByDirectionAndIndex(window2, Mocks::Window(), VK_RIGHT, true); + + Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window1)); + Assert::IsTrue(std::vector{ 0 } == m_set->GetZoneIndexSetFromWindow(window2)); + } + + TEST_METHOD (MoveRightMoreThanZoneCountReturnsFalse) + { + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 0); + for (size_t i = 0; i < m_set->GetZones().size() - 1; ++i) + { + m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, false); + } + bool moreZonesInLayout = m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_RIGHT, false); + Assert::IsFalse(moreZonesInLayout); + } + + TEST_METHOD (MoveLeftMoreThanZoneCountReturnsFalse) + { + HWND window = Mocks::Window(); + m_set->MoveWindowIntoZoneByIndex(window, Mocks::Window(), 2); + for (size_t i = 0; i < m_set->GetZones().size() - 1; ++i) + { + m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, false); + } + bool moreZonesInLayout = m_set->MoveWindowIntoZoneByDirectionAndIndex(window, Mocks::Window(), VK_LEFT, false); + Assert::IsFalse(moreZonesInLayout); + } + }; + + TEST_CLASS (ZoneSetCalculateZonesUnitTests) + { + GUID m_id; + const ZoneSetLayoutType m_layoutType = ZoneSetLayoutType::Custom; + const PCWSTR m_resolutionKey = L"WorkAreaIn"; + winrt::com_ptr m_set; + + HMONITOR m_monitor; + const std::array m_popularMonitors{ + MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1024, .bottom = 768 } }, + MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1280, .bottom = 720 } }, + MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1280, .bottom = 800 } }, + MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1280, .bottom = 1024 } }, + MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1366, .bottom = 768 } }, + MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1440, .bottom = 900 } }, + MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1536, .bottom = 864 } }, + MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1600, .bottom = 900 } }, + MONITORINFO{ .cbSize = sizeof(MONITORINFO), .rcWork{ .left = 0, .top = 0, .right = 1920, .bottom = 1080 } } + }; + + MONITORINFO m_monitorInfo; + + const std::wstring m_path = PTSettingsHelper::get_module_save_folder_location(L"FancyZones") + L"\\" + std::wstring(L"testzones.json"); + + TEST_METHOD_INITIALIZE(Init) + { + auto hres = CoCreateGuid(&m_id); + Assert::AreEqual(S_OK, hres); + + m_monitor = MonitorFromPoint(POINT{ 0, 0 }, MONITOR_DEFAULTTOPRIMARY); + + ZoneSetConfig m_config = ZoneSetConfig(m_id, m_layoutType, m_monitor, DefaultValues::SensitivityRadius); + m_set = MakeZoneSet(m_config); + } + + TEST_METHOD_CLEANUP(Cleanup) + { + std::filesystem::remove(m_path); + } + + void checkZones(const winrt::com_ptr& set, ZoneSetLayoutType type, size_t expectedCount, MONITORINFO monitorInfo) + { + auto zones = set->GetZones(); + Assert::AreEqual(expectedCount, zones.size()); + + int zoneId = 0; + for (const auto& zone : zones) + { + Assert::IsTrue(set->IsZoneEmpty(zoneId)); + + const auto& zoneRect = zone.second->GetZoneRect(); + Assert::IsTrue(zoneRect.left >= 0, L"left border is less than zero"); + Assert::IsTrue(zoneRect.top >= 0, L"top border is less than zero"); + + Assert::IsTrue(zoneRect.left < zoneRect.right, L"rect.left >= rect.right"); + Assert::IsTrue(zoneRect.top < zoneRect.bottom, L"rect.top >= rect.bottom"); + + if (type != ZoneSetLayoutType::Focus) + { + Assert::IsTrue(zoneRect.right <= monitorInfo.rcWork.right, L"right border is bigger than monitor work space"); + Assert::IsTrue(zoneRect.bottom <= monitorInfo.rcWork.bottom, L"bottom border is bigger than monitor work space"); + } + + zoneId++; + } + } + + public: + TEST_METHOD (ValidValues) + { + const int spacing = 10; + const int zoneCount = 10; + + for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) + { + ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto set = MakeZoneSet(m_config); + auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); + Assert::IsTrue(result); + checkZones(set, static_cast(type), zoneCount, monitorInfo); + } + } + } + TEST_METHOD (InvalidMonitorInfo) + { + const int spacing = 10; + const int zoneCount = 10; + + for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) + { + ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); + auto set = MakeZoneSet(m_config); + + MONITORINFO info{}; + auto result = set->CalculateZones(info.rcWork, zoneCount, spacing); + Assert::IsFalse(result); + } + } + + TEST_METHOD (ZeroSpacing) + { + const int spacing = 0; + const int zoneCount = 10; + + for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) + { + ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto set = MakeZoneSet(m_config); + auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); + Assert::IsTrue(result); + checkZones(set, static_cast(type), zoneCount, monitorInfo); + } + } + } + + TEST_METHOD (LargeNegativeSpacing) + { + const int spacing = ZoneConstants::MAX_NEGATIVE_SPACING - 1; + const int zoneCount = 10; + + for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) + { + ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); + auto set = MakeZoneSet(m_config); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); + if (type == static_cast(ZoneSetLayoutType::Focus)) + { + //Focus doesn't depends on spacing + Assert::IsTrue(result); + } + else + { + Assert::IsFalse(result); + } + } + } + } + + TEST_METHOD (HorizontallyBigSpacing) + { + const int zoneCount = 10; + + for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) + { + ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); + auto set = MakeZoneSet(m_config); + + for (const auto& monitorInfo : m_popularMonitors) + { + const int spacing = monitorInfo.rcWork.right; + auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); + if (type == static_cast(ZoneSetLayoutType::Focus)) + { + //Focus doesn't depends on spacing + Assert::IsTrue(result); + } + else + { + Assert::IsFalse(result); + } + } + } + } + + TEST_METHOD (VerticallyBigSpacing) + { + const int zoneCount = 10; + + for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) + { + ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); + auto set = MakeZoneSet(m_config); + + for (const auto& monitorInfo : m_popularMonitors) + { + const int spacing = monitorInfo.rcWork.bottom; + auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); + if (type == static_cast(ZoneSetLayoutType::Focus)) + { + //Focus doesn't depends on spacing + Assert::IsTrue(result); + } + else + { + Assert::IsFalse(result); + } + } + } + } + + TEST_METHOD (ZeroZoneCount) + { + const int spacing = 10; + const int zoneCount = 0; + + for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) + { + ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); + auto set = MakeZoneSet(m_config); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); + Assert::IsFalse(result); + } + } + } + + TEST_METHOD (BigZoneCount) + { + const int spacing = 1; + + for (int type = static_cast(ZoneSetLayoutType::Focus); type < static_cast(ZoneSetLayoutType::Custom); type++) + { + const int spacing = 10; + const int zoneCount = 40; //editor limit + + ZoneSetConfig m_config = ZoneSetConfig(m_id, static_cast(type), m_monitor, DefaultValues::SensitivityRadius); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto set = MakeZoneSet(m_config); + auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); + Assert::IsTrue(result); + checkZones(set, static_cast(type), zoneCount, monitorInfo); + } + } + } + + TEST_METHOD (CustomZonesFromNonexistentFile) + { + const int spacing = 10; + const int zoneCount = 0; + + //be sure that file does not exist + if (std::filesystem::exists(m_path)) + { + std::filesystem::remove(m_path); + } + + ZoneSetConfig m_config = ZoneSetConfig(m_id, ZoneSetLayoutType::Custom, m_monitor, DefaultValues::SensitivityRadius); + auto set = MakeZoneSet(m_config); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); + Assert::IsFalse(result); + } + } + + TEST_METHOD (CustomZoneFromEmptyFile) + { + const int spacing = 10; + const int zoneCount = 0; + + Assert::IsTrue(std::filesystem::create_directories(m_path)); + Assert::IsTrue(std::filesystem::exists(m_path)); + + ZoneSetConfig m_config = ZoneSetConfig(m_id, ZoneSetLayoutType::Custom, m_monitor, DefaultValues::SensitivityRadius); + auto set = MakeZoneSet(m_config); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); + Assert::IsFalse(result); + } + } + + TEST_METHOD (CustomZoneFromInvalidCanvasLayoutInfo) + { + const std::wstring uuid = L"uuid"; + const CanvasLayoutInfo info{ -1, 100, { CanvasLayoutInfo::Rect{ -10, -10, 100, 100 }, CanvasLayoutInfo::Rect{ 50, 50, 150, 150 } } }; + JSONHelpers::CustomZoneSetJSON expected{ uuid, CustomZoneSetData{ L"name", CustomLayoutType::Canvas, info } }; + json::to_file(m_path, JSONHelpers::CustomZoneSetJSON::ToJson(expected)); + Assert::IsTrue(std::filesystem::exists(m_path)); + + const int spacing = 10; + const int zoneCount = static_cast(info.zones.size()); + + ZoneSetConfig m_config = ZoneSetConfig(m_id, ZoneSetLayoutType::Custom, m_monitor, DefaultValues::SensitivityRadius); + auto set = MakeZoneSet(m_config); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); + Assert::IsFalse(result); + } + } + + TEST_METHOD (CustomZoneFromInvalidGridLayoutInfo) + { + const std::wstring uuid = L"uuid"; + const GridLayoutInfo grid(GridLayoutInfo(GridLayoutInfo::Full{ + .rows = 1, + .columns = 3, + .rowsPercents = { -100 }, //rows percents are negative + .columnsPercents = { 2500, 2500 }, //column percents count is invalid + .cellChildMap = { { 0, 1, 2 } } })); + JSONHelpers::CustomZoneSetJSON expected{ uuid, CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } }; + json::to_file(m_path, JSONHelpers::CustomZoneSetJSON::ToJson(expected)); + Assert::IsTrue(std::filesystem::exists(m_path)); + + const int spacing = 0; + const int zoneCount = grid.rows() * grid.columns(); + + ZoneSetConfig m_config = ZoneSetConfig(m_id, ZoneSetLayoutType::Custom, m_monitor, DefaultValues::SensitivityRadius); + auto set = MakeZoneSet(m_config); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); + Assert::IsFalse(result); + } + } + + TEST_METHOD (CustomZoneFromValidCanvasLayoutInfo) + { + //prepare device data + const std::wstring zoneUuid = L"default_device_id"; + FancyZonesDataInstance().SetDeviceInfo(zoneUuid, DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 }); + + //prepare expected data + wil::unique_cotaskmem_string uuid; + Assert::AreEqual(S_OK, StringFromCLSID(m_id, &uuid)); + const CanvasLayoutInfo info{ 123, 321, { CanvasLayoutInfo::Rect{ 0, 0, 100, 100 }, CanvasLayoutInfo::Rect{ 50, 50, 150, 150 } } }; + CustomZoneSetData zoneSetData{ L"name", CustomLayoutType::Canvas, info }; + FancyZonesDataInstance().SetCustomZonesets(uuid.get(), zoneSetData); + + //test + const int spacing = 10; + const int zoneCount = static_cast(info.zones.size()); + ZoneSetConfig m_config = ZoneSetConfig(m_id, ZoneSetLayoutType::Custom, m_monitor, DefaultValues::SensitivityRadius); + for (const auto& monitorInfo : m_popularMonitors) + { + auto set = MakeZoneSet(m_config); + auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); + Assert::IsTrue(result); + checkZones(set, ZoneSetLayoutType::Custom, zoneCount, monitorInfo); + } + } + + TEST_METHOD (CustomZoneFromValidGridFullLayoutInfo) + { + //prepare device data + const std::wstring zoneUuid = L"default_device_id"; + FancyZonesDataInstance().SetDeviceInfo(zoneUuid, DeviceInfoData{ ZoneSetData{ L"uuid", ZoneSetLayoutType::Custom }, true, 16, 3 }); + + //prepare expected data + wil::unique_cotaskmem_string uuid; + Assert::AreEqual(S_OK, StringFromCLSID(m_id, &uuid)); + const GridLayoutInfo grid(GridLayoutInfo(GridLayoutInfo::Full{ + .rows = 1, + .columns = 3, + .rowsPercents = { 10000 }, + .columnsPercents = { 2500, 5000, 2500 }, + .cellChildMap = { { 0, 1, 2 } } })); + CustomZoneSetData zoneSetData{ L"name", CustomLayoutType::Grid, grid }; + FancyZonesDataInstance().SetCustomZonesets(uuid.get(), zoneSetData); + + const int spacing = 10; + const int zoneCount = grid.rows() * grid.columns(); + + ZoneSetConfig m_config = ZoneSetConfig(m_id, ZoneSetLayoutType::Custom, m_monitor, DefaultValues::SensitivityRadius); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto set = MakeZoneSet(m_config); + auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); + Assert::IsTrue(result); + checkZones(set, ZoneSetLayoutType::Custom, zoneCount, monitorInfo); + } + } + + TEST_METHOD (CustomZoneFromValidGridMinimalLayoutInfo) + { + const std::wstring uuid = L"uuid"; + const GridLayoutInfo grid(GridLayoutInfo(GridLayoutInfo::Minimal{ + .rows = 1, + .columns = 3 })); + JSONHelpers::CustomZoneSetJSON expected{ uuid, CustomZoneSetData{ L"name", CustomLayoutType::Grid, grid } }; + json::to_file(m_path, JSONHelpers::CustomZoneSetJSON::ToJson(expected)); + Assert::IsTrue(std::filesystem::exists(m_path)); + + const int spacing = 0; + const int zoneCount = grid.rows() * grid.columns(); + + ZoneSetConfig m_config = ZoneSetConfig(m_id, ZoneSetLayoutType::Custom, m_monitor, DefaultValues::SensitivityRadius); + auto set = MakeZoneSet(m_config); + + for (const auto& monitorInfo : m_popularMonitors) + { + auto result = set->CalculateZones(monitorInfo.rcWork, zoneCount, spacing); + Assert::IsFalse(result); + } + } + }; +} diff --git a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/ZoneWindow.Spec.cpp similarity index 98% rename from src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp rename to src/modules/fancyzones/FancyZonesTests/UnitTests/ZoneWindow.Spec.cpp index 78cc54cd2a..7b858b1600 100644 --- a/src/modules/fancyzones/tests/UnitTests/ZoneWindow.Spec.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/ZoneWindow.Spec.cpp @@ -2,13 +2,13 @@ #include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include "Util.h" #include diff --git a/src/modules/fancyzones/dll/packages.config b/src/modules/fancyzones/FancyZonesTests/UnitTests/packages.config similarity index 94% rename from src/modules/fancyzones/dll/packages.config rename to src/modules/fancyzones/FancyZonesTests/UnitTests/packages.config index f0ed7a6f44..fda08e21c1 100644 --- a/src/modules/fancyzones/dll/packages.config +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/packages.config @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/src/modules/fancyzones/tests/UnitTests/pch.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/pch.cpp similarity index 97% rename from src/modules/fancyzones/tests/UnitTests/pch.cpp rename to src/modules/fancyzones/FancyZonesTests/UnitTests/pch.cpp index 91c22df2a1..64b7eef6d6 100644 --- a/src/modules/fancyzones/tests/UnitTests/pch.cpp +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/pch.cpp @@ -1,5 +1,5 @@ -// pch.cpp: source file corresponding to the pre-compiled header - -#include "pch.h" - -// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/src/modules/fancyzones/tests/UnitTests/pch.h b/src/modules/fancyzones/FancyZonesTests/UnitTests/pch.h similarity index 94% rename from src/modules/fancyzones/tests/UnitTests/pch.h rename to src/modules/fancyzones/FancyZonesTests/UnitTests/pch.h index 76b8934733..c22db7cf6e 100644 --- a/src/modules/fancyzones/tests/UnitTests/pch.h +++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/pch.h @@ -1,18 +1,18 @@ -// pch.h: This is a precompiled header file. -// Files listed below are compiled only once, improving build performance for future builds. -// This also affects IntelliSense performance, including code completion and many code browsing features. -// However, files listed here are ALL re-compiled if any one of them is updated between builds. -// Do not add files here that you will be updating frequently as this negates the performance advantage. - -#ifndef PCH_H -#define PCH_H - -// add headers that you want to pre-compile here -#include -#include -#include -#include -#include "lib/pch.h" -#include "CppUnitTest.h" - -#endif //PCH_H +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for future builds. +// This also affects IntelliSense performance, including code completion and many code browsing features. +// However, files listed here are ALL re-compiled if any one of them is updated between builds. +// Do not add files here that you will be updating frequently as this negates the performance advantage. + +#ifndef PCH_H +#define PCH_H + +// add headers that you want to pre-compile here +#include +#include +#include +#include +#include "FancyZonesLib/pch.h" +#include "CppUnitTest.h" + +#endif //PCH_H diff --git a/src/modules/fancyzones/tests/UnitTests/resource.h b/src/modules/fancyzones/FancyZonesTests/UnitTests/resource.h similarity index 100% rename from src/modules/fancyzones/tests/UnitTests/resource.h rename to src/modules/fancyzones/FancyZonesTests/UnitTests/resource.h diff --git a/src/modules/fancyzones/dll/dllmain.cpp b/src/modules/fancyzones/dll/dllmain.cpp deleted file mode 100644 index 56aa268ffd..0000000000 --- a/src/modules/fancyzones/dll/dllmain.cpp +++ /dev/null @@ -1,336 +0,0 @@ -#include "pch.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) -{ - switch (ul_reason_for_call) - { - case DLL_PROCESS_ATTACH: - Trace::RegisterProvider(); - break; - - case DLL_THREAD_ATTACH: - case DLL_THREAD_DETACH: - break; - - case DLL_PROCESS_DETACH: - Trace::UnregisterProvider(); - break; - } - return TRUE; -} - -class FancyZonesModule : public PowertoyModuleIface -{ -public: - // Return the localized display name of the powertoy - virtual PCWSTR get_name() override - { - return app_name.c_str(); - } - - // Return the non localized key of the powertoy, this will be cached by the runner - virtual const wchar_t* get_key() override - { - return app_key.c_str(); - } - - // Return JSON with the configuration options. - // These are the settings shown on the settings page along with their current values. - virtual bool get_config(_Out_ PWSTR buffer, _Out_ int* buffer_size) override - { - return m_settings->GetConfig(buffer, buffer_size); - } - - // Passes JSON with the configuration settings for the powertoy. - // This is called when the user hits Save on the settings page. - virtual void set_config(PCWSTR config) override - { - m_settings->SetConfig(config); - } - - // Signal from the Settings editor to call a custom action. - // This can be used to spawn more complex editors. - virtual void call_custom_action(const wchar_t* action) override - { - m_settings->CallCustomAction(action); - } - - // Enable the powertoy - virtual void enable() - { - Logger::info("FancyZones enabling"); - - if (!m_app) - { - InitializeWinhookEventIds(); - Trace::FancyZones::EnableFancyZones(true); - m_app = MakeFancyZones(reinterpret_cast(&__ImageBase), m_settings, std::bind(&FancyZonesModule::disable, this)); -#if defined(DISABLE_LOWLEVEL_HOOKS_WHEN_DEBUGGED) - const bool hook_disabled = IsDebuggerPresent(); -#else - const bool hook_disabled = false; -#endif - if (!hook_disabled) - { - s_llKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), NULL); - if (!s_llKeyboardHook) - { - DWORD errorCode = GetLastError(); - show_last_error_message(L"SetWindowsHookEx", errorCode, GET_RESOURCE_STRING(IDS_POWERTOYS_FANCYZONES).c_str()); - auto errorMessage = get_last_error_message(errorCode); - Trace::FancyZones::Error(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"enable.SetWindowsHookEx"); - } - } - - std::array events_to_subscribe = { - EVENT_SYSTEM_MOVESIZESTART, - EVENT_SYSTEM_MOVESIZEEND, - EVENT_OBJECT_NAMECHANGE, - EVENT_OBJECT_UNCLOAKED, - EVENT_OBJECT_SHOW, - EVENT_OBJECT_CREATE - }; - for (const auto event : events_to_subscribe) - { - auto hook = SetWinEventHook(event, event, nullptr, WinHookProc, 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); - if (hook) - { - m_staticWinEventHooks.emplace_back(hook); - } - else - { - MessageBoxW(NULL, - GET_RESOURCE_STRING(IDS_WINDOW_EVENT_LISTENER_ERROR).c_str(), - GET_RESOURCE_STRING(IDS_POWERTOYS_FANCYZONES).c_str(), - MB_OK | MB_ICONERROR); - } - } - - if (m_app) - { - m_app->Run(); - } - } - } - - // Disable the powertoy - virtual void disable() - { - Logger::info("FancyZones disabling"); - - Disable(true); - } - - // Returns if the powertoy is enabled - virtual bool is_enabled() override - { - return m_app != nullptr; - } - - // Destroy the powertoy and free memory - virtual void destroy() override - { - Disable(false); - delete this; - } - - FancyZonesModule() - { - app_name = GET_RESOURCE_STRING(IDS_FANCYZONES); - app_key = NonLocalizable::FancyZonesStr; - const auto appFolder = PTSettingsHelper::get_module_save_folder_location(app_key); - const std::filesystem::path logFolder = LoggerHelpers::get_log_folder_path(appFolder); - - std::filesystem::path logFilePath(logFolder); - logFilePath.append(LogSettings::fancyZonesLogPath); - Logger::init(LogSettings::fancyZonesLoggerName, logFilePath.wstring(), PTSettingsHelper::get_log_settings_file_location()); - - std::filesystem::path oldLogFolder(appFolder); - oldLogFolder.append(LogSettings::fancyZonesOldLogPath); - LoggerHelpers::delete_old_log_folder(oldLogFolder); - - LoggerHelpers::delete_other_versions_log_folders(appFolder, logFolder); - - m_settings = MakeFancyZonesSettings(reinterpret_cast(&__ImageBase), FancyZonesModule::get_name(), FancyZonesModule::get_key()); - FancyZonesDataInstance().LoadFancyZonesData(); - s_instance = this; - - // TODO: consider removing this call since the registry hasn't been used since 0.15 - DeleteFancyZonesRegistryData(); - } - -private: - void Disable(bool const traceEvent) - { - if (m_app) - { - if (traceEvent) - { - Trace::FancyZones::EnableFancyZones(false); - } - m_app->Destroy(); - m_app = nullptr; - m_settings->ResetCallback(); - - if (s_llKeyboardHook) - { - if (UnhookWindowsHookEx(s_llKeyboardHook)) - { - s_llKeyboardHook = nullptr; - } - } - - m_staticWinEventHooks.erase(std::remove_if(begin(m_staticWinEventHooks), - end(m_staticWinEventHooks), - [](const HWINEVENTHOOK hook) { - return UnhookWinEvent(hook); - }), - end(m_staticWinEventHooks)); - if (m_objectLocationWinEventHook) - { - if (UnhookWinEvent(m_objectLocationWinEventHook)) - { - m_objectLocationWinEventHook = nullptr; - } - } - } - } - - intptr_t HandleKeyboardHookEvent(LowlevelKeyboardEvent* data) noexcept; - void HandleWinHookEvent(WinHookEvent* data) noexcept; - - winrt::com_ptr m_app; - winrt::com_ptr m_settings; - std::wstring app_name; - //contains the non localized key of the powertoy - std::wstring app_key; - - static inline FancyZonesModule* s_instance = nullptr; - static inline HHOOK s_llKeyboardHook = nullptr; - - std::vector m_staticWinEventHooks; - HWINEVENTHOOK m_objectLocationWinEventHook = nullptr; - - static LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) - { - LowlevelKeyboardEvent event; - if (nCode == HC_ACTION && wParam == WM_KEYDOWN) - { - event.lParam = reinterpret_cast(lParam); - event.wParam = wParam; - if (s_instance) - { - if (s_instance->HandleKeyboardHookEvent(&event) == 1) - { - return 1; - } - } - } - return CallNextHookEx(NULL, nCode, wParam, lParam); - } - - static void CALLBACK WinHookProc(HWINEVENTHOOK winEventHook, - DWORD event, - HWND window, - LONG object, - LONG child, - DWORD eventThread, - DWORD eventTime) - { - WinHookEvent data{ event, window, object, child, eventThread, eventTime }; - if (s_instance) - { - s_instance->HandleWinHookEvent(&data); - } - } -}; - -intptr_t FancyZonesModule::HandleKeyboardHookEvent(LowlevelKeyboardEvent* data) noexcept -{ - return m_app.as()->OnKeyDown(data->lParam); -} - -void FancyZonesModule::HandleWinHookEvent(WinHookEvent* data) noexcept -{ - auto fzCallback = m_app.as(); - switch (data->event) - { - case EVENT_SYSTEM_MOVESIZESTART: - { - fzCallback->HandleWinHookEvent(data); - if (!m_objectLocationWinEventHook) - { - m_objectLocationWinEventHook = SetWinEventHook(EVENT_OBJECT_LOCATIONCHANGE, - EVENT_OBJECT_LOCATIONCHANGE, - nullptr, - WinHookProc, - 0, - 0, - WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); - } - } - break; - - case EVENT_SYSTEM_MOVESIZEEND: - { - if (UnhookWinEvent(m_objectLocationWinEventHook)) - { - m_objectLocationWinEventHook = nullptr; - } - fzCallback->HandleWinHookEvent(data); - } - break; - - case EVENT_OBJECT_LOCATIONCHANGE: - { - fzCallback->HandleWinHookEvent(data); - } - break; - - case EVENT_OBJECT_NAMECHANGE: - { - // The accessibility name of the desktop window changes whenever the user - // switches virtual desktops. - if (data->hwnd == GetDesktopWindow()) - { - m_app.as()->VirtualDesktopChanged(); - } - } - break; - - case EVENT_OBJECT_UNCLOAKED: - case EVENT_OBJECT_SHOW: - case EVENT_OBJECT_CREATE: - { - fzCallback->HandleWinHookEvent(data); - } - break; - - default: - break; - } -} - -extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() -{ - return new FancyZonesModule(); -} diff --git a/src/runner/main.cpp b/src/runner/main.cpp index 73281c8267..2f1b07f29b 100644 --- a/src/runner/main.cpp +++ b/src/runner/main.cpp @@ -131,7 +131,7 @@ int runner(bool isProcessElevated, bool openSettings, bool openOobe) // Load Powertoys DLLs const std::array knownModules = { - L"modules/FancyZones/fancyzones.dll", + L"modules/FancyZones/FancyZonesModuleInterface.dll", L"modules/FileExplorerPreview/powerpreview.dll", L"modules/ImageResizer/ImageResizerExt.dll", L"modules/KeyboardManager/KeyboardManager.dll", diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/FancyZonesSettingsIPCMessage.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/FancyZonesSettingsIPCMessage.cs deleted file mode 100644 index 4da9cac2f1..0000000000 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/FancyZonesSettingsIPCMessage.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Microsoft.PowerToys.Settings.UI.Library -{ - public class FancyZonesSettingsIPCMessage - { - [JsonPropertyName("powertoys")] - public SndFancyZonesSettings Powertoys { get; set; } - - public FancyZonesSettingsIPCMessage() - { - } - - public FancyZonesSettingsIPCMessage(SndFancyZonesSettings settings) - { - Powertoys = settings; - } - - public string ToJsonString() - { - return JsonSerializer.Serialize(this); - } - } -} diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/SndFancyZonesSettings.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/SndFancyZonesSettings.cs deleted file mode 100644 index aaf391b2c5..0000000000 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/SndFancyZonesSettings.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Text.Json; - -namespace Microsoft.PowerToys.Settings.UI.Library -{ - public class SndFancyZonesSettings - { - public FancyZonesSettings FancyZones { get; set; } - - public SndFancyZonesSettings() - { - } - - public SndFancyZonesSettings(FancyZonesSettings settings) - { - FancyZones = settings; - } - - public string ToJsonString() - { - return JsonSerializer.Serialize(this); - } - } -} diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs index 82d6497e5d..c990b1376e 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation +// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -14,6 +14,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels { public class FancyZonesViewModel : Observable { + private ISettingsUtils SettingsUtils { get; set; } + private GeneralSettings GeneralSettingsConfig { get; set; } private const string ModuleName = FancyZonesSettings.ModuleName; @@ -39,8 +41,15 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels Positional = 2, } - public FancyZonesViewModel(ISettingsRepository settingsRepository, ISettingsRepository moduleSettingsRepository, Func ipcMSGCallBackFunc, string configFileSubfolder = "") + public FancyZonesViewModel(SettingsUtils settingsUtils, ISettingsRepository settingsRepository, ISettingsRepository moduleSettingsRepository, Func ipcMSGCallBackFunc, string configFileSubfolder = "") { + if (settingsUtils == null) + { + throw new ArgumentNullException(nameof(settingsUtils)); + } + + SettingsUtils = settingsUtils; + // To obtain the general settings configurations of PowerToys Settings. if (settingsRepository == null) { @@ -635,12 +644,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels public void NotifyPropertyChanged([CallerMemberName] string propertyName = null) { OnPropertyChanged(propertyName); - if (SendConfigMSG != null) - { - SndFancyZonesSettings outsettings = new SndFancyZonesSettings(Settings); - SndModuleSettings ipcMessage = new SndModuleSettings(outsettings); - SendConfigMSG(ipcMessage.ToJsonString()); - } + SettingsUtils.SaveSettings(Settings.ToJsonString(), GetSettingsSubPath()); } private static string ToRGBHex(string color) diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/FancyZones.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/FancyZones.cs index 5238f8fc18..19895b3bce 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/FancyZones.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.UnitTests/ViewModelTests/FancyZones.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation +// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -43,7 +43,7 @@ namespace ViewModelTests var fancyZonesRepository = new BackCompatTestProperties.MockSettingsRepository(mockSettingsUtils); // Initialise View Model with test Config files - FancyZonesViewModel viewModel = new FancyZonesViewModel(generalSettingsRepository, fancyZonesRepository, ColorPickerIsEnabledByDefault_IPC); + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils, generalSettingsRepository, fancyZonesRepository, ColorPickerIsEnabledByDefault_IPC); // Verify that the old settings persisted Assert.AreEqual(originalGeneralSettings.Enabled.FancyZones, viewModel.IsEnabled); @@ -85,6 +85,8 @@ namespace ViewModelTests private Mock mockFancyZonesSettingsUtils; + private Func sendMockIPCConfigMSG = msg => { return 0; }; + [TestInitialize] public void SetUpStubSettingUtils() { @@ -95,6 +97,8 @@ namespace ViewModelTests [TestMethod] public void IsEnabledShouldDisableModuleWhenSuccessful() { + Mock mockSettingsUtils = new Mock(); + Func sendMockIPCConfigMSG = msg => { OutGoingGeneralSettings snd = JsonSerializer.Deserialize(msg); @@ -103,7 +107,7 @@ namespace ViewModelTests }; // arrange - FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); Assert.IsTrue(viewModel.IsEnabled); // check if the module is enabled. // act @@ -113,323 +117,405 @@ namespace ViewModelTests [TestMethod] public void ShiftDragShouldSetValue2FalseWhenSuccessful() { - // Assert - Func sendMockIPCConfigMSG = msg => - { - FancyZonesSettingsIPCMessage snd = JsonSerializer.Deserialize(msg); - Assert.IsFalse(snd.Powertoys.FancyZones.Properties.FancyzonesShiftDrag.Value); - return 0; - }; + Mock mockSettingsUtils = new Mock(); // arrange - FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); Assert.IsTrue(viewModel.ShiftDrag); // check if value was initialized to false. // act viewModel.ShiftDrag = false; + + // assert + var expected = viewModel.ShiftDrag; + var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesShiftDrag.Value; + Assert.AreEqual(expected, actual); } [TestMethod] public void OverrideSnapHotkeysShouldSetValue2TrueWhenSuccessful() { - // Assert - Func sendMockIPCConfigMSG = msg => - { - FancyZonesSettingsIPCMessage snd = JsonSerializer.Deserialize(msg); - Assert.IsTrue(snd.Powertoys.FancyZones.Properties.FancyzonesOverrideSnapHotkeys.Value); - return 0; - }; + Mock mockSettingsUtils = new Mock(); // arrange - FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); Assert.IsFalse(viewModel.OverrideSnapHotkeys); // check if value was initialized to false. // act viewModel.OverrideSnapHotkeys = true; + + // assert + var expected = viewModel.OverrideSnapHotkeys; + var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesOverrideSnapHotkeys.Value; + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void MoveWindowsAcrossMonitorsShouldSetValue2TrueWhenSuccessful() + { + Mock mockSettingsUtils = new Mock(); + + // arrange + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + Assert.IsFalse(viewModel.MoveWindowsAcrossMonitors); // check if value was initialized to false. + + // act + viewModel.MoveWindowsAcrossMonitors = true; + + // assert + var expected = viewModel.MoveWindowsAcrossMonitors; + var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesMoveWindowsAcrossMonitors.Value; + Assert.AreEqual(expected, actual); } [TestMethod] public void MoveWindowsBasedOnPositionShouldSetValue2TrueWhenSuccessful() { - // Assert - Func sendMockIPCConfigMSG = msg => - { - FancyZonesSettingsIPCMessage snd = JsonSerializer.Deserialize(msg); - Assert.IsTrue(snd.Powertoys.FancyZones.Properties.FancyzonesMoveWindowsBasedOnPosition.Value); - return 0; - }; + Mock mockSettingsUtils = new Mock(); // arrange - FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); Assert.IsFalse(viewModel.MoveWindowsBasedOnPosition); // check if value was initialized to false. + Assert.IsTrue(viewModel.MoveWindowsBasedOnZoneIndex); // check if value was initialized to true. // act viewModel.MoveWindowsBasedOnPosition = true; + + // assert + var basedOnPositionExpected = viewModel.MoveWindowsBasedOnPosition; + var basedOnPositionActual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesMoveWindowsBasedOnPosition.Value; + Assert.AreEqual(basedOnPositionExpected, basedOnPositionActual); + + var basedOnZoneIndexExpected = viewModel.MoveWindowsBasedOnZoneIndex; + var basedOnZoneIndexActual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesMoveWindowsBasedOnPosition.Value; + Assert.AreNotEqual(basedOnZoneIndexExpected, basedOnZoneIndexActual); } [TestMethod] - public void ZoneSetChangeFlashZonesShouldSetValue2FalseWhenSuccessful() + public void QuickLayoutSwitchShouldSetValue2FalseWhenSuccessful() { - // Assert - Func sendMockIPCConfigMSG = msg => - { - FancyZonesSettingsIPCMessage snd = JsonSerializer.Deserialize(msg); - Assert.IsTrue(snd.Powertoys.FancyZones.Properties.FancyzonesMakeDraggedWindowTransparent.Value); - return 0; - }; + Mock mockSettingsUtils = new Mock(); // arrange - FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + Assert.IsTrue(viewModel.QuickLayoutSwitch); // check if value was initialized to true. + + // act + viewModel.QuickLayoutSwitch = false; + + // assert + var expected = viewModel.QuickLayoutSwitch; + var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesQuickLayoutSwitch.Value; + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void FlashZonesOnQuickSwitchShouldSetValue2FalseWhenSuccessful() + { + Mock mockSettingsUtils = new Mock(); + + // arrange + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + Assert.IsTrue(viewModel.FlashZonesOnQuickSwitch); // check if value was initialized to true. + + // act + viewModel.FlashZonesOnQuickSwitch = false; + + // assert + var expected = viewModel.FlashZonesOnQuickSwitch; + var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesFlashZonesOnQuickSwitch.Value; + Assert.AreEqual(expected, actual); + } + + /* + * Temporarily commented out + * + [TestMethod] + public void MakeDraggedWindowsTransparentShouldSetValue2TrueWhenSuccessful() + { + Mock mockSettingsUtils = new Mock(); + + // arrange + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); Assert.IsFalse(viewModel.MakeDraggedWindowsTransparent); // check if value was initialized to false. // act viewModel.MakeDraggedWindowsTransparent = true; - } + + // assert + var expected = viewModel.MakeDraggedWindowsTransparent; + var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesShiftDrag.Value; + Assert.AreEqual(expected, actual); + }*/ [TestMethod] public void MouseSwitchShouldSetValue2TrueWhenSuccessful() { - // Assert - Func sendMockIPCConfigMSG = msg => - { - FancyZonesSettingsIPCMessage snd = JsonSerializer.Deserialize(msg); - Assert.IsTrue(snd.Powertoys.FancyZones.Properties.FancyzonesMouseSwitch.Value); - return 0; - }; + Mock mockSettingsUtils = new Mock(); // arrange - FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); Assert.IsFalse(viewModel.MouseSwitch); // check if value was initialized to false. // act viewModel.MouseSwitch = true; + + // assert + var expected = viewModel.MouseSwitch; + var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesMouseSwitch.Value; + Assert.AreEqual(expected, actual); } [TestMethod] public void DisplayChangeMoveWindowsShouldSetValue2TrueWhenSuccessful() { - // Assert - Func sendMockIPCConfigMSG = msg => - { - FancyZonesSettingsIPCMessage snd = JsonSerializer.Deserialize(msg); - Assert.IsTrue(snd.Powertoys.FancyZones.Properties.FancyzonesDisplayChangeMoveWindows.Value); - return 0; - }; + Mock mockSettingsUtils = new Mock(); // arrange - FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); Assert.IsFalse(viewModel.DisplayChangeMoveWindows); // check if value was initialized to false. // act viewModel.DisplayChangeMoveWindows = true; + + // assert + var expected = viewModel.DisplayChangeMoveWindows; + var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesDisplayChangeMoveWindows.Value; + Assert.AreEqual(expected, actual); } [TestMethod] public void ZoneSetChangeMoveWindowsShouldSetValue2TrueWhenSuccessful() { - // Assert - Func sendMockIPCConfigMSG = msg => - { - FancyZonesSettingsIPCMessage snd = JsonSerializer.Deserialize(msg); - Assert.IsTrue(snd.Powertoys.FancyZones.Properties.FancyzonesZoneSetChangeMoveWindows.Value); - return 0; - }; + Mock mockSettingsUtils = new Mock(); // arrange - FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); Assert.IsFalse(viewModel.ZoneSetChangeMoveWindows); // check if value was initialized to false. // act viewModel.ZoneSetChangeMoveWindows = true; + + // assert + var expected = viewModel.ZoneSetChangeMoveWindows; + var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesZoneSetChangeMoveWindows.Value; + Assert.AreEqual(expected, actual); } [TestMethod] public void AppLastZoneMoveWindowsShouldSetValue2TrueWhenSuccessful() { - // Assert - Func sendMockIPCConfigMSG = msg => - { - FancyZonesSettingsIPCMessage snd = JsonSerializer.Deserialize(msg); - Assert.IsTrue(snd.Powertoys.FancyZones.Properties.FancyzonesAppLastZoneMoveWindows.Value); - return 0; - }; + Mock mockSettingsUtils = new Mock(); // arrange - FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); Assert.IsFalse(viewModel.AppLastZoneMoveWindows); // check if value was initialized to false. // act viewModel.AppLastZoneMoveWindows = true; + + // assert + var expected = viewModel.AppLastZoneMoveWindows; + var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesAppLastZoneMoveWindows.Value; + Assert.AreEqual(expected, actual); } + [TestMethod] public void OpenWindowOnActiveMonitorShouldSetValue2TrueWhenSuccessful() { - // Assert - Func sendMockIPCConfigMSG = msg => - { - FancyZonesSettingsIPCMessage snd = JsonSerializer.Deserialize(msg); - Assert.IsTrue(snd.Powertoys.FancyZones.Properties.FancyzonesOpenWindowOnActiveMonitor.Value); - return 0; - }; + Mock mockSettingsUtils = new Mock(); // arrange - FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); Assert.IsFalse(viewModel.OpenWindowOnActiveMonitor); // check if value was initialized to false. // act viewModel.OpenWindowOnActiveMonitor = true; + + // assert + var expected = viewModel.OpenWindowOnActiveMonitor; + var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesOpenWindowOnActiveMonitor.Value; + Assert.AreEqual(expected, actual); } [TestMethod] public void RestoreSizeShouldSetValue2TrueWhenSuccessful() { - // Assert - Func sendMockIPCConfigMSG = msg => - { - FancyZonesSettingsIPCMessage snd = JsonSerializer.Deserialize(msg); - Assert.IsTrue(snd.Powertoys.FancyZones.Properties.FancyzonesRestoreSize.Value); - return 0; - }; + Mock mockSettingsUtils = new Mock(); // arrange - FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); Assert.IsFalse(viewModel.RestoreSize); // check if value was initialized to false. // act viewModel.RestoreSize = true; + + // assert + var expected = viewModel.RestoreSize; + var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesRestoreSize.Value; + Assert.AreEqual(expected, actual); } [TestMethod] public void UseCursorPosEditorStartupScreenShouldSetValue2FalseWhenSuccessful() { - // Assert - Func sendMockIPCConfigMSG = msg => - { - FancyZonesSettingsIPCMessage snd = JsonSerializer.Deserialize(msg); - Assert.IsTrue(snd.Powertoys.FancyZones.Properties.UseCursorposEditorStartupscreen.Value); - return 0; - }; + Mock mockSettingsUtils = new Mock(); // arrange - FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); Assert.IsTrue(viewModel.UseCursorPosEditorStartupScreen); // check if value was initialized to false. // act viewModel.UseCursorPosEditorStartupScreen = true; + + // assert + var expected = viewModel.UseCursorPosEditorStartupScreen; + var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.UseCursorposEditorStartupscreen.Value; + Assert.AreEqual(expected, actual); } [TestMethod] public void ShowOnAllMonitorsShouldSetValue2TrueWhenSuccessful() { - // Assert - Func sendMockIPCConfigMSG = msg => - { - FancyZonesSettingsIPCMessage snd = JsonSerializer.Deserialize(msg); - Assert.IsTrue(snd.Powertoys.FancyZones.Properties.FancyzonesShowOnAllMonitors.Value); - return 0; - }; + Mock mockSettingsUtils = new Mock(); // arrange - FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); Assert.IsFalse(viewModel.ShowOnAllMonitors); // check if value was initialized to false. // act viewModel.ShowOnAllMonitors = true; + + // assert + var expected = viewModel.ShowOnAllMonitors; + var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesShowOnAllMonitors.Value; + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void SpanZonesAcrossMonitorsShouldSetValue2TrueWhenSuccessful() + { + Mock mockSettingsUtils = new Mock(); + + // arrange + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + Assert.IsFalse(viewModel.SpanZonesAcrossMonitors); // check if value was initialized to false. + + // act + viewModel.SpanZonesAcrossMonitors = true; + + // assert + var expected = viewModel.SpanZonesAcrossMonitors; + var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesSpanZonesAcrossMonitors.Value; + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void OverlappingZonesAlgorithmIndexShouldSetValue2AnotherWhenSuccessful() + { + Mock mockSettingsUtils = new Mock(); + + // arrange + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + Assert.AreEqual(0, viewModel.OverlappingZonesAlgorithmIndex); // check if value was initialized to false. + + // act + viewModel.OverlappingZonesAlgorithmIndex = 1; + + // assert + var expected = viewModel.OverlappingZonesAlgorithmIndex; + var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesOverlappingZonesAlgorithm.Value; + Assert.AreEqual(expected, actual); } [TestMethod] public void ZoneHighlightColorShouldSetColorValue2WhiteWhenSuccessful() { - // Assert - Func sendMockIPCConfigMSG = msg => - { - FancyZonesSettingsIPCMessage snd = JsonSerializer.Deserialize(msg); - Assert.AreEqual("#FFFFFF", snd.Powertoys.FancyZones.Properties.FancyzonesZoneHighlightColor.Value); - return 0; - }; + Mock mockSettingsUtils = new Mock(); // arrange - FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); Assert.AreEqual(ConfigDefaults.DefaultFancyZonesZoneHighlightColor, viewModel.ZoneHighlightColor); // act viewModel.ZoneHighlightColor = "#FFFFFF"; + + // assert + var expected = viewModel.ZoneHighlightColor; + var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesZoneHighlightColor.Value; + Assert.AreEqual(expected, actual); } [TestMethod] public void ZoneBorderColorShouldSetColorValue2WhiteWhenSuccessful() { - // Assert - Func sendMockIPCConfigMSG = msg => - { - FancyZonesSettingsIPCMessage snd = JsonSerializer.Deserialize(msg); - Assert.AreEqual("#FFFFFF", snd.Powertoys.FancyZones.Properties.FancyzonesBorderColor.Value); - return 0; - }; + Mock mockSettingsUtils = new Mock(); // arrange - FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); Assert.AreEqual(ConfigDefaults.DefaultFancyzonesBorderColor, viewModel.ZoneBorderColor); // act viewModel.ZoneBorderColor = "#FFFFFF"; + + // assert + var expected = viewModel.ZoneBorderColor; + var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesBorderColor.Value; + Assert.AreEqual(expected, actual); } [TestMethod] public void ZoneInActiveColorShouldSetColorValue2WhiteWhenSuccessful() { - // Assert - Func sendMockIPCConfigMSG = msg => - { - FancyZonesSettingsIPCMessage snd = JsonSerializer.Deserialize(msg); - Assert.AreEqual("#FFFFFF", snd.Powertoys.FancyZones.Properties.FancyzonesInActiveColor.Value); - return 0; - }; + Mock mockSettingsUtils = new Mock(); // arrange - FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); Assert.AreEqual(ConfigDefaults.DefaultFancyZonesInActiveColor, viewModel.ZoneInActiveColor); // act viewModel.ZoneInActiveColor = "#FFFFFF"; + + // assert + var expected = viewModel.ZoneInActiveColor; + var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesInActiveColor.Value; + Assert.AreEqual(expected, actual); } [TestMethod] public void ExcludedAppsShouldSetColorValue2WhiteWhenSuccessful() { - // Assert - Func sendMockIPCConfigMSG = msg => - { - FancyZonesSettingsIPCMessage snd = JsonSerializer.Deserialize(msg); - Assert.AreEqual("Sample", snd.Powertoys.FancyZones.Properties.FancyzonesExcludedApps.Value); - return 0; - }; + Mock mockSettingsUtils = new Mock(); // arrange - FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); Assert.AreEqual(string.Empty, viewModel.ExcludedApps); // act viewModel.ExcludedApps = "Sample"; + + // assert + var expected = viewModel.ExcludedApps; + var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesExcludedApps.Value; + Assert.AreEqual(expected, actual); } [TestMethod] public void HighlightOpacityShouldSetOpacityValueTo60WhenSuccessful() { - // Assert - Func sendMockIPCConfigMSG = msg => - { - FancyZonesSettingsIPCMessage snd = JsonSerializer.Deserialize(msg); - Assert.AreEqual(60, snd.Powertoys.FancyZones.Properties.FancyzonesHighlightOpacity.Value); - return 0; - }; + Mock mockSettingsUtils = new Mock(); // arrange - FancyZonesViewModel viewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); + FancyZonesViewModel viewModel = new FancyZonesViewModel(mockSettingsUtils.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object), sendMockIPCConfigMSG, FancyZonesTestFolderName); Assert.AreEqual(50, viewModel.HighlightOpacity); // act viewModel.HighlightOpacity = 60; + + // assert + var expected = viewModel.HighlightOpacity; + var actual = SettingsRepository.GetInstance(mockFancyZonesSettingsUtils.Object).SettingsConfig.Properties.FancyzonesHighlightOpacity.Value; + Assert.AreEqual(expected, actual); } } } diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml.cs index f8687cfd1c..ea85cd2c85 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/FancyZonesPage.xaml.cs @@ -1,9 +1,8 @@ -// Copyright (c) Microsoft Corporation +// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.PowerToys.Settings.UI.Library; -using Microsoft.PowerToys.Settings.UI.Library.Utilities; using Microsoft.PowerToys.Settings.UI.Library.ViewModels; using Windows.UI.Xaml.Controls; @@ -17,7 +16,7 @@ namespace Microsoft.PowerToys.Settings.UI.Views { InitializeComponent(); var settingsUtils = new SettingsUtils(); - ViewModel = new FancyZonesViewModel(SettingsRepository.GetInstance(settingsUtils), SettingsRepository.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage); + ViewModel = new FancyZonesViewModel(settingsUtils, SettingsRepository.GetInstance(settingsUtils), SettingsRepository.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage); DataContext = ViewModel; } } diff --git a/tools/BugReportTool/BugReportTool/EventViewer.cpp b/tools/BugReportTool/BugReportTool/EventViewer.cpp index 5b35224234..d2f0fa279b 100644 --- a/tools/BugReportTool/BugReportTool/EventViewer.cpp +++ b/tools/BugReportTool/BugReportTool/EventViewer.cpp @@ -18,6 +18,7 @@ namespace L"ColorPickerUI.exe", L"PowerToys.Awake.exe" L"FancyZonesEditor.exe", + L"PowerToys.FancyZones.exe", L"PowerToys.KeyboardManagerEngine.exe", L"PowerToys.KeyboardManagerEditor.exe", L"PowerLauncher.exe", diff --git a/tools/BugReportTool/BugReportTool/RegistryUtils.cpp b/tools/BugReportTool/BugReportTool/RegistryUtils.cpp index 63c093db0c..d1a1fc5ffb 100644 --- a/tools/BugReportTool/BugReportTool/RegistryUtils.cpp +++ b/tools/BugReportTool/BugReportTool/RegistryUtils.cpp @@ -163,7 +163,8 @@ void ReportCompatibilityTab(HKEY key, wofstream& report) L"PowerToys.exe", L"ColorPickerUI.exe", L"FancyZonesEditor.exe", - L"PowerToys.KeyboardManagerEditor.exe", + L"PowerToys.FancyZones.exe", + L"PowerToys.KeyboardManagerEngine.exe", L"PowerToys.KeyboardManagerEditor.exe", L"PowerLauncher.exe", L"PowerToys.ShortcutGuide.exe"