Merge remote-tracking branch 'origin/main' into dev/snickler/net8-upgrade

This commit is contained in:
Jeremy Sinclair 2023-09-21 21:09:08 -04:00
commit 2a9788bbbe
43 changed files with 1419 additions and 628 deletions

View File

@ -40,7 +40,7 @@
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.0.3" /> <PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.0.3" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.756" /> <PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.756" />
<PackageVersion Include="Microsoft.Windows.SDK.Contracts" Version="10.0.19041.1" /> <PackageVersion Include="Microsoft.Windows.SDK.Contracts" Version="10.0.19041.1" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.4.230822000" /> <PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.4.230913002" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" /> <PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" /> <PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
<PackageVersion Include="ModernWpfUI" Version="0.9.4" /> <PackageVersion Include="ModernWpfUI" Version="0.9.4" />

View File

@ -341,7 +341,7 @@ SOFTWARE.
- Microsoft.Windows.CsWinRT 2.0.3 - Microsoft.Windows.CsWinRT 2.0.3
- Microsoft.Windows.SDK.BuildTools 10.0.22621.756 - Microsoft.Windows.SDK.BuildTools 10.0.22621.756
- Microsoft.Windows.SDK.Contracts 10.0.19041.1 - Microsoft.Windows.SDK.Contracts 10.0.19041.1
- Microsoft.WindowsAppSDK 1.4.230822000 - Microsoft.WindowsAppSDK 1.4.230913002
- Microsoft.Xaml.Behaviors.WinUI.Managed 2.0.9 - Microsoft.Xaml.Behaviors.WinUI.Managed 2.0.9
- Microsoft.Xaml.Behaviors.Wpf 1.1.39 - Microsoft.Xaml.Behaviors.Wpf 1.1.39
- ModernWpfUI 0.9.4 - ModernWpfUI 0.9.4

View File

@ -199,7 +199,7 @@
<MenuFlyoutItem <MenuFlyoutItem
x:Uid="MoveUp" x:Uid="MoveUp"
Click="ReorderButtonUp_Click" Click="ReorderButtonUp_Click"
IsEnabled="{Binding DataContext.Filtered, ElementName=Page, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}"> IsEnabled="{x:Bind ViewModel.Filtered, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<MenuFlyoutItem.Icon> <MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE74A;" /> <FontIcon Glyph="&#xE74A;" />
</MenuFlyoutItem.Icon> </MenuFlyoutItem.Icon>
@ -207,7 +207,7 @@
<MenuFlyoutItem <MenuFlyoutItem
x:Uid="MoveDown" x:Uid="MoveDown"
Click="ReorderButtonDown_Click" Click="ReorderButtonDown_Click"
IsEnabled="{Binding DataContext.Filtered, ElementName=Page, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}"> IsEnabled="{x:Bind ViewModel.Filtered, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
<MenuFlyoutItem.Icon> <MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE74B;" /> <FontIcon Glyph="&#xE74B;" />
</MenuFlyoutItem.Icon> </MenuFlyoutItem.Icon>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230822000\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230822000\build\native\Microsoft.WindowsAppSDK.props')" /> <Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230913002\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230913002\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.props')" /> <Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.props')" /> <Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals"> <PropertyGroup Label="Globals">
@ -147,7 +147,7 @@
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" /> <Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.targets')" /> <Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.targets')" /> <Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230822000\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230822000\build\native\Microsoft.WindowsAppSDK.targets')" /> <Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230913002\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230913002\build\native\Microsoft.WindowsAppSDK.targets')" />
</ImportGroup> </ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup> <PropertyGroup>
@ -158,7 +158,7 @@
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.targets'))" /> <Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.props'))" /> <Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.targets'))" /> <Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230822000\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230822000\build\native\Microsoft.WindowsAppSDK.props'))" /> <Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230913002\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230913002\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230822000\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230822000\build\native\Microsoft.WindowsAppSDK.targets'))" /> <Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230913002\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230913002\build\native\Microsoft.WindowsAppSDK.targets'))" />
</Target> </Target>
</Project> </Project>

View File

@ -3,5 +3,5 @@
<package id="Microsoft.Windows.CppWinRT" version="2.0.221104.6" targetFramework="native" /> <package id="Microsoft.Windows.CppWinRT" version="2.0.221104.6" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.220914.1" targetFramework="native" /> <package id="Microsoft.Windows.ImplementationLibrary" version="1.0.220914.1" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.755" targetFramework="native" /> <package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.755" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.4.230822000" targetFramework="native" /> <package id="Microsoft.WindowsAppSDK" version="1.4.230913002" targetFramework="native" />
</packages> </packages>

View File

@ -6,6 +6,7 @@
#include <FancyZonesLib/on_thread_executor.h> #include <FancyZonesLib/on_thread_executor.h>
#include <FancyZonesLib/Settings.h> #include <FancyZonesLib/Settings.h>
#include <FancyZonesLib/VirtualDesktop.h> #include <FancyZonesLib/VirtualDesktop.h>
#include <FancyZonesLib/WorkArea.h>
#include <FancyZonesLib/util.h> #include <FancyZonesLib/util.h>
#include <common/Display/dpi_aware.h> #include <common/Display/dpi_aware.h>
@ -36,6 +37,31 @@ namespace JsonUtils
int monitorHeight{}; int monitorHeight{};
bool isSelected = false; bool isSelected = false;
bool FillFromWorkArea(const WorkArea* const workArea)
{
const auto virtualDesktopIdStr = FancyZonesUtils::GuidToString(workArea->UniqueId().virtualDesktopId);
if (!virtualDesktopIdStr)
{
return false;
}
const auto& monitorId = workArea->UniqueId().monitorId;
monitorName = monitorId.deviceId.id;
monitorInstanceId = monitorId.deviceId.instanceId;
monitorNumber = monitorId.deviceId.number;
monitorSerialNumber = monitorId.serialNumber;
const auto& rect = workArea->GetWorkAreaRect();
top = rect.top();
left = rect.left();
workAreaWidth = rect.width();
workAreaHeight = rect.height();
virtualDesktop = virtualDesktopIdStr.value();
return true;
}
static json::JsonObject ToJson(const MonitorInfo& monitor) static json::JsonObject ToJson(const MonitorInfo& monitor)
{ {
json::JsonObject result{}; json::JsonObject result{};
@ -84,21 +110,8 @@ namespace JsonUtils
}; };
} }
bool EditorParameters::Save() noexcept bool EditorParameters::Save(const WorkAreaConfiguration& configuration, OnThreadExecutor& dpiUnawareThread) noexcept
{ {
const auto virtualDesktopIdStr = FancyZonesUtils::GuidToString(VirtualDesktop::instance().GetCurrentVirtualDesktopId());
if (!virtualDesktopIdStr)
{
Logger::error(L"Save editor params: invalid virtual desktop id");
return false;
}
OnThreadExecutor dpiUnawareThread;
dpiUnawareThread.submit(OnThreadExecutor::task_t{ [] {
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE);
SetThreadDpiHostingBehavior(DPI_HOSTING_BEHAVIOR_MIXED);
} }).wait();
const bool spanZonesAcrossMonitors = FancyZonesSettings::settings().spanZonesAcrossMonitors; const bool spanZonesAcrossMonitors = FancyZonesSettings::settings().spanZonesAcrossMonitors;
JsonUtils::EditorArgs argsJson; JsonUtils::EditorArgs argsJson;
@ -107,23 +120,31 @@ bool EditorParameters::Save() noexcept
if (spanZonesAcrossMonitors) if (spanZonesAcrossMonitors)
{ {
RECT combinedWorkArea; const auto& workArea = configuration.GetWorkArea(nullptr);
dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&]() { if (!workArea)
combinedWorkArea = FancyZonesUtils::GetAllMonitorsCombinedRect<&MONITORINFOEX::rcWork>(); {
return false;
} }).wait(); }
RECT combinedMonitorArea = FancyZonesUtils::GetAllMonitorsCombinedRect<&MONITORINFOEX::rcMonitor>();
JsonUtils::MonitorInfo monitorJson; JsonUtils::MonitorInfo monitorJson;
monitorJson.monitorName = ZonedWindowProperties::MultiMonitorName; if (!monitorJson.FillFromWorkArea(workArea))
monitorJson.monitorInstanceId = ZonedWindowProperties::MultiMonitorInstance; {
monitorJson.monitorNumber = 0; return false;
monitorJson.virtualDesktop = virtualDesktopIdStr.value(); }
RECT combinedWorkArea;
dpiUnawareThread.submit(OnThreadExecutor::task_t{
[&]() {
combinedWorkArea = FancyZonesUtils::GetAllMonitorsCombinedRect<&MONITORINFOEX::rcWork>();
} }).wait();
RECT combinedMonitorArea = FancyZonesUtils::GetAllMonitorsCombinedRect<&MONITORINFOEX::rcMonitor>();
// use dpi-unaware values
monitorJson.top = combinedWorkArea.top; monitorJson.top = combinedWorkArea.top;
monitorJson.left = combinedWorkArea.left; monitorJson.left = combinedWorkArea.left;
monitorJson.workAreaWidth = combinedWorkArea.right - combinedWorkArea.left; monitorJson.workAreaWidth = combinedWorkArea.right - combinedWorkArea.left;
monitorJson.workAreaHeight = combinedWorkArea.bottom - combinedWorkArea.top; monitorJson.workAreaHeight = combinedWorkArea.bottom - combinedWorkArea.top;
monitorJson.monitorWidth = combinedMonitorArea.right - combinedMonitorArea.left; monitorJson.monitorWidth = combinedMonitorArea.right - combinedMonitorArea.left;
monitorJson.monitorHeight = combinedMonitorArea.bottom - combinedMonitorArea.top; monitorJson.monitorHeight = combinedMonitorArea.bottom - combinedMonitorArea.top;
monitorJson.isSelected = true; monitorJson.isSelected = true;
@ -133,8 +154,6 @@ bool EditorParameters::Save() noexcept
} }
else else
{ {
auto monitors = MonitorUtils::IdentifyMonitors();
HMONITOR targetMonitor{}; HMONITOR targetMonitor{};
if (FancyZonesSettings::settings().use_cursorpos_editor_startupscreen) if (FancyZonesSettings::settings().use_cursorpos_editor_startupscreen)
{ {
@ -153,9 +172,24 @@ bool EditorParameters::Save() noexcept
return false; return false;
} }
for (auto& monitorData : monitors) const auto& config = configuration.GetAllWorkAreas();
for (auto& [monitor, workArea] : config)
{ {
HMONITOR monitor = monitorData.monitor; JsonUtils::MonitorInfo monitorJson;
if (!monitorJson.FillFromWorkArea(workArea.get()))
{
continue;
}
monitorJson.isSelected = monitor == targetMonitor; /* Is monitor selected for the main editor window opening */
UINT dpi = 0;
if (DPIAware::GetScreenDPIForMonitor(monitor, dpi) != S_OK)
{
continue;
}
monitorJson.dpi = dpi;
MONITORINFOEX monitorInfo{}; MONITORINFOEX monitorInfo{};
dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] { dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] {
@ -166,31 +200,6 @@ bool EditorParameters::Save() noexcept
} }
} }).wait(); } }).wait();
JsonUtils::MonitorInfo monitorJson;
if (monitor == targetMonitor)
{
monitorJson.isSelected = true; /* Is monitor selected for the main editor window opening */
}
monitorJson.monitorName = monitorData.deviceId.id;
monitorJson.monitorInstanceId = monitorData.deviceId.instanceId;
monitorJson.monitorNumber = monitorData.deviceId.number;
monitorJson.monitorSerialNumber = monitorData.serialNumber;
monitorJson.virtualDesktop = virtualDesktopIdStr.value();
UINT dpi = 0;
if (DPIAware::GetScreenDPIForMonitor(monitor, dpi) != S_OK)
{
continue;
}
monitorJson.dpi = dpi;
monitorJson.top = monitorInfo.rcWork.top;
monitorJson.left = monitorInfo.rcWork.left;
monitorJson.workAreaWidth = monitorInfo.rcWork.right - monitorInfo.rcWork.left;
monitorJson.workAreaHeight = monitorInfo.rcWork.bottom - monitorInfo.rcWork.top;
float width = static_cast<float>(monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left); float width = static_cast<float>(monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left);
float height = static_cast<float>(monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top); float height = static_cast<float>(monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top);
DPIAware::Convert(monitor, width, height); DPIAware::Convert(monitor, width, height);
@ -198,6 +207,12 @@ bool EditorParameters::Save() noexcept
monitorJson.monitorWidth = static_cast<int>(std::roundf(width)); monitorJson.monitorWidth = static_cast<int>(std::roundf(width));
monitorJson.monitorHeight = static_cast<int>(std::roundf(height)); monitorJson.monitorHeight = static_cast<int>(std::roundf(height));
// use dpi-unaware values
monitorJson.top = monitorInfo.rcWork.top;
monitorJson.left = monitorInfo.rcWork.left;
monitorJson.workAreaWidth = monitorInfo.rcWork.right - monitorInfo.rcWork.left;
monitorJson.workAreaHeight = monitorInfo.rcWork.bottom - monitorInfo.rcWork.top;
argsJson.monitors.emplace_back(std::move(monitorJson)); argsJson.monitors.emplace_back(std::move(monitorJson));
} }
} }

View File

@ -1,5 +1,8 @@
#pragma once #pragma once
#include <FancyZonesLib/on_thread_executor.h>
#include <FancyZonesLib/WorkAreaConfiguration.h>
namespace NonLocalizable namespace NonLocalizable
{ {
namespace EditorParametersIds namespace EditorParametersIds
@ -26,5 +29,5 @@ namespace NonLocalizable
class EditorParameters class EditorParameters
{ {
public: public:
static bool Save() noexcept; static bool Save(const WorkAreaConfiguration& configuration, OnThreadExecutor& dpiUnawareThread) noexcept;
}; };

View File

@ -15,20 +15,22 @@
#include <FancyZonesLib/FancyZonesData/AppZoneHistory.h> #include <FancyZonesLib/FancyZonesData/AppZoneHistory.h>
#include <FancyZonesLib/FancyZonesData/CustomLayouts.h> #include <FancyZonesLib/FancyZonesData/CustomLayouts.h>
#include <FancyZonesLib/FancyZonesData/DefaultLayouts.h> #include <FancyZonesLib/FancyZonesData/DefaultLayouts.h>
#include <FancyZonesLib/FancyZonesData/LastUsedVirtualDesktop.h>
#include <FancyZonesLib/FancyZonesData/LayoutHotkeys.h> #include <FancyZonesLib/FancyZonesData/LayoutHotkeys.h>
#include <FancyZonesLib/FancyZonesData/LayoutTemplates.h> #include <FancyZonesLib/FancyZonesData/LayoutTemplates.h>
#include <FancyZonesLib/FancyZonesWindowProcessing.h> #include <FancyZonesLib/FancyZonesWindowProcessing.h>
#include <FancyZonesLib/FancyZonesWindowProperties.h> #include <FancyZonesLib/FancyZonesWindowProperties.h>
#include <FancyZonesLib/FancyZonesWinHookEventIDs.h> #include <FancyZonesLib/FancyZonesWinHookEventIDs.h>
#include <FancyZonesLib/MonitorUtils.h> #include <FancyZonesLib/MonitorUtils.h>
#include <FancyZonesLib/MonitorWorkAreaMap.h>
#include <FancyZonesLib/on_thread_executor.h> #include <FancyZonesLib/on_thread_executor.h>
#include <FancyZonesLib/Settings.h> #include <FancyZonesLib/Settings.h>
#include <FancyZonesLib/SettingsObserver.h> #include <FancyZonesLib/SettingsObserver.h>
#include <FancyZonesLib/trace.h> #include <FancyZonesLib/trace.h>
#include <FancyZonesLib/VirtualDesktop.h>
#include <FancyZonesLib/WindowKeyboardSnap.h> #include <FancyZonesLib/WindowKeyboardSnap.h>
#include <FancyZonesLib/WindowMouseSnap.h> #include <FancyZonesLib/WindowMouseSnap.h>
#include <FancyZonesLib/WorkArea.h> #include <FancyZonesLib/WorkArea.h>
#include <FancyZonesLib/WorkAreaConfiguration.h>
enum class DisplayChangeType enum class DisplayChangeType
{ {
@ -87,6 +89,7 @@ public:
AppliedLayouts::instance().LoadData(); AppliedLayouts::instance().LoadData();
AppZoneHistory::instance().LoadData(); AppZoneHistory::instance().LoadData();
DefaultLayouts::instance().LoadData(); DefaultLayouts::instance().LoadData();
LastUsedVirtualDesktop::instance().LoadData();
} }
// IFancyZones // IFancyZones
@ -169,7 +172,7 @@ private:
HWND m_window{}; HWND m_window{};
std::unique_ptr<WindowMouseSnap> m_windowMouseSnapper{}; std::unique_ptr<WindowMouseSnap> m_windowMouseSnapper{};
WindowKeyboardSnap m_windowKeyboardSnapper{}; WindowKeyboardSnap m_windowKeyboardSnapper{};
MonitorWorkAreaMap m_workAreaHandler; WorkAreaConfiguration m_workAreaConfiguration;
DraggingState m_draggingState; DraggingState m_draggingState;
wil::unique_handle m_terminateEditorEvent; // Handle of FancyZonesEditor.exe we launch and wait on wil::unique_handle m_terminateEditorEvent; // Handle of FancyZonesEditor.exe we launch and wait on
@ -253,6 +256,7 @@ FancyZones::Run() noexcept
} }
}); });
VirtualDesktop::instance().UpdateVirtualDesktopId();
SyncVirtualDesktops(); SyncVirtualDesktops();
// id format of applied-layouts and app-zone-history was changed in 0.60 // id format of applied-layouts and app-zone-history was changed in 0.60
@ -267,7 +271,7 @@ FancyZones::Run() noexcept
IFACEMETHODIMP_(void) IFACEMETHODIMP_(void)
FancyZones::Destroy() noexcept FancyZones::Destroy() noexcept
{ {
m_workAreaHandler.Clear(); m_workAreaConfiguration.Clear();
BufferedPaintUnInit(); BufferedPaintUnInit();
if (m_window) if (m_window)
{ {
@ -289,7 +293,7 @@ FancyZones::VirtualDesktopChanged() noexcept
void FancyZones::MoveSizeStart(HWND window, HMONITOR monitor) void FancyZones::MoveSizeStart(HWND window, HMONITOR monitor)
{ {
m_windowMouseSnapper = WindowMouseSnap::Create(window, m_workAreaHandler.GetAllWorkAreas()); m_windowMouseSnapper = WindowMouseSnap::Create(window, m_workAreaConfiguration.GetAllWorkAreas());
if (m_windowMouseSnapper) if (m_windowMouseSnapper)
{ {
if (FancyZonesSettings::settings().spanZonesAcrossMonitors) if (FancyZonesSettings::settings().spanZonesAcrossMonitors)
@ -329,7 +333,7 @@ void FancyZones::MoveSizeEnd()
bool FancyZones::MoveToAppLastZone(HWND window, HMONITOR monitor) noexcept bool FancyZones::MoveToAppLastZone(HWND window, HMONITOR monitor) noexcept
{ {
const auto& workAreas = m_workAreaHandler.GetAllWorkAreas(); const auto& workAreas = m_workAreaConfiguration.GetAllWorkAreas();
WorkArea* workArea{ nullptr }; WorkArea* workArea{ nullptr };
ZoneIndexSet indexes{}; ZoneIndexSet indexes{};
@ -397,12 +401,6 @@ void FancyZones::WindowCreated(HWND window) noexcept
return; return;
} }
const bool isCandidateForLastKnownZone = FancyZonesWindowUtils::IsCandidateForZoning(window);
if (!isCandidateForLastKnownZone)
{
return;
}
HMONITOR primary = MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY); HMONITOR primary = MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY);
HMONITOR active = primary; HMONITOR active = primary;
@ -523,7 +521,7 @@ void FancyZones::ToggleEditor() noexcept
m_terminateEditorEvent.reset(CreateEvent(nullptr, true, false, nullptr)); m_terminateEditorEvent.reset(CreateEvent(nullptr, true, false, nullptr));
if (!EditorParameters::Save()) if (!EditorParameters::Save(m_workAreaConfiguration, m_dpiUnawareThread))
{ {
Logger::error(L"Failed to save editor startup parameters"); Logger::error(L"Failed to save editor startup parameters");
return; return;
@ -623,11 +621,11 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa
// Check whether Alt is used in the shortcut key combination // Check whether Alt is used in the shortcut key combination
if (GetAsyncKeyState(VK_MENU) & 0x8000) if (GetAsyncKeyState(VK_MENU) & 0x8000)
{ {
m_windowKeyboardSnapper.Extend(foregroundWindow, windowRect, monitor, static_cast<DWORD>(lparam), m_workAreaHandler.GetAllWorkAreas()); m_windowKeyboardSnapper.Extend(foregroundWindow, windowRect, monitor, static_cast<DWORD>(lparam), m_workAreaConfiguration.GetAllWorkAreas());
} }
else else
{ {
m_windowKeyboardSnapper.Snap(foregroundWindow, windowRect, monitor, static_cast<DWORD>(lparam), m_workAreaHandler.GetAllWorkAreas(), monitors); m_windowKeyboardSnapper.Snap(foregroundWindow, windowRect, monitor, static_cast<DWORD>(lparam), m_workAreaConfiguration.GetAllWorkAreas(), monitors);
} }
} }
else else
@ -637,7 +635,7 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa
} }
else else
{ {
m_windowKeyboardSnapper.Snap(foregroundWindow, monitor, static_cast<DWORD>(lparam), m_workAreaHandler.GetAllWorkAreas(), FancyZonesUtils::GetMonitorsOrdered()); m_windowKeyboardSnapper.Snap(foregroundWindow, monitor, static_cast<DWORD>(lparam), m_workAreaConfiguration.GetAllWorkAreas(), FancyZonesUtils::GetMonitorsOrdered());
} }
} }
else if (message == WM_PRIV_INIT) else if (message == WM_PRIV_INIT)
@ -732,6 +730,7 @@ void FancyZones::OnDisplayChange(DisplayChangeType changeType) noexcept
updateWindowsPositions = FancyZonesSettings::settings().displayChange_moveWindows; updateWindowsPositions = FancyZonesSettings::settings().displayChange_moveWindows;
break; break;
case DisplayChangeType::VirtualDesktop: // Switched virtual desktop case DisplayChangeType::VirtualDesktop: // Switched virtual desktop
SyncVirtualDesktops();
break; break;
case DisplayChangeType::Initialization: // Initialization case DisplayChangeType::Initialization: // Initialization
updateWindowsPositions = FancyZonesSettings::settings().zoneSetChange_moveWindows; updateWindowsPositions = FancyZonesSettings::settings().zoneSetChange_moveWindows;
@ -761,14 +760,17 @@ bool FancyZones::AddWorkArea(HMONITOR monitor, const FancyZonesDataTypes::WorkAr
rect = FancyZonesUtils::GetAllMonitorsCombinedRect<&MONITORINFO::rcWork>(); rect = FancyZonesUtils::GetAllMonitorsCombinedRect<&MONITORINFO::rcWork>();
} }
auto workArea = WorkArea::Create(m_hinstance, id, m_workAreaHandler.GetParent(monitor), rect); auto parentWorkAreaId = id;
parentWorkAreaId.virtualDesktopId = LastUsedVirtualDesktop::instance().GetId();
auto workArea = WorkArea::Create(m_hinstance, id, parentWorkAreaId, rect);
if (!workArea) if (!workArea)
{ {
Logger::error(L"Failed to create work area {}", id.toString()); Logger::error(L"Failed to create work area {}", id.toString());
return false; return false;
} }
m_workAreaHandler.AddWorkArea(monitor, std::move(workArea)); m_workAreaConfiguration.AddWorkArea(monitor, std::move(workArea));
return true; return true;
} }
@ -790,8 +792,7 @@ void FancyZones::UpdateWorkAreas(bool updateWindowPositions) noexcept
{ {
Logger::debug(L"Update work areas, update windows positions: {}", updateWindowPositions); Logger::debug(L"Update work areas, update windows positions: {}", updateWindowPositions);
m_workAreaHandler.SaveParentIds(); m_workAreaConfiguration.Clear();
m_workAreaHandler.Clear();
if (FancyZonesSettings::settings().spanZonesAcrossMonitors) if (FancyZonesSettings::settings().spanZonesAcrossMonitors)
{ {
@ -829,7 +830,7 @@ void FancyZones::UpdateWorkAreas(bool updateWindowPositions) noexcept
if (FancyZonesSettings::settings().spanZonesAcrossMonitors) // one work area across monitors if (FancyZonesSettings::settings().spanZonesAcrossMonitors) // one work area across monitors
{ {
const auto workArea = m_workAreaHandler.GetWorkArea(nullptr); const auto workArea = m_workAreaConfiguration.GetWorkArea(nullptr);
if (workArea) if (workArea)
{ {
for (const auto& [window, zones] : windowsToSnap) for (const auto& [window, zones] : windowsToSnap)
@ -846,7 +847,7 @@ void FancyZones::UpdateWorkAreas(bool updateWindowPositions) noexcept
const auto window = iter->first; const auto window = iter->first;
const auto zones = iter->second; const auto zones = iter->second;
const auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL); const auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL);
const auto workAreaForMonitor = m_workAreaHandler.GetWorkArea(monitor); const auto workAreaForMonitor = m_workAreaConfiguration.GetWorkArea(monitor);
if (workAreaForMonitor && AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaForMonitor->UniqueId(), workAreaForMonitor->GetLayoutId()) == zones) if (workAreaForMonitor && AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaForMonitor->UniqueId(), workAreaForMonitor->GetLayoutId()) == zones)
{ {
workAreaForMonitor->Snap(window, zones, false); workAreaForMonitor->Snap(window, zones, false);
@ -861,7 +862,7 @@ void FancyZones::UpdateWorkAreas(bool updateWindowPositions) noexcept
// snap rest of the windows to other work areas (in case they were moved after the monitor unplug) // snap rest of the windows to other work areas (in case they were moved after the monitor unplug)
for (const auto& [window, zones] : windowsToSnap) for (const auto& [window, zones] : windowsToSnap)
{ {
for (const auto& [_, workArea] : m_workAreaHandler.GetAllWorkAreas()) for (const auto& [_, workArea] : m_workAreaConfiguration.GetAllWorkAreas())
{ {
const auto savedIndexes = AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId()); const auto savedIndexes = AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId());
if (savedIndexes == zones) if (savedIndexes == zones)
@ -874,7 +875,7 @@ void FancyZones::UpdateWorkAreas(bool updateWindowPositions) noexcept
if (updateWindowPositions) if (updateWindowPositions)
{ {
for (const auto& [_, workArea] : m_workAreaHandler.GetAllWorkAreas()) for (const auto& [_, workArea] : m_workAreaConfiguration.GetAllWorkAreas())
{ {
if (workArea) if (workArea)
{ {
@ -889,7 +890,7 @@ void FancyZones::CycleWindows(bool reverse) noexcept
auto window = GetForegroundWindow(); auto window = GetForegroundWindow();
HMONITOR current = WorkAreaKeyFromWindow(window); HMONITOR current = WorkAreaKeyFromWindow(window);
auto workArea = m_workAreaHandler.GetWorkArea(current); auto workArea = m_workAreaConfiguration.GetWorkArea(current);
if (workArea) if (workArea)
{ {
workArea->CycleWindows(window, reverse); workArea->CycleWindows(window, reverse);
@ -898,15 +899,23 @@ void FancyZones::CycleWindows(bool reverse) noexcept
void FancyZones::SyncVirtualDesktops() noexcept void FancyZones::SyncVirtualDesktops() noexcept
{ {
// Explorer persists current virtual desktop identifier to registry on a per session basis,
// but only after first virtual desktop switch happens. If the user hasn't switched virtual
// desktops in this session value in registry will be empty and we will use default GUID in
// that case (00000000-0000-0000-0000-000000000000).
auto lastUsed = LastUsedVirtualDesktop::instance().GetId();
auto current = VirtualDesktop::instance().GetCurrentVirtualDesktopId();
auto guids = VirtualDesktop::instance().GetVirtualDesktopIdsFromRegistry(); auto guids = VirtualDesktop::instance().GetVirtualDesktopIdsFromRegistry();
if (guids.has_value())
if (current != lastUsed)
{ {
AppZoneHistory::instance().RemoveDeletedVirtualDesktops(*guids); LastUsedVirtualDesktop::instance().SetId(current);
AppliedLayouts::instance().RemoveDeletedVirtualDesktops(*guids); LastUsedVirtualDesktop::instance().SaveData();
} }
AppZoneHistory::instance().SyncVirtualDesktops(); AppliedLayouts::instance().SyncVirtualDesktops(current, lastUsed, guids);
AppliedLayouts::instance().SyncVirtualDesktops(); AppZoneHistory::instance().SyncVirtualDesktops(current, lastUsed, guids);
} }
void FancyZones::UpdateHotkey(int hotkeyId, const PowerToysSettings::HotkeyObject& hotkeyObject, bool enable) noexcept void FancyZones::UpdateHotkey(int hotkeyId, const PowerToysSettings::HotkeyObject& hotkeyObject, bool enable) noexcept
@ -960,7 +969,7 @@ void FancyZones::SettingsUpdate(SettingId id)
break; break;
case SettingId::SpanZonesAcrossMonitors: case SettingId::SpanZonesAcrossMonitors:
{ {
m_workAreaHandler.Clear(); m_workAreaConfiguration.Clear();
PostMessageW(m_window, WM_PRIV_INIT, NULL, NULL); PostMessageW(m_window, WM_PRIV_INIT, NULL, NULL);
} }
break; break;
@ -971,7 +980,7 @@ void FancyZones::SettingsUpdate(SettingId id)
void FancyZones::RefreshLayouts() noexcept void FancyZones::RefreshLayouts() noexcept
{ {
for (const auto& [_, workArea] : m_workAreaHandler.GetAllWorkAreas()) for (const auto& [_, workArea] : m_workAreaConfiguration.GetAllWorkAreas())
{ {
if (workArea) if (workArea)
{ {
@ -994,11 +1003,11 @@ bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept
return false; return false;
} }
if (FancyZonesSettings::settings().overrideSnapHotkeys && FancyZonesWindowUtils::IsCandidateForZoning(window)) if (FancyZonesSettings::settings().overrideSnapHotkeys)
{ {
HMONITOR monitor = WorkAreaKeyFromWindow(window); HMONITOR monitor = WorkAreaKeyFromWindow(window);
auto workArea = m_workAreaHandler.GetWorkArea(monitor); auto workArea = m_workAreaConfiguration.GetWorkArea(monitor);
if (!workArea) if (!workArea)
{ {
Logger::error(L"No work area for processing snap hotkey"); Logger::error(L"No work area for processing snap hotkey");
@ -1043,13 +1052,15 @@ void FancyZones::ApplyQuickLayout(int key) noexcept
return; return;
} }
auto workArea = m_workAreaHandler.GetWorkAreaFromCursor(); auto workArea = m_workAreaConfiguration.GetWorkAreaFromCursor();
if (workArea) if (workArea)
{ {
AppliedLayouts::instance().ApplyLayout(workArea->UniqueId(), layout.value()); if (AppliedLayouts::instance().ApplyLayout(workArea->UniqueId(), layout.value()))
AppliedLayouts::instance().SaveData(); {
RefreshLayouts(); RefreshLayouts();
FlashZones(); FlashZones();
AppliedLayouts::instance().SaveData();
}
} }
} }
@ -1057,7 +1068,7 @@ void FancyZones::FlashZones() noexcept
{ {
if (FancyZonesSettings::settings().flashZonesOnQuickSwitch && !m_draggingState.IsDragging()) if (FancyZonesSettings::settings().flashZonesOnQuickSwitch && !m_draggingState.IsDragging())
{ {
for (const auto& [_, workArea] : m_workAreaHandler.GetAllWorkAreas()) for (const auto& [_, workArea] : m_workAreaConfiguration.GetAllWorkAreas())
{ {
if (workArea) if (workArea)
{ {

View File

@ -26,13 +26,7 @@ public:
return settingsFileName; return settingsFileName;
} }
private:
#if defined(UNIT_TESTS) #if defined(UNIT_TESTS)
friend class FancyZonesUnitTests::LayoutHotkeysUnitTests;
friend class FancyZonesUnitTests::LayoutTemplatesUnitTests;
friend class FancyZonesUnitTests::CustomLayoutsUnitTests;
friend class FancyZonesUnitTests::AppliedLayoutsUnitTests;
inline void SetSettingsModulePath(std::wstring_view moduleName) inline void SetSettingsModulePath(std::wstring_view moduleName)
{ {
std::wstring result = PTSettingsHelper::get_module_save_folder_location(moduleName); std::wstring result = PTSettingsHelper::get_module_save_folder_location(moduleName);
@ -46,6 +40,8 @@ private:
return result + L"\\" + std::wstring(L"zones-settings.json"); return result + L"\\" + std::wstring(L"zones-settings.json");
} }
#endif #endif
private:
std::wstring settingsFileName; std::wstring settingsFileName;
std::wstring zonesSettingsFileName; std::wstring zonesSettingsFileName;
std::wstring appZoneHistoryFileName; std::wstring appZoneHistoryFileName;

View File

@ -591,67 +591,43 @@ ZoneIndexSet AppZoneHistory::GetAppLastZoneIndexSet(HWND window, const FancyZone
return {}; return {};
} }
void AppZoneHistory::SyncVirtualDesktops() void AppZoneHistory::SyncVirtualDesktops(const GUID& currentVirtualDesktop, const GUID& lastUsedVirtualDesktop, std::optional<std::vector<GUID>> desktops)
{ {
// Explorer persists current virtual desktop identifier to registry on a per session basis, TAppZoneHistoryMap history;
// but only after first virtual desktop switch happens. If the user hasn't switched virtual
// desktops in this session value in registry will be empty and we will use default GUID in
// that case (00000000-0000-0000-0000-000000000000).
auto savedInRegistryVirtualDesktopID = VirtualDesktop::instance().GetCurrentVirtualDesktopIdFromRegistry(); std::unordered_set<GUID> activeDesktops{};
if (!savedInRegistryVirtualDesktopID.has_value() || savedInRegistryVirtualDesktopID.value() == GUID_NULL) if (desktops.has_value())
{ {
return; activeDesktops = std::unordered_set<GUID>(std::begin(desktops.value()), std::end(desktops.value()));
} }
auto currentVirtualDesktopStr = FancyZonesUtils::GuidToString(savedInRegistryVirtualDesktopID.value()); auto findCurrentVirtualDesktopInSavedHistory = [&](const std::pair<std::wstring, std::vector<FancyZonesDataTypes::AppZoneHistoryData>>& val) -> bool
if (!currentVirtualDesktopStr.has_value())
{ {
Logger::error(L"Failed to convert virtual desktop GUID to string"); for (auto& data : val.second)
return; {
if (data.workAreaId.virtualDesktopId == currentVirtualDesktop)
{
return true;
} }
}
Logger::info(L"AppZoneHistory Sync virtual desktops: current {}", currentVirtualDesktopStr.value()); return false;
};
bool replaceLastUsedWithCurrent = !desktops.has_value() || currentVirtualDesktop == GUID_NULL || lastUsedVirtualDesktop == GUID_NULL || std::find_if(m_history.begin(), m_history.end(), findCurrentVirtualDesktopInSavedHistory) == m_history.end();
bool dirtyFlag = false; bool dirtyFlag = false;
for (auto& [path, perDesktopData] : m_history)
{
for (auto& data : perDesktopData)
{
if (data.workAreaId.virtualDesktopId == GUID_NULL)
{
data.workAreaId.virtualDesktopId = savedInRegistryVirtualDesktopID.value();
dirtyFlag = true;
}
}
}
if (dirtyFlag)
{
Logger::info(L"Update Virtual Desktop id to {}", currentVirtualDesktopStr.value());
SaveData();
}
}
void AppZoneHistory::RemoveDeletedVirtualDesktops(const std::vector<GUID>& activeDesktops)
{
std::unordered_set<GUID> active(std::begin(activeDesktops), std::end(activeDesktops));
bool dirtyFlag = false;
for (auto it = std::begin(m_history); it != std::end(m_history);) for (auto it = std::begin(m_history); it != std::end(m_history);)
{ {
auto& perDesktopData = it->second; auto& perDesktopData = it->second;
for (auto desktopIt = std::begin(perDesktopData); desktopIt != std::end(perDesktopData);) for (auto desktopIt = std::begin(perDesktopData); desktopIt != std::end(perDesktopData);)
{ {
if (desktopIt->workAreaId.virtualDesktopId != GUID_NULL && !active.contains(desktopIt->workAreaId.virtualDesktopId)) if (replaceLastUsedWithCurrent && desktopIt->workAreaId.virtualDesktopId == lastUsedVirtualDesktop)
{ {
auto virtualDesktopIdStr = FancyZonesUtils::GuidToString(desktopIt->workAreaId.virtualDesktopId); desktopIt->workAreaId.virtualDesktopId = currentVirtualDesktop;
if (virtualDesktopIdStr) dirtyFlag = true;
{
Logger::info(L"Remove Virtual Desktop id {} from app-zone-history", virtualDesktopIdStr.value());
} }
if (desktopIt->workAreaId.virtualDesktopId != currentVirtualDesktop && !activeDesktops.contains(desktopIt->workAreaId.virtualDesktopId))
{
desktopIt = perDesktopData.erase(desktopIt); desktopIt = perDesktopData.erase(desktopIt);
dirtyFlag = true; dirtyFlag = true;
} }
@ -664,6 +640,7 @@ void AppZoneHistory::RemoveDeletedVirtualDesktops(const std::vector<GUID>& activ
if (perDesktopData.empty()) if (perDesktopData.empty())
{ {
it = m_history.erase(it); it = m_history.erase(it);
dirtyFlag = true;
} }
else else
{ {

View File

@ -41,6 +41,13 @@ public:
#endif #endif
} }
#if defined(UNIT_TESTS)
inline void SetAppZoneHistory(const TAppZoneHistoryMap& history)
{
m_history = history;
}
#endif
void LoadData(); void LoadData();
void SaveData(); void SaveData();
void AdjustWorkAreaIds(const std::vector<FancyZonesDataTypes::MonitorId>& ids); void AdjustWorkAreaIds(const std::vector<FancyZonesDataTypes::MonitorId>& ids);
@ -56,8 +63,7 @@ public:
bool IsAnotherWindowOfApplicationInstanceZoned(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId) const noexcept; bool IsAnotherWindowOfApplicationInstanceZoned(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId) const noexcept;
ZoneIndexSet GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId) const; ZoneIndexSet GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId) const;
void SyncVirtualDesktops(); void SyncVirtualDesktops(const GUID& currentVirtualDesktop, const GUID& lastUsedVirtualDesktop, std::optional<std::vector<GUID>> desktops);
void RemoveDeletedVirtualDesktops(const std::vector<GUID>& activeDesktops);
private: private:
AppZoneHistory(); AppZoneHistory();

View File

@ -279,29 +279,7 @@ void AppliedLayouts::LoadData()
void AppliedLayouts::SaveData() void AppliedLayouts::SaveData()
{ {
bool dirtyFlag = false;
TAppliedLayoutsMap updatedMap;
for (const auto& [id, data] : m_layouts)
{
auto updatedId = id;
if (!VirtualDesktop::instance().IsVirtualDesktopIdSavedInRegistry(id.virtualDesktopId))
{
updatedId.virtualDesktopId = GUID_NULL;
dirtyFlag = true;
}
updatedMap.insert({ updatedId, data });
}
if (dirtyFlag)
{
json::to_file(AppliedLayoutsFileName(), JsonUtils::SerializeJson(updatedMap));
}
else
{
json::to_file(AppliedLayoutsFileName(), JsonUtils::SerializeJson(m_layouts)); json::to_file(AppliedLayoutsFileName(), JsonUtils::SerializeJson(m_layouts));
}
} }
void AppliedLayouts::AdjustWorkAreaIds(const std::vector<FancyZonesDataTypes::MonitorId>& ids) void AppliedLayouts::AdjustWorkAreaIds(const std::vector<FancyZonesDataTypes::MonitorId>& ids)
@ -344,86 +322,75 @@ void AppliedLayouts::AdjustWorkAreaIds(const std::vector<FancyZonesDataTypes::Mo
} }
} }
void AppliedLayouts::SyncVirtualDesktops() void AppliedLayouts::SyncVirtualDesktops(const GUID& currentVirtualDesktop, const GUID& lastUsedVirtualDesktop, std::optional<std::vector<GUID>> desktops)
{ {
// Explorer persists current virtual desktop identifier to registry on a per session basis, TAppliedLayoutsMap layouts;
// but only after first virtual desktop switch happens. If the user hasn't switched virtual
// desktops in this session value in registry will be empty and we will use default GUID in
// that case (00000000-0000-0000-0000-000000000000).
auto savedInRegistryVirtualDesktopID = VirtualDesktop::instance().GetCurrentVirtualDesktopIdFromRegistry(); auto findCurrentVirtualDesktopInSavedLayouts = [&](const std::pair<FancyZonesDataTypes::WorkAreaId, LayoutData>& val) -> bool { return val.first.virtualDesktopId == currentVirtualDesktop; };
if (!savedInRegistryVirtualDesktopID.has_value() || savedInRegistryVirtualDesktopID.value() == GUID_NULL) bool replaceLastUsedWithCurrent = !desktops.has_value() || currentVirtualDesktop == GUID_NULL ||
std::find_if(m_layouts.begin(), m_layouts.end(), findCurrentVirtualDesktopInSavedLayouts) == m_layouts.end();
bool copyToOtherVirtualDesktops = lastUsedVirtualDesktop == GUID_NULL && currentVirtualDesktop != GUID_NULL && desktops.has_value();
for (const auto& [workAreaId, layout] : m_layouts)
{ {
return; if (replaceLastUsedWithCurrent && workAreaId.virtualDesktopId == lastUsedVirtualDesktop)
{
// replace "lastUsedVirtualDesktop" with "currentVirtualDesktop"
auto updatedWorkAreaId = workAreaId;
updatedWorkAreaId.virtualDesktopId = currentVirtualDesktop;
layouts.insert({ updatedWorkAreaId, layout });
if (copyToOtherVirtualDesktops)
{
// Copy to other virtual desktops on the 1st VD creation.
// If we just replace the id, we'll lose the layout on the other desktops.
// Usage scenario:
// apply the layout to the single virtual desktop with id = GUID_NULL,
// create the 2nd virtual desktop and switch to it,
// so virtual desktop id changes from GUID_NULL to a valid value of the 2nd VD.
// Then change the layout on the 2nd VD and switch back to the 1st VD.
// Layout on the initial VD will be changed too without initializing it beforehand.
for (const auto& id : desktops.value())
{
if (id != currentVirtualDesktop)
{
auto copyWorkAreaId = workAreaId;
copyWorkAreaId.virtualDesktopId = id;
layouts.insert({ copyWorkAreaId, layout });
} }
auto currentVirtualDesktopStr = FancyZonesUtils::GuidToString(savedInRegistryVirtualDesktopID.value());
if (!currentVirtualDesktopStr.has_value())
{
Logger::error(L"Failed to convert virtual desktop GUID to string");
return;
} }
Logger::info(L"AppliedLayouts Sync virtual desktops: current {}", currentVirtualDesktopStr.value());
bool dirtyFlag = false;
std::vector<FancyZonesDataTypes::WorkAreaId> replaceWithCurrentId{};
for (const auto& [id, data] : m_layouts)
{
if (id.virtualDesktopId == GUID_NULL)
{
replaceWithCurrentId.push_back(id);
dirtyFlag = true;
} }
} }
for (const auto& id : replaceWithCurrentId) if (workAreaId.virtualDesktopId == currentVirtualDesktop || (desktops.has_value() &&
std::find(desktops.value().begin(), desktops.value().end(), workAreaId.virtualDesktopId) != desktops.value().end()))
{ {
auto mapEntry = m_layouts.extract(id); // keep only actual virtual desktop values
mapEntry.key().virtualDesktopId = savedInRegistryVirtualDesktopID.value(); layouts.insert({ workAreaId, layout });
m_layouts.insert(std::move(mapEntry)); }
} }
if (dirtyFlag) if (layouts != m_layouts)
{ {
Logger::info(L"Update Virtual Desktop id to {}", currentVirtualDesktopStr.value()); m_layouts = layouts;
SaveData(); SaveData();
}
}
void AppliedLayouts::RemoveDeletedVirtualDesktops(const std::vector<GUID>& activeDesktops) std::wstring currentStr = FancyZonesUtils::GuidToString(currentVirtualDesktop).value_or(L"incorrect guid");
{ std::wstring lastUsedStr = FancyZonesUtils::GuidToString(lastUsedVirtualDesktop).value_or(L"incorrect guid");
std::unordered_set<GUID> active(std::begin(activeDesktops), std::end(activeDesktops)); std::wstring registryStr{};
bool dirtyFlag = false; if (desktops.has_value())
for (auto it = std::begin(m_layouts); it != std::end(m_layouts);)
{ {
GUID desktopId = it->first.virtualDesktopId; for (const auto& id : desktops.value())
if (desktopId != GUID_NULL)
{ {
auto foundId = active.find(desktopId); registryStr += FancyZonesUtils::GuidToString(id).value_or(L"incorrect guid") + L" ";
if (foundId == std::end(active))
{
wil::unique_cotaskmem_string virtualDesktopIdStr;
if (SUCCEEDED(StringFromCLSID(desktopId, &virtualDesktopIdStr)))
{
Logger::info(L"Remove Virtual Desktop id {}", virtualDesktopIdStr.get());
}
it = m_layouts.erase(it);
dirtyFlag = true;
continue;
} }
} }
++it; else
{
registryStr = L"empty";
} }
if (dirtyFlag) Logger::info(L"Synced virtual desktops. Current: {}, last used: {}, registry: {}", currentStr, lastUsedStr, registryStr);
{
SaveData();
} }
} }
@ -449,9 +416,9 @@ bool AppliedLayouts::IsLayoutApplied(const FancyZonesDataTypes::WorkAreaId& id)
return iter != m_layouts.end(); return iter != m_layouts.end();
} }
bool AppliedLayouts::ApplyLayout(const FancyZonesDataTypes::WorkAreaId& deviceId, LayoutData layout) bool AppliedLayouts::ApplyLayout(const FancyZonesDataTypes::WorkAreaId& workAreaId, LayoutData layout)
{ {
m_layouts[deviceId] = std::move(layout); m_layouts[workAreaId] = layout;
return true; return true;
} }
@ -496,9 +463,5 @@ bool AppliedLayouts::CloneLayout(const FancyZonesDataTypes::WorkAreaId& srcId, c
} }
Logger::info(L"Clone layout from {} to {}", dstId.toString(), srcId.toString()); Logger::info(L"Clone layout from {} to {}", dstId.toString(), srcId.toString());
m_layouts[dstId] = m_layouts[srcId]; return ApplyLayout(dstId, m_layouts[srcId]);
SaveData();
return true;
} }

View File

@ -49,12 +49,18 @@ public:
#endif #endif
} }
#if defined(UNIT_TESTS)
inline void SetAppliedLayouts(TAppliedLayoutsMap layouts)
{
m_layouts = layouts;
}
#endif
void LoadData(); void LoadData();
void SaveData(); void SaveData();
void AdjustWorkAreaIds(const std::vector<FancyZonesDataTypes::MonitorId>& ids); void AdjustWorkAreaIds(const std::vector<FancyZonesDataTypes::MonitorId>& ids);
void SyncVirtualDesktops(); void SyncVirtualDesktops(const GUID& currentVirtualDesktop, const GUID& lastUsedVirtualDesktop, std::optional<std::vector<GUID>> desktops);
void RemoveDeletedVirtualDesktops(const std::vector<GUID>& activeDesktops);
std::optional<LayoutData> GetDeviceLayout(const FancyZonesDataTypes::WorkAreaId& id) const noexcept; std::optional<LayoutData> GetDeviceLayout(const FancyZonesDataTypes::WorkAreaId& id) const noexcept;
const TAppliedLayoutsMap& GetAppliedLayoutMap() const noexcept; const TAppliedLayoutsMap& GetAppliedLayoutMap() const noexcept;

View File

@ -0,0 +1,78 @@
#include "../pch.h"
#include "LastUsedVirtualDesktop.h"
#include <common/logger/logger.h>
#include <FancyZonesLib/util.h>
namespace JsonUtils
{
GUID ParseJson(const json::JsonObject& json)
{
auto idStr = json.GetNamedString(NonLocalizable::LastUsedVirtualDesktop::LastUsedVirtualDesktopID);
auto idOpt = FancyZonesUtils::GuidFromString(idStr.c_str());
if (!idOpt.has_value())
{
return {};
}
return idOpt.value();
}
json::JsonObject SerializeJson(const GUID& id)
{
json::JsonObject result{};
auto virtualDesktopStr = FancyZonesUtils::GuidToString(id);
if (virtualDesktopStr)
{
result.SetNamedValue(NonLocalizable::LastUsedVirtualDesktop::LastUsedVirtualDesktopID, json::value(virtualDesktopStr.value()));
}
return result;
}
}
LastUsedVirtualDesktop& LastUsedVirtualDesktop::instance()
{
static LastUsedVirtualDesktop self;
return self;
}
void LastUsedVirtualDesktop::LoadData()
{
auto data = json::from_file(LastUsedVirtualDesktopFileName());
try
{
if (data)
{
m_id = JsonUtils::ParseJson(data.value());
}
else
{
m_id = GUID_NULL;
}
}
catch (const winrt::hresult_error& e)
{
Logger::error(L"Parsing last-used-virtual-desktop error: {}", e.message());
}
}
void LastUsedVirtualDesktop::SaveData() const
{
json::to_file(LastUsedVirtualDesktopFileName(), JsonUtils::SerializeJson(m_id));
}
GUID LastUsedVirtualDesktop::GetId() const
{
return m_id;
}
void LastUsedVirtualDesktop::SetId(GUID id)
{
m_id = id;
}

View File

@ -0,0 +1,41 @@
#pragma once
#include <common/SettingsAPI/settings_helpers.h>
#include <FancyZonesLib/ModuleConstants.h>
namespace NonLocalizable
{
namespace LastUsedVirtualDesktop
{
const static wchar_t* LastUsedVirtualDesktopID = L"last-used-virtual-desktop";
}
}
class LastUsedVirtualDesktop
{
public:
static LastUsedVirtualDesktop& instance();
inline static std::wstring LastUsedVirtualDesktopFileName()
{
std::wstring saveFolderPath = PTSettingsHelper::get_module_save_folder_location(NonLocalizable::ModuleKey);
#if defined(UNIT_TESTS)
return saveFolderPath + L"\\test-last-used-virtual-desktop.json";
#else
return saveFolderPath + L"\\last-used-virtual-desktop.json";
#endif
}
void LoadData();
void SaveData() const;
GUID GetId() const;
void SetId(GUID id);
private:
LastUsedVirtualDesktop() = default;
~LastUsedVirtualDesktop() = default;
GUID m_id{};
};

View File

@ -209,14 +209,12 @@ namespace FancyZonesDataTypes
inline bool operator==(const WorkAreaId& lhs, const WorkAreaId& rhs) inline bool operator==(const WorkAreaId& lhs, const WorkAreaId& rhs)
{ {
bool vdEqual = (lhs.virtualDesktopId == rhs.virtualDesktopId || lhs.virtualDesktopId == GUID_NULL || rhs.virtualDesktopId == GUID_NULL); return lhs.virtualDesktopId == rhs.virtualDesktopId && lhs.monitorId == rhs.monitorId;
return vdEqual && lhs.monitorId == rhs.monitorId;
} }
inline bool operator!=(const WorkAreaId& lhs, const WorkAreaId& rhs) inline bool operator!=(const WorkAreaId& lhs, const WorkAreaId& rhs)
{ {
bool vdEqual = (lhs.virtualDesktopId == rhs.virtualDesktopId || lhs.virtualDesktopId == GUID_NULL || rhs.virtualDesktopId == GUID_NULL); return lhs.virtualDesktopId != rhs.virtualDesktopId || lhs.monitorId != rhs.monitorId;
return !vdEqual || lhs.monitorId != rhs.monitorId;
} }
inline bool operator<(const WorkAreaId& lhs, const WorkAreaId& rhs) inline bool operator<(const WorkAreaId& lhs, const WorkAreaId& rhs)
@ -240,6 +238,11 @@ namespace FancyZonesDataTypes
return lhs.monitorId.deviceId < rhs.monitorId.deviceId; return lhs.monitorId.deviceId < rhs.monitorId.deviceId;
} }
inline bool operator==(const AppZoneHistoryData& lhs, const AppZoneHistoryData& rhs)
{
return lhs.layoutId == rhs.layoutId && lhs.workAreaId == rhs.workAreaId && lhs.zoneIndexSet == rhs.zoneIndexSet && lhs.processIdToHandleMap == rhs.processIdToHandleMap;
}
} }
namespace std namespace std

View File

@ -43,6 +43,7 @@
<ClInclude Include="FancyZones.h" /> <ClInclude Include="FancyZones.h" />
<ClInclude Include="FancyZonesDataTypes.h" /> <ClInclude Include="FancyZonesDataTypes.h" />
<ClInclude Include="FancyZonesData\DefaultLayouts.h" /> <ClInclude Include="FancyZonesData\DefaultLayouts.h" />
<ClInclude Include="FancyZonesData\LastUsedVirtualDesktop.h" />
<ClInclude Include="FancyZonesData\LayoutData.h" /> <ClInclude Include="FancyZonesData\LayoutData.h" />
<ClInclude Include="FancyZonesData\LayoutDefaults.h" /> <ClInclude Include="FancyZonesData\LayoutDefaults.h" />
<ClInclude Include="FancyZonesData\LayoutTemplates.h" /> <ClInclude Include="FancyZonesData\LayoutTemplates.h" />
@ -59,7 +60,7 @@
<ClInclude Include="LayoutAssignedWindows.h" /> <ClInclude Include="LayoutAssignedWindows.h" />
<ClInclude Include="ModuleConstants.h" /> <ClInclude Include="ModuleConstants.h" />
<ClInclude Include="MonitorUtils.h" /> <ClInclude Include="MonitorUtils.h" />
<ClInclude Include="MonitorWorkAreaMap.h" /> <ClInclude Include="WorkAreaConfiguration.h" />
<ClInclude Include="NotificationUtil.h" /> <ClInclude Include="NotificationUtil.h" />
<ClInclude Include="pch.h" /> <ClInclude Include="pch.h" />
<ClInclude Include="Generated Files/resource.h" /> <ClInclude Include="Generated Files/resource.h" />
@ -100,9 +101,13 @@
<ClCompile Include="FancyZonesData\DefaultLayouts.cpp"> <ClCompile Include="FancyZonesData\DefaultLayouts.cpp">
<PrecompiledHeaderFile>../pch.h</PrecompiledHeaderFile> <PrecompiledHeaderFile>../pch.h</PrecompiledHeaderFile>
</ClCompile> </ClCompile>
<ClCompile Include="FancyZonesData\LastUsedVirtualDesktop.cpp">
<PrecompiledHeaderFile>../pch.h</PrecompiledHeaderFile>
</ClCompile>
<ClCompile Include="FancyZonesData\LayoutTemplates.cpp"> <ClCompile Include="FancyZonesData\LayoutTemplates.cpp">
<PrecompiledHeaderFile>../pch.h</PrecompiledHeaderFile> <PrecompiledHeaderFile>../pch.h</PrecompiledHeaderFile>
</ClCompile> </ClCompile>
<ClCompile Include="FancyZonesWindowProcessing.cpp" />
<ClCompile Include="FancyZonesWindowProperties.cpp" /> <ClCompile Include="FancyZonesWindowProperties.cpp" />
<ClCompile Include="FancyZonesWinHookEventIDs.cpp" /> <ClCompile Include="FancyZonesWinHookEventIDs.cpp" />
<ClCompile Include="FancyZonesData.cpp" /> <ClCompile Include="FancyZonesData.cpp" />
@ -114,7 +119,7 @@
<ClCompile Include="LayoutConfigurator.cpp" /> <ClCompile Include="LayoutConfigurator.cpp" />
<ClCompile Include="LayoutAssignedWindows.cpp" /> <ClCompile Include="LayoutAssignedWindows.cpp" />
<ClCompile Include="MonitorUtils.cpp" /> <ClCompile Include="MonitorUtils.cpp" />
<ClCompile Include="MonitorWorkAreaMap.cpp" /> <ClCompile Include="WorkAreaConfiguration.cpp" />
<ClCompile Include="OnThreadExecutor.cpp" /> <ClCompile Include="OnThreadExecutor.cpp" />
<ClCompile Include="pch.cpp"> <ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(CIBuild)'!='true'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(CIBuild)'!='true'">Create</PrecompiledHeader>

View File

@ -54,7 +54,7 @@
<ClInclude Include="MouseButtonsHook.h"> <ClInclude Include="MouseButtonsHook.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="MonitorWorkAreaMap.h"> <ClInclude Include="WorkAreaConfiguration.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="GenericKeyHook.h"> <ClInclude Include="GenericKeyHook.h">
@ -168,6 +168,9 @@
<ClInclude Include="WindowKeyboardSnap.h"> <ClInclude Include="WindowKeyboardSnap.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="FancyZonesData\LastUsedVirtualDesktop.h">
<Filter>Header Files\FancyZonesData</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="pch.cpp"> <ClCompile Include="pch.cpp">
@ -200,7 +203,7 @@
<ClCompile Include="MouseButtonsHook.cpp"> <ClCompile Include="MouseButtonsHook.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="MonitorWorkAreaMap.cpp"> <ClCompile Include="WorkAreaConfiguration.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="FancyZonesData.cpp"> <ClCompile Include="FancyZonesData.cpp">
@ -272,6 +275,12 @@
<ClCompile Include="WindowKeyboardSnap.cpp"> <ClCompile Include="WindowKeyboardSnap.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="FancyZonesData\LastUsedVirtualDesktop.cpp">
<Filter>Source Files\FancyZonesData</Filter>
</ClCompile>
<ClCompile Include="FancyZonesWindowProcessing.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="packages.config" /> <None Include="packages.config" />

View File

@ -0,0 +1,56 @@
#include "pch.h"
#include "FancyZonesWindowProcessing.h"
#include <FancyZonesLib/Settings.h>
#include <FancyZonesLib/VirtualDesktop.h>
#include <FancyZonesLib/WindowUtils.h>
bool FancyZonesWindowProcessing::IsProcessable(HWND window) noexcept
{
const bool isSplashScreen = FancyZonesWindowUtils::IsSplashScreen(window);
if (isSplashScreen)
{
return false;
}
const bool windowMinimized = IsIconic(window);
if (windowMinimized)
{
return false;
}
const bool standard = FancyZonesWindowUtils::IsStandardWindow(window);
if (!standard)
{
return false;
}
// popup could be the window we don't want to snap: start menu, notification popup, tray window, etc.
// also, popup could be the windows we want to snap disregarding the "allowSnapPopupWindows" setting, e.g. Telegram
bool isPopup = FancyZonesWindowUtils::IsPopupWindow(window) && !FancyZonesWindowUtils::HasThickFrameAndMinimizeMaximizeButtons(window);
if (isPopup && !FancyZonesSettings::settings().allowSnapPopupWindows)
{
return false;
}
// allow child windows
auto hasOwner = FancyZonesWindowUtils::HasVisibleOwner(window);
if (hasOwner && !FancyZonesSettings::settings().allowSnapChildWindows)
{
return false;
}
if (FancyZonesWindowUtils::IsExcluded(window))
{
return false;
}
// Switch between virtual desktops results with posting same windows messages that also indicate
// creation of new window. We need to check if window being processed is on currently active desktop.
if (!VirtualDesktop::instance().IsWindowOnCurrentDesktop(window))
{
return false;
}
return true;
}

View File

@ -1,39 +1,6 @@
#pragma once #pragma once
#include <FancyZonesLib/VirtualDesktop.h>
#include <FancyZonesLib/WindowUtils.h>
namespace FancyZonesWindowProcessing namespace FancyZonesWindowProcessing
{ {
inline bool IsProcessable(HWND window) noexcept bool IsProcessable(HWND window) noexcept;
{
const bool isSplashScreen = FancyZonesWindowUtils::IsSplashScreen(window);
if (isSplashScreen)
{
return false;
}
const bool windowMinimized = IsIconic(window);
if (windowMinimized)
{
return false;
}
// For windows that FancyZones shouldn't process (start menu, tray, popup menus)
// VirtualDesktopManager is unable to retrieve virtual desktop id and returns an error.
auto desktopId = VirtualDesktop::instance().GetDesktopId(window);
if (!desktopId.has_value())
{
return false;
}
// Switch between virtual desktops results with posting same windows messages that also indicate
// creation of new window. We need to check if window being processed is on currently active desktop.
if (!VirtualDesktop::instance().IsWindowOnCurrentDesktop(window))
{
return false;
}
return true;
}
} }

View File

@ -270,30 +270,16 @@ GUID VirtualDesktop::GetCurrentVirtualDesktopId() const noexcept
return m_currentVirtualDesktopId; return m_currentVirtualDesktopId;
} }
GUID VirtualDesktop::GetPreviousVirtualDesktopId() const noexcept
{
return m_previousDesktopId;
}
void VirtualDesktop::UpdateVirtualDesktopId() noexcept void VirtualDesktop::UpdateVirtualDesktopId() noexcept
{ {
m_previousDesktopId = m_currentVirtualDesktopId;
auto currentVirtualDesktopId = GetCurrentVirtualDesktopIdFromRegistry(); auto currentVirtualDesktopId = GetCurrentVirtualDesktopIdFromRegistry();
if (!currentVirtualDesktopId.has_value())
{
Logger::info("No Virtual Desktop Id found in registry");
currentVirtualDesktopId = VirtualDesktop::instance().GetDesktopIdByTopLevelWindows();
}
if (currentVirtualDesktopId.has_value()) if (currentVirtualDesktopId.has_value())
{ {
m_currentVirtualDesktopId = *currentVirtualDesktopId; m_currentVirtualDesktopId = currentVirtualDesktopId.value();
if (m_currentVirtualDesktopId == GUID_NULL)
{
Logger::warn("Couldn't retrieve virtual desktop id");
} }
else
{
m_currentVirtualDesktopId = GUID_NULL;
} }
Trace::VirtualDesktopChanged(); Trace::VirtualDesktopChanged();

View File

@ -7,7 +7,6 @@ public:
// saved values // saved values
GUID GetCurrentVirtualDesktopId() const noexcept; GUID GetCurrentVirtualDesktopId() const noexcept;
GUID GetPreviousVirtualDesktopId() const noexcept;
void UpdateVirtualDesktopId() noexcept; void UpdateVirtualDesktopId() noexcept;
// IVirtualDesktopManager // IVirtualDesktopManager
@ -29,7 +28,6 @@ private:
IVirtualDesktopManager* m_vdManager{nullptr}; IVirtualDesktopManager* m_vdManager{nullptr};
GUID m_currentVirtualDesktopId{}; GUID m_currentVirtualDesktopId{};
GUID m_previousDesktopId{};
std::optional<std::vector<GUID>> GetVirtualDesktopIdsFromRegistry(HKEY hKey) const; std::optional<std::vector<GUID>> GetVirtualDesktopIdsFromRegistry(HKEY hKey) const;
}; };

View File

@ -32,7 +32,6 @@ WindowMouseSnap::~WindowMouseSnap()
std::unique_ptr<WindowMouseSnap> WindowMouseSnap::Create(HWND window, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas) std::unique_ptr<WindowMouseSnap> WindowMouseSnap::Create(HWND window, const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& activeWorkAreas)
{ {
if (!FancyZonesWindowProcessing::IsProcessable(window) || if (!FancyZonesWindowProcessing::IsProcessable(window) ||
!FancyZonesWindowUtils::IsCandidateForZoning(window) ||
FancyZonesWindowUtils::IsCursorTypeIndicatingSizeEvent()) FancyZonesWindowUtils::IsCursorTypeIndicatingSizeEvent())
{ {
return nullptr; return nullptr;

View File

@ -166,6 +166,8 @@ bool FancyZonesWindowUtils::HasVisibleOwner(HWND window) noexcept
bool FancyZonesWindowUtils::IsStandardWindow(HWND window) bool FancyZonesWindowUtils::IsStandardWindow(HWND window)
{ {
// True if from the styles the window looks like a standard window
if (GetAncestor(window, GA_ROOT) != window) if (GetAncestor(window, GA_ROOT) != window)
{ {
return false; return false;
@ -181,13 +183,6 @@ bool FancyZonesWindowUtils::IsStandardWindow(HWND window)
return false; return false;
} }
std::array<char, 256> class_name;
GetClassNameA(window, class_name.data(), static_cast<int>(class_name.size()));
if (is_system_window(window, class_name.data()))
{
return false;
}
return true; return true;
} }
@ -203,44 +198,6 @@ bool FancyZonesWindowUtils::HasThickFrameAndMinimizeMaximizeButtons(HWND window)
return ((style & WS_THICKFRAME) == WS_THICKFRAME && (style & WS_MINIMIZEBOX) == WS_MINIMIZEBOX && (style & WS_MAXIMIZEBOX) == WS_MAXIMIZEBOX); return ((style & WS_THICKFRAME) == WS_THICKFRAME && (style & WS_MINIMIZEBOX) == WS_MINIMIZEBOX && (style & WS_MAXIMIZEBOX) == WS_MAXIMIZEBOX);
} }
bool FancyZonesWindowUtils::IsCandidateForZoning(HWND window)
{
bool isStandard = IsStandardWindow(window);
if (!isStandard)
{
return false;
}
// popup could be the window we don't want to snap: start menu, notification popup, tray window, etc.
// also, popup could be the windows we want to snap disregarding the "allowSnapPopupWindows" setting, e.g. Telegram
bool isPopup = IsPopupWindow(window);
if (isPopup && !HasThickFrameAndMinimizeMaximizeButtons(window) && !FancyZonesSettings::settings().allowSnapPopupWindows)
{
return false;
}
// allow child windows
auto hasOwner = HasVisibleOwner(window);
if (hasOwner && !FancyZonesSettings::settings().allowSnapChildWindows)
{
return false;
}
std::wstring processPath = get_process_path_waiting_uwp(window);
CharUpperBuffW(const_cast<std::wstring&>(processPath).data(), static_cast<DWORD>(processPath.length()));
if (IsExcludedByUser(window, processPath))
{
return false;
}
if (IsExcludedByDefault(window, processPath))
{
return false;
}
return true;
}
bool FancyZonesWindowUtils::IsProcessOfWindowElevated(HWND window) bool FancyZonesWindowUtils::IsProcessOfWindowElevated(HWND window)
{ {
DWORD pid = 0; DWORD pid = 0;
@ -268,6 +225,23 @@ bool FancyZonesWindowUtils::IsProcessOfWindowElevated(HWND window)
return false; return false;
} }
bool FancyZonesWindowUtils::IsExcluded(HWND window)
{
std::wstring processPath = get_process_path_waiting_uwp(window);
CharUpperBuffW(const_cast<std::wstring&>(processPath).data(), static_cast<DWORD>(processPath.length()));
if (IsExcludedByUser(window, processPath))
{
return true;
}
if (IsExcludedByDefault(window, processPath))
{
return true;
}
return false;
}
bool FancyZonesWindowUtils::IsExcludedByUser(const HWND& hwnd, std::wstring& processPath) noexcept bool FancyZonesWindowUtils::IsExcludedByUser(const HWND& hwnd, std::wstring& processPath) noexcept
{ {
return (check_excluded_app(hwnd, processPath, FancyZonesSettings::settings().excludedAppsArray)); return (check_excluded_app(hwnd, processPath, FancyZonesSettings::settings().excludedAppsArray));
@ -281,6 +255,13 @@ bool FancyZonesWindowUtils::IsExcludedByDefault(const HWND& hwnd, std::wstring&
return true; return true;
} }
std::array<char, 256> class_name;
GetClassNameA(hwnd, class_name.data(), static_cast<int>(class_name.size()));
if (is_system_window(hwnd, class_name.data()))
{
return true;
}
static std::vector<std::wstring> defaultExcludedApps = { NonLocalizable::PowerToysAppFZEditor, NonLocalizable::CoreWindow, NonLocalizable::SearchUI }; static std::vector<std::wstring> defaultExcludedApps = { NonLocalizable::PowerToysAppFZEditor, NonLocalizable::CoreWindow, NonLocalizable::SearchUI };
return (check_excluded_app(hwnd, processPath, defaultExcludedApps)); return (check_excluded_app(hwnd, processPath, defaultExcludedApps));
} }

View File

@ -21,8 +21,9 @@ namespace FancyZonesWindowUtils
bool IsStandardWindow(HWND window); bool IsStandardWindow(HWND window);
bool IsPopupWindow(HWND window) noexcept; bool IsPopupWindow(HWND window) noexcept;
bool HasThickFrameAndMinimizeMaximizeButtons(HWND window) noexcept; bool HasThickFrameAndMinimizeMaximizeButtons(HWND window) noexcept;
bool IsCandidateForZoning(HWND window);
bool IsProcessOfWindowElevated(HWND window); // If HWND is already dead, we assume it wasn't elevated bool IsProcessOfWindowElevated(HWND window); // If HWND is already dead, we assume it wasn't elevated
bool IsExcluded(HWND window);
bool IsExcludedByUser(const HWND& hwnd, std::wstring& processPath) noexcept; bool IsExcludedByUser(const HWND& hwnd, std::wstring& processPath) noexcept;
bool IsExcludedByDefault(const HWND& hwnd, std::wstring& processPath) noexcept; bool IsExcludedByDefault(const HWND& hwnd, std::wstring& processPath) noexcept;

View File

@ -244,14 +244,12 @@ void WorkArea::InitLayout(const FancyZonesDataTypes::WorkAreaId& parentUniqueId)
const bool isLayoutAlreadyApplied = AppliedLayouts::instance().IsLayoutApplied(m_uniqueId); const bool isLayoutAlreadyApplied = AppliedLayouts::instance().IsLayoutApplied(m_uniqueId);
if (!isLayoutAlreadyApplied) if (!isLayoutAlreadyApplied)
{ {
if (parentUniqueId.virtualDesktopId != GUID_NULL) if (!AppliedLayouts::instance().CloneLayout(parentUniqueId, m_uniqueId))
{
AppliedLayouts::instance().CloneLayout(parentUniqueId, m_uniqueId);
}
else
{ {
AppliedLayouts::instance().ApplyDefaultLayout(m_uniqueId); AppliedLayouts::instance().ApplyDefaultLayout(m_uniqueId);
} }
AppliedLayouts::instance().SaveData();
} }
CalculateZoneSet(); CalculateZoneSet();

View File

@ -1,9 +1,9 @@
#include "pch.h" #include "pch.h"
#include "MonitorWorkAreaMap.h" #include "WorkAreaConfiguration.h"
#include <FancyZonesLib/WorkArea.h> #include <FancyZonesLib/WorkArea.h>
WorkArea* const MonitorWorkAreaMap::GetWorkArea(HMONITOR monitor) const WorkArea* const WorkAreaConfiguration::GetWorkArea(HMONITOR monitor) const
{ {
auto iter = m_workAreaMap.find(monitor); auto iter = m_workAreaMap.find(monitor);
if (iter != m_workAreaMap.end()) if (iter != m_workAreaMap.end())
@ -14,7 +14,7 @@ WorkArea* const MonitorWorkAreaMap::GetWorkArea(HMONITOR monitor) const
return nullptr; return nullptr;
} }
WorkArea* const MonitorWorkAreaMap::GetWorkAreaFromCursor() const WorkArea* const WorkAreaConfiguration::GetWorkAreaFromCursor() const
{ {
const auto allMonitorsWorkArea = GetWorkArea(nullptr); const auto allMonitorsWorkArea = GetWorkArea(nullptr);
if (allMonitorsWorkArea) if (allMonitorsWorkArea)
@ -35,7 +35,7 @@ WorkArea* const MonitorWorkAreaMap::GetWorkAreaFromCursor() const
} }
} }
WorkArea* const MonitorWorkAreaMap::GetWorkAreaFromWindow(HWND window) const WorkArea* const WorkAreaConfiguration::GetWorkAreaFromWindow(HWND window) const
{ {
const auto allMonitorsWorkArea = GetWorkArea(nullptr); const auto allMonitorsWorkArea = GetWorkArea(nullptr);
if (allMonitorsWorkArea) if (allMonitorsWorkArea)
@ -51,39 +51,17 @@ WorkArea* const MonitorWorkAreaMap::GetWorkAreaFromWindow(HWND window) const
} }
} }
const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& MonitorWorkAreaMap::GetAllWorkAreas() const noexcept const std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>>& WorkAreaConfiguration::GetAllWorkAreas() const noexcept
{ {
return m_workAreaMap; return m_workAreaMap;
} }
void MonitorWorkAreaMap::AddWorkArea(HMONITOR monitor, std::unique_ptr<WorkArea> workArea) void WorkAreaConfiguration::AddWorkArea(HMONITOR monitor, std::unique_ptr<WorkArea> workArea)
{ {
m_workAreaMap.insert({ monitor, std::move(workArea) }); m_workAreaMap.insert({ monitor, std::move(workArea) });
} }
FancyZonesDataTypes::WorkAreaId MonitorWorkAreaMap::GetParent(HMONITOR monitor) const void WorkAreaConfiguration::Clear() noexcept
{
if (m_workAreaParents.contains(monitor))
{
return m_workAreaParents.at(monitor);
}
return FancyZonesDataTypes::WorkAreaId{};
}
void MonitorWorkAreaMap::SaveParentIds()
{
m_workAreaParents.clear();
for (const auto& [monitor, workArea] : m_workAreaMap)
{
if (workArea)
{
m_workAreaParents.insert({ monitor, workArea->UniqueId() });
}
}
}
void MonitorWorkAreaMap::Clear() noexcept
{ {
m_workAreaMap.clear(); m_workAreaMap.clear();
} }

View File

@ -1,15 +1,14 @@
#pragma once #pragma once
#include "GuidUtils.h"
#include <FancyZonesLib/FancyZonesDataTypes.h> #include <FancyZonesLib/FancyZonesDataTypes.h>
class WorkArea; class WorkArea;
class MonitorWorkAreaMap class WorkAreaConfiguration
{ {
public: public:
/** /**
* Get work area based on virtual desktop id and monitor handle. * Get work area based on monitor handle.
* *
* @param[in] monitor Monitor handle. * @param[in] monitor Monitor handle.
* *
@ -19,7 +18,7 @@ public:
WorkArea* const GetWorkArea(HMONITOR monitor) const; WorkArea* const GetWorkArea(HMONITOR monitor) const;
/** /**
* Get work area based on virtual desktop id and the current cursor position. * Get work area based on the current cursor position.
* *
* @returns Object representing single work area, interface to all actions available on work area * @returns Object representing single work area, interface to all actions available on work area
* (e.g. moving windows through zone layout specified for that work area). * (e.g. moving windows through zone layout specified for that work area).
@ -49,13 +48,6 @@ public:
*/ */
void AddWorkArea(HMONITOR monitor, std::unique_ptr<WorkArea> workArea); void AddWorkArea(HMONITOR monitor, std::unique_ptr<WorkArea> workArea);
FancyZonesDataTypes::WorkAreaId GetParent(HMONITOR monitor) const;
/**
* Saving current work area IDs as parents for later use.
*/
void SaveParentIds();
/** /**
* Clear all persisted work area related data. * Clear all persisted work area related data.
*/ */
@ -63,5 +55,4 @@ public:
private: private:
std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>> m_workAreaMap; std::unordered_map<HMONITOR, std::unique_ptr<WorkArea>> m_workAreaMap;
std::unordered_map<HMONITOR, FancyZonesDataTypes::WorkAreaId> m_workAreaParents{};
}; };

View File

@ -339,4 +339,159 @@ namespace FancyZonesUnitTests
Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(nullptr, workAreaId, layoutId)); Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(nullptr, workAreaId, layoutId));
} }
}; };
TEST_CLASS (AppZoneHistorySyncVirtualDesktops)
{
const GUID virtualDesktop1 = FancyZonesUtils::GuidFromString(L"{30387C86-BB15-476D-8683-AF93F6D73E99}").value();
const GUID virtualDesktop2 = FancyZonesUtils::GuidFromString(L"{65F6343A-868F-47EE-838E-55A178A7FB7A}").value();
const GUID deletedVirtualDesktop = FancyZonesUtils::GuidFromString(L"{2D9F3E2D-F61D-4618-B35D-85C9B8DFDFD8}").value();
FancyZonesDataTypes::WorkAreaId GetWorkAreaID(GUID virtualDesktop)
{
return FancyZonesDataTypes::WorkAreaId{
.monitorId = {
.deviceId = { .id = L"id", .instanceId = L"id", .number = 1 },
.serialNumber = L"serial-number"
},
.virtualDesktopId = virtualDesktop
};
}
FancyZonesDataTypes::AppZoneHistoryData GetAppZoneHistoryData(GUID virtualDesktop, const std::wstring& layoutId, const ZoneIndexSet& zones)
{
return FancyZonesDataTypes::AppZoneHistoryData{
.layoutId = FancyZonesUtils::GuidFromString(layoutId).value(),
.workAreaId = GetWorkAreaID(virtualDesktop),
.zoneIndexSet = zones
};
};
TEST_METHOD_INITIALIZE(Init)
{
AppZoneHistory::instance().LoadData();
}
TEST_METHOD_CLEANUP(CleanUp)
{
std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName());
}
TEST_METHOD (SyncVirtualDesktops_SwitchVirtualDesktop)
{
AppZoneHistory::TAppZoneHistoryMap history{};
const std::wstring app = L"app";
history.insert({ app, std::vector<FancyZonesDataTypes::AppZoneHistoryData>{
GetAppZoneHistoryData(virtualDesktop1, L"{147243D0-1111-4225-BCD3-31029FE384FC}", { 0 }),
GetAppZoneHistoryData(virtualDesktop2, L"{EAC1BB3B-13D6-4839-BBF7-58C3E8AB7229}", { 1 }),
} });
AppZoneHistory::instance().SetAppZoneHistory(history);
GUID currentVirtualDesktop = virtualDesktop1;
GUID lastUsedVirtualDesktop = virtualDesktop2;
std::optional<std::vector<GUID>> virtualDesktopsInRegistry = { { virtualDesktop1, virtualDesktop2 } };
AppZoneHistory::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry);
Assert::IsTrue(history.at(app)[0] == AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(virtualDesktop1)).value());
Assert::IsTrue(history.at(app)[1] == AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(virtualDesktop2)).value());
}
TEST_METHOD (SyncVirtualDesktops_CurrentVirtualDesktopDeleted)
{
AppZoneHistory::TAppZoneHistoryMap history{};
const std::wstring app = L"app";
history.insert({ app, std::vector<FancyZonesDataTypes::AppZoneHistoryData>{
GetAppZoneHistoryData(virtualDesktop1, L"{147243D0-1111-4225-BCD3-31029FE384FC}", { 0 }),
GetAppZoneHistoryData(deletedVirtualDesktop, L"{EAC1BB3B-13D6-4839-BBF7-58C3E8AB7229}", { 1 }),
} });
AppZoneHistory::instance().SetAppZoneHistory(history);
GUID currentVirtualDesktop = virtualDesktop1;
GUID lastUsedVirtualDesktop = deletedVirtualDesktop;
std::optional<std::vector<GUID>> virtualDesktopsInRegistry = { { virtualDesktop1 } };
AppZoneHistory::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry);
Assert::IsTrue(history.at(app)[0] == AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(virtualDesktop1)).value());
Assert::IsFalse(AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(deletedVirtualDesktop)).has_value());
}
TEST_METHOD (SyncVirtualDesktops_NotCurrentVirtualDesktopDeleted)
{
AppZoneHistory::TAppZoneHistoryMap history{};
const std::wstring app = L"app";
history.insert({ app, std::vector<FancyZonesDataTypes::AppZoneHistoryData>{
GetAppZoneHistoryData(virtualDesktop1, L"{147243D0-1111-4225-BCD3-31029FE384FC}", { 0 }),
GetAppZoneHistoryData(deletedVirtualDesktop, L"{EAC1BB3B-13D6-4839-BBF7-58C3E8AB7229}", { 1 }),
} });
AppZoneHistory::instance().SetAppZoneHistory(history);
GUID currentVirtualDesktop = virtualDesktop1;
GUID lastUsedVirtualDesktop = virtualDesktop1;
std::optional<std::vector<GUID>> virtualDesktopsInRegistry = { { virtualDesktop1 } };
AppZoneHistory::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry);
Assert::IsTrue(history.at(app)[0] == AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(virtualDesktop1)).value());
Assert::IsFalse(AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(deletedVirtualDesktop)).has_value());
}
TEST_METHOD (SyncVirtualDesktops_AllIdsFromRegistryAreNew)
{
AppZoneHistory::TAppZoneHistoryMap history{};
const std::wstring app = L"app";
history.insert({ app, std::vector<FancyZonesDataTypes::AppZoneHistoryData>{
GetAppZoneHistoryData(deletedVirtualDesktop, L"{147243D0-1111-4225-BCD3-31029FE384FC}", { 0 }),
} });
AppZoneHistory::instance().SetAppZoneHistory(history);
GUID currentVirtualDesktop = virtualDesktop1;
GUID lastUsedVirtualDesktop = deletedVirtualDesktop;
std::optional<std::vector<GUID>> virtualDesktopsInRegistry = { { virtualDesktop1, virtualDesktop2 } };
AppZoneHistory::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry);
auto expected = history.at(app)[0];
expected.workAreaId.virtualDesktopId = currentVirtualDesktop;
Assert::IsTrue(expected == AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(virtualDesktop1)).value());
Assert::IsFalse(AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(virtualDesktop2)).has_value());
Assert::IsFalse(AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(deletedVirtualDesktop)).has_value());
}
TEST_METHOD (SyncVirtualDesktop_NoDesktopsInRegistry)
{
AppZoneHistory::TAppZoneHistoryMap history{};
const std::wstring app = L"app";
history.insert({ app, std::vector<FancyZonesDataTypes::AppZoneHistoryData>{
GetAppZoneHistoryData(deletedVirtualDesktop, L"{147243D0-1111-4225-BCD3-31029FE384FC}", { 0 }),
} });
AppZoneHistory::instance().SetAppZoneHistory(history);
GUID currentVirtualDesktop = GUID_NULL;
GUID lastUsedVirtualDesktop = deletedVirtualDesktop;
std::optional<std::vector<GUID>> virtualDesktopsInRegistry = std::nullopt;
AppZoneHistory::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry);
auto expected = history.at(app)[0];
expected.workAreaId.virtualDesktopId = currentVirtualDesktop;
Assert::IsTrue(expected == AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(currentVirtualDesktop)).value());
Assert::IsFalse(AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(deletedVirtualDesktop)).has_value());
}
TEST_METHOD (SyncVirtualDesktop_SwithVirtualDesktopFirstTime)
{
AppZoneHistory::TAppZoneHistoryMap history{};
const std::wstring app = L"app";
history.insert({ app, std::vector<FancyZonesDataTypes::AppZoneHistoryData>{
GetAppZoneHistoryData(GUID_NULL, L"{147243D0-1111-4225-BCD3-31029FE384FC}", { 0 }),
} });
AppZoneHistory::instance().SetAppZoneHistory(history);
GUID currentVirtualDesktop = virtualDesktop1;
GUID lastUsedVirtualDesktop = GUID_NULL;
std::optional<std::vector<GUID>> virtualDesktopsInRegistry = { { virtualDesktop1, virtualDesktop2 } };
AppZoneHistory::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry);
auto expected = history.at(app)[0];
expected.workAreaId.virtualDesktopId = currentVirtualDesktop;
Assert::IsTrue(expected == AppZoneHistory::instance().GetZoneHistory(app, GetWorkAreaID(currentVirtualDesktop)).value());
}
};
} }

View File

@ -14,24 +14,14 @@ namespace FancyZonesUnitTests
{ {
TEST_CLASS (AppliedLayoutsUnitTests) TEST_CLASS (AppliedLayoutsUnitTests)
{ {
FancyZonesData& m_fzData = FancyZonesDataInstance();
std::wstring m_testFolder = L"FancyZonesUnitTests";
std::wstring m_testFolderPath = PTSettingsHelper::get_module_save_folder_location(m_testFolder);
TEST_METHOD_INITIALIZE(Init) TEST_METHOD_INITIALIZE(Init)
{ {
m_fzData.SetSettingsModulePath(L"FancyZonesUnitTests"); AppliedLayouts::instance().LoadData();
} }
TEST_METHOD_CLEANUP(CleanUp) TEST_METHOD_CLEANUP(CleanUp)
{ {
// Move...FromZonesSettings creates all of these files, clean up
std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName()); std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName());
std::filesystem::remove(CustomLayouts::CustomLayoutsFileName());
std::filesystem::remove(LayoutHotkeys::LayoutHotkeysFileName());
std::filesystem::remove(LayoutTemplates::LayoutTemplatesFileName());
std::filesystem::remove_all(m_testFolderPath);
AppliedLayouts::instance().LoadData(); // clean data
} }
TEST_METHOD (AppliedLayoutsParse) TEST_METHOD (AppliedLayoutsParse)
@ -75,7 +65,7 @@ namespace FancyZonesUnitTests
Assert::IsTrue(AppliedLayouts::instance().IsLayoutApplied(id)); Assert::IsTrue(AppliedLayouts::instance().IsLayoutApplied(id));
} }
TEST_METHOD(AppliedLayoutsParseDataWithResolution) TEST_METHOD (AppliedLayoutsParseDataWithResolution)
{ {
// prepare // prepare
json::JsonObject root{}; json::JsonObject root{};
@ -242,143 +232,94 @@ namespace FancyZonesUnitTests
Assert::IsTrue(AppliedLayouts::instance().GetAppliedLayoutMap().empty()); Assert::IsTrue(AppliedLayouts::instance().GetAppliedLayoutMap().empty());
} }
TEST_METHOD (MoveAppliedLayoutsFromZonesSettings) TEST_METHOD (Save)
{ {
// prepare FancyZonesDataTypes::WorkAreaId workAreaId1{
json::JsonObject root{}; .monitorId = {
json::JsonArray devicesArray{}, customLayoutsArray{}, templateLayoutsArray{}, quickLayoutKeysArray{}; .deviceId = { .id = L"id-1", .instanceId = L"id-1", .number = 1 },
.serialNumber = L"serial-number-1"
{ },
json::JsonObject activeZoneset{}; .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{30387C86-BB15-476D-8683-AF93F6D73E99}").value()
activeZoneset.SetNamedValue(L"uuid", json::value(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}")); };
activeZoneset.SetNamedValue(L"type", json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Rows))); FancyZonesDataTypes::WorkAreaId workAreaId2{
.monitorId = {
json::JsonObject obj{}; .deviceId = { .id = L"id-2", .instanceId = L"id-2", .number = 2 },
obj.SetNamedValue(L"device-id", json::value(L"VSC9636#5&37ac4db&0&UID160005_3840_2160_{00000000-0000-0000-0000-000000000000}")); .serialNumber = L"serial-number-2" },
obj.SetNamedValue(L"active-zoneset", activeZoneset);; .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{30387C86-BB15-476D-8683-AF93F6D73E99}").value()
obj.SetNamedValue(L"editor-show-spacing", json::value(true)); };
obj.SetNamedValue(L"editor-spacing", json::value(3)); FancyZonesDataTypes::WorkAreaId workAreaId3{
obj.SetNamedValue(L"editor-zone-count", json::value(4)); .monitorId = {
obj.SetNamedValue(L"editor-sensitivity-radius", json::value(22)); .deviceId = { .id = L"id-1", .instanceId = L"id-1", .number = 1 },
.serialNumber = L"serial-number-1" },
devicesArray.Append(obj); .virtualDesktopId = GUID_NULL
} };
FancyZonesDataTypes::WorkAreaId workAreaId4{
root.SetNamedValue(L"devices", devicesArray); .monitorId = {
root.SetNamedValue(L"custom-zone-sets", customLayoutsArray); .deviceId = { .id = L"id-2", .instanceId = L"id-2", .number = 2 },
root.SetNamedValue(L"templates", templateLayoutsArray); .serialNumber = L"serial-number-2" },
root.SetNamedValue(L"quick-layout-keys", quickLayoutKeysArray); .virtualDesktopId = GUID_NULL
json::to_file(m_fzData.GetZoneSettingsPath(m_testFolder), root);
// test
m_fzData.ReplaceZoneSettingsFileFromOlderVersions();
AppliedLayouts::instance().LoadData();
Assert::AreEqual((size_t)1, AppliedLayouts::instance().GetAppliedLayoutMap().size());
FancyZonesDataTypes::WorkAreaId id{
.monitorId = { .deviceId = { .id = L"VSC9636", .instanceId = L"5&37ac4db&0&UID160005" }, .serialNumber = L"" },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{00000000-0000-0000-0000-000000000000}").value()
}; };
Assert::IsTrue(AppliedLayouts::instance().GetDeviceLayout(id).has_value());
}
TEST_METHOD (MoveAppliedLayoutsFromZonesSettingsNoAppliedLayoutsData) LayoutData layout1{ .uuid = FancyZonesUtils::GuidFromString(L"{D7DBECFA-23FC-4F45-9B56-51CFA9F6ABA2}").value() };
{ LayoutData layout2{ .uuid = FancyZonesUtils::GuidFromString(L"{B9EDB48C-EC48-4E82-993F-A15DC1FF09D3}").value() };
// prepare LayoutData layout3{ .uuid = FancyZonesUtils::GuidFromString(L"{94CF0000-7814-4D72-9624-794060FA269C}").value() };
json::JsonObject root{}; LayoutData layout4{ .uuid = FancyZonesUtils::GuidFromString(L"{13FA7ADF-1B6C-4FB6-8142-254B77C128E2}").value() };
json::JsonArray customLayoutsArray{}, templateLayoutsArray{}, quickLayoutKeysArray{};
root.SetNamedValue(L"custom-zone-sets", customLayoutsArray); AppliedLayouts::TAppliedLayoutsMap expected{};
root.SetNamedValue(L"templates", templateLayoutsArray); expected.insert({ workAreaId1, layout1 });
root.SetNamedValue(L"quick-layout-keys", quickLayoutKeysArray); expected.insert({ workAreaId2, layout2 });
json::to_file(m_fzData.GetZoneSettingsPath(m_testFolder), root); expected.insert({ workAreaId3, layout3 });
expected.insert({ workAreaId4, layout4 });
AppliedLayouts::instance().SetAppliedLayouts(expected);
AppliedLayouts::instance().SaveData();
// test
m_fzData.ReplaceZoneSettingsFileFromOlderVersions();
AppliedLayouts::instance().LoadData(); AppliedLayouts::instance().LoadData();
Assert::IsTrue(AppliedLayouts::instance().GetAppliedLayoutMap().empty()); auto actual = AppliedLayouts::instance().GetAppliedLayoutMap();
} Assert::AreEqual(expected.size(), actual.size());
Assert::IsTrue(expected.at(workAreaId1) == actual.at(workAreaId1));
TEST_METHOD (MoveAppliedLayoutsFromZonesSettingsNoFile) Assert::IsTrue(expected.at(workAreaId2) == actual.at(workAreaId2));
{ Assert::IsTrue(expected.at(workAreaId3) == actual.at(workAreaId3));
// test Assert::IsTrue(expected.at(workAreaId4) == actual.at(workAreaId4));
m_fzData.ReplaceZoneSettingsFileFromOlderVersions();
AppliedLayouts::instance().LoadData();
Assert::IsTrue(AppliedLayouts::instance().GetAppliedLayoutMap().empty());
} }
TEST_METHOD (CloneDeviceInfo) TEST_METHOD (CloneDeviceInfo)
{ {
FancyZonesDataTypes::WorkAreaId deviceSrc{ FancyZonesDataTypes::WorkAreaId deviceSrc{
.monitorId = { .deviceId = { .id = L"Device1", .instanceId = L"" }, .serialNumber = L"" }, .monitorId = { .deviceId = { .id = L"Device1", .instanceId = L"" }, .serialNumber = L"" },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{00000000-0000-0000-0000-000000000000}").value() .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{EA6B6934-D55F-49F5-A9A5-CFADE21FFFB8}").value()
}; };
FancyZonesDataTypes::WorkAreaId deviceDst{ FancyZonesDataTypes::WorkAreaId deviceDst{
.monitorId = { .deviceId = { .id = L"Device2", .instanceId = L"" }, .serialNumber = L"" }, .monitorId = { .deviceId = { .id = L"Device2", .instanceId = L"" }, .serialNumber = L"" },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{00000000-0000-0000-0000-000000000000}").value() .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{EF1A8099-7D1E-4738-805A-571B31B02674}").value()
}; };
Assert::IsTrue(AppliedLayouts::instance().ApplyDefaultLayout(deviceSrc)); LayoutData layout { .uuid = FancyZonesUtils::GuidFromString(L"{361F96DD-FD10-4D01-ABAC-CC1C857294DD}").value() };
Assert::IsTrue(AppliedLayouts::instance().ApplyDefaultLayout(deviceDst)); Assert::IsTrue(AppliedLayouts::instance().ApplyLayout(deviceSrc, layout));
AppliedLayouts::instance().CloneLayout(deviceSrc, deviceDst); AppliedLayouts::instance().CloneLayout(deviceSrc, deviceDst);
auto actualMap = AppliedLayouts::instance().GetAppliedLayoutMap(); Assert::IsTrue(layout == AppliedLayouts::instance().GetDeviceLayout(deviceSrc));
Assert::IsFalse(actualMap.find(deviceSrc) == actualMap.end()); Assert::IsTrue(layout == AppliedLayouts::instance().GetDeviceLayout(deviceDst));
Assert::IsFalse(actualMap.find(deviceDst) == actualMap.end());
auto expected = AppliedLayouts::instance().GetDeviceLayout(deviceSrc);
auto actual = AppliedLayouts::instance().GetDeviceLayout(deviceDst);
Assert::IsTrue(expected.has_value());
Assert::IsTrue(actual.has_value());
Assert::IsTrue(expected.value().uuid == actual.value().uuid);
}
TEST_METHOD (CloneDeviceInfoIntoUnknownDevice)
{
FancyZonesDataTypes::WorkAreaId deviceSrc{
.monitorId = { .deviceId = { .id = L"Device1", .instanceId = L"" }, .serialNumber = L"" },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{00000000-0000-0000-0000-000000000000}").value()
};
FancyZonesDataTypes::WorkAreaId deviceDst{
.monitorId = { .deviceId = { .id = L"Device2", .instanceId = L"" }, .serialNumber = L"" },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{00000000-0000-0000-0000-000000000000}").value()
};
Assert::IsTrue(AppliedLayouts::instance().ApplyDefaultLayout(deviceSrc));
AppliedLayouts::instance().CloneLayout(deviceSrc, deviceDst);
auto actualMap = AppliedLayouts::instance().GetAppliedLayoutMap();
Assert::IsFalse(actualMap.find(deviceSrc) == actualMap.end());
Assert::IsFalse(actualMap.find(deviceDst) == actualMap.end());
auto expected = AppliedLayouts::instance().GetDeviceLayout(deviceSrc);
auto actual = AppliedLayouts::instance().GetDeviceLayout(deviceDst);
Assert::IsTrue(expected.has_value());
Assert::IsTrue(actual.has_value());
Assert::IsTrue(expected.value().uuid == actual.value().uuid);
} }
TEST_METHOD (CloneDeviceInfoFromUnknownDevice) TEST_METHOD (CloneDeviceInfoFromUnknownDevice)
{ {
FancyZonesDataTypes::WorkAreaId deviceSrc{ FancyZonesDataTypes::WorkAreaId deviceSrc{
.monitorId = { .deviceId = { .id = L"Device1", .instanceId = L"" }, .serialNumber = L"" }, .monitorId = { .deviceId = { .id = L"Device1", .instanceId = L"" }, .serialNumber = L"" },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{00000000-0000-0000-0000-000000000000}").value() .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{EA6B6934-D55F-49F5-A9A5-CFADE21FFFB8}").value()
}; };
FancyZonesDataTypes::WorkAreaId deviceDst{ FancyZonesDataTypes::WorkAreaId deviceDst{
.monitorId = { .deviceId = { .id = L"Device2", .instanceId = L"" }, .serialNumber = L"" }, .monitorId = { .deviceId = { .id = L"Device2", .instanceId = L"" }, .serialNumber = L"" },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{00000000-0000-0000-0000-000000000000}").value() .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{EF1A8099-7D1E-4738-805A-571B31B02674}").value()
}; };
AppliedLayouts::instance().LoadData(); AppliedLayouts::instance().LoadData();
Assert::IsTrue(AppliedLayouts::instance().ApplyDefaultLayout(deviceDst));
Assert::IsFalse(AppliedLayouts::instance().CloneLayout(deviceSrc, deviceDst)); Assert::IsFalse(AppliedLayouts::instance().CloneLayout(deviceSrc, deviceDst));
Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(deviceSrc).has_value()); Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(deviceSrc).has_value());
Assert::IsTrue(AppliedLayouts::instance().GetDeviceLayout(deviceDst).has_value()); Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(deviceDst).has_value());
} }
TEST_METHOD (CloneDeviceInfoNullVirtualDesktopId) TEST_METHOD (CloneDeviceInfoNullVirtualDesktopId)
@ -389,35 +330,25 @@ namespace FancyZonesUnitTests
}; };
FancyZonesDataTypes::WorkAreaId deviceDst{ FancyZonesDataTypes::WorkAreaId deviceDst{
.monitorId = { .deviceId = { .id = L"Device2", .instanceId = L"" }, .serialNumber = L"" }, .monitorId = { .deviceId = { .id = L"Device2", .instanceId = L"" }, .serialNumber = L"" },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{00000000-0000-0000-0000-000000000000}").value() .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{EF1A8099-7D1E-4738-805A-571B31B02674}").value()
}; };
Assert::IsTrue(AppliedLayouts::instance().ApplyDefaultLayout(deviceSrc)); LayoutData layout{ .uuid = FancyZonesUtils::GuidFromString(L"{361F96DD-FD10-4D01-ABAC-CC1C857294DD}").value() };
Assert::IsTrue(AppliedLayouts::instance().ApplyDefaultLayout(deviceDst)); Assert::IsTrue(AppliedLayouts::instance().ApplyLayout(deviceSrc, layout));
AppliedLayouts::instance().CloneLayout(deviceSrc, deviceDst); AppliedLayouts::instance().CloneLayout(deviceSrc, deviceDst);
auto actualMap = AppliedLayouts::instance().GetAppliedLayoutMap(); Assert::IsTrue(layout == AppliedLayouts::instance().GetDeviceLayout(deviceSrc));
Assert::IsFalse(actualMap.find(deviceSrc) == actualMap.end()); Assert::IsTrue(layout == AppliedLayouts::instance().GetDeviceLayout(deviceDst));
Assert::IsFalse(actualMap.find(deviceDst) == actualMap.end());
auto expected = AppliedLayouts::instance().GetDeviceLayout(deviceSrc);
auto actual = AppliedLayouts::instance().GetDeviceLayout(deviceDst);
Assert::IsTrue(expected.has_value());
Assert::IsTrue(actual.has_value());
Assert::IsTrue(expected.value().uuid == actual.value().uuid);
} }
TEST_METHOD (ApplyLayout) TEST_METHOD (ApplyLayout)
{ {
// prepare FancyZonesDataTypes::WorkAreaId workAreaId {
FancyZonesDataTypes::WorkAreaId deviceId {
.monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" }, .serialNumber = L"" }, .monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" }, .serialNumber = L"" },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{61FA9FC0-26A6-4B37-A834-491C148DFC57}").value() .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{61FA9FC0-26A6-4B37-A834-491C148DFC57}").value()
}; };
// test
LayoutData expectedLayout { LayoutData expectedLayout {
.uuid = FancyZonesUtils::GuidFromString(L"{33A2B101-06E0-437B-A61E-CDBECF502906}").value(), .uuid = FancyZonesUtils::GuidFromString(L"{33A2B101-06E0-437B-A61E-CDBECF502906}").value(),
.type = FancyZonesDataTypes::ZoneSetLayoutType::Focus, .type = FancyZonesDataTypes::ZoneSetLayoutType::Focus,
@ -427,47 +358,30 @@ namespace FancyZonesUnitTests
.sensitivityRadius = 30 .sensitivityRadius = 30
}; };
AppliedLayouts::instance().ApplyLayout(deviceId, expectedLayout); AppliedLayouts::instance().ApplyLayout(workAreaId, expectedLayout);
Assert::IsFalse(AppliedLayouts::instance().GetAppliedLayoutMap().empty()); Assert::IsTrue(AppliedLayouts::instance().GetDeviceLayout(workAreaId).has_value());
Assert::IsTrue(AppliedLayouts::instance().GetDeviceLayout(deviceId).has_value()); Assert::IsTrue(expectedLayout == AppliedLayouts::instance().GetAppliedLayoutMap().find(workAreaId)->second);
auto actual = AppliedLayouts::instance().GetAppliedLayoutMap().find(deviceId)->second;
Assert::IsTrue(expectedLayout.type == actual.type);
Assert::AreEqual(expectedLayout.showSpacing, actual.showSpacing);
Assert::AreEqual(expectedLayout.spacing, actual.spacing);
Assert::AreEqual(expectedLayout.zoneCount, actual.zoneCount);
Assert::AreEqual(expectedLayout.sensitivityRadius, actual.sensitivityRadius);
} }
TEST_METHOD (ApplyLayoutReplace) TEST_METHOD (ApplyLayoutReplace)
{ {
// prepare // prepare
FancyZonesDataTypes::WorkAreaId deviceId{ FancyZonesDataTypes::WorkAreaId workAreaId{
.monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" }, .serialNumber = L"" }, .monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" }, .serialNumber = L"" },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{61FA9FC0-26A6-4B37-A834-491C148DFC57}").value() .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{61FA9FC0-26A6-4B37-A834-491C148DFC57}").value()
}; };
json::JsonObject root{}; LayoutData layout{
json::JsonArray layoutsArray{}; .uuid = FancyZonesUtils::GuidFromString(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}").value(),
{ .type = FancyZonesDataTypes::ZoneSetLayoutType::Rows,
json::JsonObject layout{}; .showSpacing = true,
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}")); .spacing = 3,
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Rows))); .zoneCount = 4,
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(true)); .sensitivityRadius = 22
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(3)); };
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4));
layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(22));
json::JsonObject obj{}; AppliedLayouts::instance().SetAppliedLayouts({ {workAreaId, layout} });
obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceIdID, json::value(L"DELA026#5&10a58c63&0&UID16777488_2194_1234_{61FA9FC0-26A6-4B37-A834-491C148DFC57}"));
obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout);
layoutsArray.Append(obj);
}
root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray);
json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root);
AppliedLayouts::instance().LoadData();
// test // test
LayoutData expectedLayout{ LayoutData expectedLayout{
@ -479,18 +393,8 @@ namespace FancyZonesUnitTests
.sensitivityRadius = 30 .sensitivityRadius = 30
}; };
AppliedLayouts::instance().ApplyLayout(deviceId, expectedLayout); AppliedLayouts::instance().ApplyLayout(workAreaId, expectedLayout);
Assert::IsTrue(expectedLayout == AppliedLayouts::instance().GetDeviceLayout(workAreaId));
Assert::AreEqual((size_t)1, AppliedLayouts::instance().GetAppliedLayoutMap().size());
Assert::IsTrue(AppliedLayouts::instance().GetDeviceLayout(deviceId).has_value());
auto actual = AppliedLayouts::instance().GetAppliedLayoutMap().find(deviceId)->second;
Assert::AreEqual(FancyZonesUtils::GuidToString(expectedLayout.uuid).value().c_str(), FancyZonesUtils::GuidToString(actual.uuid).value().c_str());
Assert::IsTrue(expectedLayout.type == actual.type);
Assert::AreEqual(expectedLayout.showSpacing, actual.showSpacing);
Assert::AreEqual(expectedLayout.spacing, actual.spacing);
Assert::AreEqual(expectedLayout.zoneCount, actual.zoneCount);
Assert::AreEqual(expectedLayout.sensitivityRadius, actual.sensitivityRadius);
} }
TEST_METHOD (ApplyDefaultLayout) TEST_METHOD (ApplyDefaultLayout)
@ -553,4 +457,245 @@ namespace FancyZonesUnitTests
Assert::IsFalse(AppliedLayouts::instance().IsLayoutApplied(id2)); Assert::IsFalse(AppliedLayouts::instance().IsLayoutApplied(id2));
} }
}; };
TEST_CLASS (AppliedLayoutsSyncVirtualDesktops)
{
const GUID virtualDesktop1 = FancyZonesUtils::GuidFromString(L"{30387C86-BB15-476D-8683-AF93F6D73E99}").value();
const GUID virtualDesktop2 = FancyZonesUtils::GuidFromString(L"{65F6343A-868F-47EE-838E-55A178A7FB7A}").value();
const GUID deletedVirtualDesktop = FancyZonesUtils::GuidFromString(L"{2D9F3E2D-F61D-4618-B35D-85C9B8DFDFD8}").value();
LayoutData layout1{ .uuid = FancyZonesUtils::GuidFromString(L"{D7DBECFA-23FC-4F45-9B56-51CFA9F6ABA2}").value() };
LayoutData layout2{ .uuid = FancyZonesUtils::GuidFromString(L"{B9EDB48C-EC48-4E82-993F-A15DC1FF09D3}").value() };
LayoutData layout3{ .uuid = FancyZonesUtils::GuidFromString(L"{94CF0000-7814-4D72-9624-794060FA269C}").value() };
LayoutData layout4{ .uuid = FancyZonesUtils::GuidFromString(L"{13FA7ADF-1B6C-4FB6-8142-254B77C128E2}").value() };
FancyZonesDataTypes::WorkAreaId GetWorkAreaID(int number, GUID virtualDesktop)
{
return FancyZonesDataTypes::WorkAreaId{
.monitorId = {
.deviceId = {
.id = std::wstring(L"id-") + std::to_wstring(number),
.instanceId = std::wstring(L"id-") + std::to_wstring(number),
.number = number
},
.serialNumber = std::wstring(L"serial-number-") + std::to_wstring(number)
},
.virtualDesktopId = virtualDesktop
};
}
TEST_METHOD_INITIALIZE(Init)
{
AppliedLayouts::instance().LoadData();
}
TEST_METHOD_CLEANUP(CleanUp)
{
std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName());
}
TEST_METHOD(SyncVirtualDesktops_SwitchVirtualDesktop)
{
AppliedLayouts::TAppliedLayoutsMap layouts{};
layouts.insert({ GetWorkAreaID(1, virtualDesktop1), layout1 });
layouts.insert({ GetWorkAreaID(2, virtualDesktop1), layout2 });
layouts.insert({ GetWorkAreaID(1, virtualDesktop2), layout3 });
layouts.insert({ GetWorkAreaID(2, virtualDesktop2), layout4 });
AppliedLayouts::instance().SetAppliedLayouts(layouts);
GUID currentVirtualDesktop = virtualDesktop1;
GUID lastUsedVirtualDesktop = virtualDesktop2;
std::optional<std::vector<GUID>> virtualDesktopsInRegistry = { { virtualDesktop1, virtualDesktop2 } };
AppliedLayouts::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry);
Assert::IsTrue(layout1 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, virtualDesktop1)));
Assert::IsTrue(layout2 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, virtualDesktop1)));
Assert::IsTrue(layout3 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, virtualDesktop2)));
Assert::IsTrue(layout4 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, virtualDesktop2)));
}
TEST_METHOD (SyncVirtualDesktops_CurrentVirtualDesktopDeleted)
{
AppliedLayouts::TAppliedLayoutsMap layouts{};
layouts.insert({ GetWorkAreaID(1, virtualDesktop1), layout1 });
layouts.insert({ GetWorkAreaID(2, virtualDesktop1), layout2 });
layouts.insert({ GetWorkAreaID(1, deletedVirtualDesktop), layout3 });
layouts.insert({ GetWorkAreaID(2, deletedVirtualDesktop), layout4 });
AppliedLayouts::instance().SetAppliedLayouts(layouts);
GUID currentVirtualDesktop = virtualDesktop1;
GUID lastUsedVirtualDesktop = deletedVirtualDesktop;
std::optional<std::vector<GUID>> virtualDesktopsInRegistry = { { virtualDesktop1 } };
AppliedLayouts::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry);
Assert::IsTrue(layout1 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, virtualDesktop1)));
Assert::IsTrue(layout2 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, virtualDesktop1)));
Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, deletedVirtualDesktop)).has_value());
Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, deletedVirtualDesktop)).has_value());
}
TEST_METHOD (SyncVirtualDesktops_NotCurrentVirtualDesktopDeleted)
{
AppliedLayouts::TAppliedLayoutsMap layouts{};
layouts.insert({ GetWorkAreaID(1, virtualDesktop1), layout1 });
layouts.insert({ GetWorkAreaID(2, virtualDesktop1), layout2 });
layouts.insert({ GetWorkAreaID(1, deletedVirtualDesktop), layout3 });
layouts.insert({ GetWorkAreaID(2, deletedVirtualDesktop), layout4 });
AppliedLayouts::instance().SetAppliedLayouts(layouts);
GUID currentVirtualDesktop = virtualDesktop1;
GUID lastUsedVirtualDesktop = virtualDesktop1;
std::optional<std::vector<GUID>> virtualDesktopsInRegistry = { { virtualDesktop1 } };
AppliedLayouts::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry);
Assert::IsTrue(layout1 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, virtualDesktop1)));
Assert::IsTrue(layout2 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, virtualDesktop1)));
Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, deletedVirtualDesktop)).has_value());
Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, deletedVirtualDesktop)).has_value());
}
TEST_METHOD (SyncVirtualDesktops_AllIdsFromRegistryAreNew)
{
AppliedLayouts::TAppliedLayoutsMap layouts{};
layouts.insert({ GetWorkAreaID(1, deletedVirtualDesktop), layout1 });
layouts.insert({ GetWorkAreaID(2, deletedVirtualDesktop), layout2 });
AppliedLayouts::instance().SetAppliedLayouts(layouts);
GUID currentVirtualDesktop = virtualDesktop1;
GUID lastUsedVirtualDesktop = deletedVirtualDesktop;
std::optional<std::vector<GUID>> virtualDesktopsInRegistry = { { virtualDesktop1, virtualDesktop2 } };
AppliedLayouts::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry);
Assert::IsTrue(layout1 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, virtualDesktop1)));
Assert::IsTrue(layout2 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, virtualDesktop1)));
Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, virtualDesktop2)).has_value());
Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, virtualDesktop2)).has_value());
Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, deletedVirtualDesktop)).has_value());
Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, deletedVirtualDesktop)).has_value());
}
TEST_METHOD (SyncVirtualDesktop_NoDesktopsInRegistry)
{
AppliedLayouts::TAppliedLayoutsMap layouts{};
layouts.insert({ GetWorkAreaID(1, deletedVirtualDesktop), layout1 });
layouts.insert({ GetWorkAreaID(2, deletedVirtualDesktop), layout2 });
AppliedLayouts::instance().SetAppliedLayouts(layouts);
GUID currentVirtualDesktop = GUID_NULL;
GUID lastUsedVirtualDesktop = deletedVirtualDesktop;
std::optional<std::vector<GUID>> virtualDesktopsInRegistry = std::nullopt;
AppliedLayouts::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry);
Assert::IsTrue(layout1 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, GUID_NULL)));
Assert::IsTrue(layout2 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, GUID_NULL)));
Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, deletedVirtualDesktop)).has_value());
Assert::IsFalse(AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, deletedVirtualDesktop)).has_value());
}
TEST_METHOD(SyncVirtualDesktops_SwithVirtualDesktopFirstTime)
{
AppliedLayouts::TAppliedLayoutsMap layouts{};
layouts.insert({ GetWorkAreaID(1, GUID_NULL), layout1 });
layouts.insert({ GetWorkAreaID(2, GUID_NULL), layout2 });
AppliedLayouts::instance().SetAppliedLayouts(layouts);
GUID currentVirtualDesktop = virtualDesktop2;
GUID lastUsedVirtualDesktop = GUID_NULL;
std::optional<std::vector<GUID>> virtualDesktopsInRegistry = { { virtualDesktop1, virtualDesktop2 } };
AppliedLayouts::instance().SyncVirtualDesktops(currentVirtualDesktop, lastUsedVirtualDesktop, virtualDesktopsInRegistry);
Assert::IsTrue(layout1 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, virtualDesktop1)));
Assert::IsTrue(layout2 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, virtualDesktop1)));
Assert::IsTrue(layout1 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(1, virtualDesktop2)));
Assert::IsTrue(layout2 == AppliedLayouts::instance().GetDeviceLayout(GetWorkAreaID(2, virtualDesktop2)));
}
};
TEST_CLASS (AppliedLayoutsFromOutdatedFileMappingUnitTests)
{
FancyZonesData& m_fzData = FancyZonesDataInstance();
std::wstring m_testFolder = L"FancyZonesUnitTests";
std::wstring m_testFolderPath = PTSettingsHelper::get_module_save_folder_location(m_testFolder);
TEST_METHOD_INITIALIZE(Init)
{
m_fzData.SetSettingsModulePath(m_testFolder);
}
TEST_METHOD_CLEANUP(CleanUp)
{
// MoveAppliedLayoutsFromZonesSettings creates all of these files, clean up
std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName());
std::filesystem::remove(CustomLayouts::CustomLayoutsFileName());
std::filesystem::remove(LayoutHotkeys::LayoutHotkeysFileName());
std::filesystem::remove(LayoutTemplates::LayoutTemplatesFileName());
std::filesystem::remove_all(m_testFolderPath);
AppliedLayouts::instance().LoadData(); // clean data
}
TEST_METHOD (MoveAppliedLayoutsFromZonesSettings)
{
// prepare
json::JsonObject root{};
json::JsonArray devicesArray{}, customLayoutsArray{}, templateLayoutsArray{}, quickLayoutKeysArray{};
{
json::JsonObject activeZoneset{};
activeZoneset.SetNamedValue(L"uuid", json::value(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}"));
activeZoneset.SetNamedValue(L"type", json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Rows)));
json::JsonObject obj{};
obj.SetNamedValue(L"device-id", json::value(L"VSC9636#5&37ac4db&0&UID160005_3840_2160_{00000000-0000-0000-0000-000000000000}"));
obj.SetNamedValue(L"active-zoneset", activeZoneset);
obj.SetNamedValue(L"editor-show-spacing", json::value(true));
obj.SetNamedValue(L"editor-spacing", json::value(3));
obj.SetNamedValue(L"editor-zone-count", json::value(4));
obj.SetNamedValue(L"editor-sensitivity-radius", json::value(22));
devicesArray.Append(obj);
}
root.SetNamedValue(L"devices", devicesArray);
root.SetNamedValue(L"custom-zone-sets", customLayoutsArray);
root.SetNamedValue(L"templates", templateLayoutsArray);
root.SetNamedValue(L"quick-layout-keys", quickLayoutKeysArray);
json::to_file(m_fzData.GetZoneSettingsPath(m_testFolder), root);
// test
m_fzData.ReplaceZoneSettingsFileFromOlderVersions();
AppliedLayouts::instance().LoadData();
Assert::AreEqual((size_t)1, AppliedLayouts::instance().GetAppliedLayoutMap().size());
FancyZonesDataTypes::WorkAreaId id{
.monitorId = { .deviceId = { .id = L"VSC9636", .instanceId = L"5&37ac4db&0&UID160005" }, .serialNumber = L"" },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{00000000-0000-0000-0000-000000000000}").value()
};
Assert::IsTrue(AppliedLayouts::instance().GetDeviceLayout(id).has_value());
}
TEST_METHOD (MoveAppliedLayoutsFromZonesSettingsNoAppliedLayoutsData)
{
// prepare
json::JsonObject root{};
json::JsonArray customLayoutsArray{}, templateLayoutsArray{}, quickLayoutKeysArray{};
root.SetNamedValue(L"custom-zone-sets", customLayoutsArray);
root.SetNamedValue(L"templates", templateLayoutsArray);
root.SetNamedValue(L"quick-layout-keys", quickLayoutKeysArray);
json::to_file(m_fzData.GetZoneSettingsPath(m_testFolder), root);
// test
m_fzData.ReplaceZoneSettingsFileFromOlderVersions();
AppliedLayouts::instance().LoadData();
Assert::IsTrue(AppliedLayouts::instance().GetAppliedLayoutMap().empty());
}
TEST_METHOD (MoveAppliedLayoutsFromZonesSettingsNoFile)
{
// test
m_fzData.ReplaceZoneSettingsFileFromOlderVersions();
AppliedLayouts::instance().LoadData();
Assert::IsTrue(AppliedLayouts::instance().GetAppliedLayoutMap().empty());
}
};
} }

View File

@ -43,11 +43,11 @@ namespace FancyZonesUnitTests
Assert::IsFalse(id1 == id2); Assert::IsFalse(id1 == id2);
} }
TEST_METHOD (VirtualDesktopNull) TEST_METHOD (VirtualDesktopDifferent)
{ {
FancyZonesDataTypes::WorkAreaId id1{ FancyZonesDataTypes::WorkAreaId id1{
.monitorId = { .deviceId = { .id = L"device", .instanceId = L"instance-id" }, .serialNumber = L"serial-number" }, .monitorId = { .deviceId = { .id = L"device", .instanceId = L"instance-id" }, .serialNumber = L"serial-number" },
.virtualDesktopId = GUID_NULL .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{F21F6F29-76FD-4FC1-8970-17AB8AD64847}").value()
}; };
FancyZonesDataTypes::WorkAreaId id2{ FancyZonesDataTypes::WorkAreaId id2{
@ -55,14 +55,14 @@ namespace FancyZonesUnitTests
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{E21F6F29-76FD-4FC1-8970-17AB8AD64847}").value() .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{E21F6F29-76FD-4FC1-8970-17AB8AD64847}").value()
}; };
Assert::IsTrue(id1 == id2); Assert::IsFalse(id1 == id2);
} }
TEST_METHOD (VirtualDesktopDifferent) TEST_METHOD (VirtualDesktopNull)
{ {
FancyZonesDataTypes::WorkAreaId id1{ FancyZonesDataTypes::WorkAreaId id1{
.monitorId = { .deviceId = { .id = L"device", .instanceId = L"instance-id" }, .serialNumber = L"serial-number" }, .monitorId = { .deviceId = { .id = L"device", .instanceId = L"instance-id" }, .serialNumber = L"serial-number" },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{F21F6F29-76FD-4FC1-8970-17AB8AD64847}").value() .virtualDesktopId = GUID_NULL
}; };
FancyZonesDataTypes::WorkAreaId id2{ FancyZonesDataTypes::WorkAreaId id2{

View File

@ -52,7 +52,7 @@ namespace Microsoft.Plugin.Shell
{ {
Key = "ShellCommandExecution", Key = "ShellCommandExecution",
DisplayLabel = Resources.wox_shell_command_execution, DisplayLabel = Resources.wox_shell_command_execution,
SelectionTypeValue = (int)PluginAdditionalOption.SelectionType.Combobox, PluginOptionType = PluginAdditionalOption.AdditionalOptionType.Combobox,
ComboBoxOptions = new List<string> ComboBoxOptions = new List<string>
{ {
Resources.run_command_in_command_prompt, Resources.run_command_in_command_prompt,
@ -60,7 +60,7 @@ namespace Microsoft.Plugin.Shell
Resources.find_executable_file_and_run_it, Resources.find_executable_file_and_run_it,
Resources.run_command_in_windows_terminal, Resources.run_command_in_windows_terminal,
}, },
Option = (int)_settings.Shell, ComboBoxValue = (int)_settings.Shell,
}, },
}; };
@ -439,7 +439,7 @@ namespace Microsoft.Plugin.Shell
_settings.LeaveShellOpen = leaveShellOpen; _settings.LeaveShellOpen = leaveShellOpen;
var optionShell = settings.AdditionalOptions.FirstOrDefault(x => x.Key == "ShellCommandExecution"); var optionShell = settings.AdditionalOptions.FirstOrDefault(x => x.Key == "ShellCommandExecution");
shellOption = optionShell?.Option ?? shellOption; shellOption = optionShell?.ComboBoxValue ?? shellOption;
_settings.Shell = (ExecutionShell)shellOption; _settings.Shell = (ExecutionShell)shellOption;
} }

View File

@ -24,6 +24,8 @@ namespace Peek.FilePreviewer.Controls
/// </summary> /// </summary>
private Uri? _navigatedUri; private Uri? _navigatedUri;
private Color originalBackgroundColor;
public delegate void NavigationCompletedHandler(WebView2? sender, CoreWebView2NavigationCompletedEventArgs? args); public delegate void NavigationCompletedHandler(WebView2? sender, CoreWebView2NavigationCompletedEventArgs? args);
public delegate void DOMContentLoadedHandler(CoreWebView2? sender, CoreWebView2DOMContentLoadedEventArgs? args); public delegate void DOMContentLoadedHandler(CoreWebView2? sender, CoreWebView2DOMContentLoadedEventArgs? args);
@ -120,7 +122,11 @@ namespace Peek.FilePreviewer.Controls
{ {
await PreviewBrowser.EnsureCoreWebView2Async(); await PreviewBrowser.EnsureCoreWebView2Async();
// transparent background when loading the page // Storing the original background color so it can be reset later for specific file types like HTML.
originalBackgroundColor = PreviewBrowser.DefaultBackgroundColor;
// Setting the background color to transparent when initially loading the WebView2 component.
// This ensures that non-HTML files are displayed with a transparent background.
PreviewBrowser.DefaultBackgroundColor = Color.FromArgb(0, 0, 0, 0); PreviewBrowser.DefaultBackgroundColor = Color.FromArgb(0, 0, 0, 0);
PreviewBrowser.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = false; PreviewBrowser.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = false;
@ -150,6 +156,15 @@ namespace Peek.FilePreviewer.Controls
private void CoreWebView2_DOMContentLoaded(CoreWebView2 sender, CoreWebView2DOMContentLoadedEventArgs args) private void CoreWebView2_DOMContentLoaded(CoreWebView2 sender, CoreWebView2DOMContentLoadedEventArgs args)
{ {
// If the file being previewed is HTML or HTM, reset the background color to its original state.
// This is done to ensure that HTML and HTM files are displayed as intended, with their own background settings.
if (Source?.ToString().EndsWith(".html", StringComparison.OrdinalIgnoreCase) == true ||
Source?.ToString().EndsWith(".htm", StringComparison.OrdinalIgnoreCase) == true)
{
// Reset to default behavior for HTML files
PreviewBrowser.DefaultBackgroundColor = originalBackgroundColor;
}
DOMContentLoaded?.Invoke(sender, args); DOMContentLoaded?.Invoke(sender, args);
} }

View File

@ -2,6 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license. // The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.Collections.Concurrent;
using PowerToys.PowerAccentKeyboardService; using PowerToys.PowerAccentKeyboardService;
namespace PowerAccent.Core namespace PowerAccent.Core
@ -80,11 +81,15 @@ namespace PowerAccent.Core
}; };
} }
// Store the computed letters for each key, so that subsequent calls don't take as long.
private static ConcurrentDictionary<LetterKey, string[]> _allLanguagesCache = new ConcurrentDictionary<LetterKey, string[]>();
// All // All
private static string[] GetDefaultLetterKeyALL(LetterKey letter) private static string[] GetDefaultLetterKeyALL(LetterKey letter)
{ {
// would be even better to loop through Languages and call these functions dynamically, but I don't know how to do that! if (!_allLanguagesCache.ContainsKey(letter))
return GetDefaultLetterKeyCA(letter) {
_allLanguagesCache[letter] = GetDefaultLetterKeyCA(letter)
.Union(GetDefaultLetterKeyCUR(letter)) .Union(GetDefaultLetterKeyCUR(letter))
.Union(GetDefaultLetterKeyCY(letter)) .Union(GetDefaultLetterKeyCY(letter))
.Union(GetDefaultLetterKeyCZ(letter)) .Union(GetDefaultLetterKeyCZ(letter))
@ -116,6 +121,9 @@ namespace PowerAccent.Core
.ToArray(); .ToArray();
} }
return _allLanguagesCache[letter];
}
// Currencies (source: https://www.eurochange.co.uk/travel-money/world-currency-abbreviations-symbols-and-codes-travel-money) // Currencies (source: https://www.eurochange.co.uk/travel-money/world-currency-abbreviations-symbols-and-codes-travel-money)
private static string[] GetDefaultLetterKeyCUR(LetterKey letter) private static string[] GetDefaultLetterKeyCUR(LetterKey letter)
{ {

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.props')" /> <Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230822000\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230822000\build\native\Microsoft.WindowsAppSDK.props')" /> <Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230913002\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230913002\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.props')" /> <Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.props')" />
<PropertyGroup Label="Globals"> <PropertyGroup Label="Globals">
<CppWinRTOptimized>true</CppWinRTOptimized> <CppWinRTOptimized>true</CppWinRTOptimized>
@ -208,7 +208,7 @@
<Import Project="..\..\..\..\packages\boost.1.80.0\build\boost.targets" Condition="Exists('..\..\..\..\packages\boost.1.80.0\build\boost.targets')" /> <Import Project="..\..\..\..\packages\boost.1.80.0\build\boost.targets" Condition="Exists('..\..\..\..\packages\boost.1.80.0\build\boost.targets')" />
<Import Project="..\..\..\..\packages\boost_regex-vc143.1.80.0\build\boost_regex-vc143.targets" Condition="Exists('..\..\..\..\packages\boost_regex-vc143.1.80.0\build\boost_regex-vc143.targets')" /> <Import Project="..\..\..\..\packages\boost_regex-vc143.1.80.0\build\boost_regex-vc143.targets" Condition="Exists('..\..\..\..\packages\boost_regex-vc143.1.80.0\build\boost_regex-vc143.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.targets')" /> <Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230822000\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230822000\build\native\Microsoft.WindowsAppSDK.targets')" /> <Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230913002\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230913002\build\native\Microsoft.WindowsAppSDK.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" /> <Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.targets')" /> <Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup> </ImportGroup>
@ -220,8 +220,8 @@
<Error Condition="!Exists('..\..\..\..\packages\boost_regex-vc143.1.80.0\build\boost_regex-vc143.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\boost_regex-vc143.1.80.0\build\boost_regex-vc143.targets'))" /> <Error Condition="!Exists('..\..\..\..\packages\boost_regex-vc143.1.80.0\build\boost_regex-vc143.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\boost_regex-vc143.1.80.0\build\boost_regex-vc143.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.props'))" /> <Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.targets'))" /> <Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.22621.755\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230822000\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230822000\build\native\Microsoft.WindowsAppSDK.props'))" /> <Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230913002\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230913002\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230822000\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230822000\build\native\Microsoft.WindowsAppSDK.targets'))" /> <Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230913002\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.4.230913002\build\native\Microsoft.WindowsAppSDK.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" /> <Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.220914.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.props'))" /> <Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.targets'))" /> <Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.221104.6\build\native\Microsoft.Windows.CppWinRT.targets'))" />

View File

@ -5,5 +5,5 @@
<package id="Microsoft.Windows.CppWinRT" version="2.0.221104.6" targetFramework="native" /> <package id="Microsoft.Windows.CppWinRT" version="2.0.221104.6" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.220914.1" targetFramework="native" /> <package id="Microsoft.Windows.ImplementationLibrary" version="1.0.220914.1" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.755" targetFramework="native" /> <package id="Microsoft.Windows.SDK.BuildTools" version="10.0.22621.755" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.4.230822000" targetFramework="native" /> <package id="Microsoft.WindowsAppSDK" version="1.4.230913002" targetFramework="native" />
</packages> </packages>

View File

@ -8,7 +8,9 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.UI.Input; using Microsoft.UI.Input;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
@ -413,21 +415,75 @@ namespace RegistryPreview
value += registryLine; value += registryLine;
} }
// Clean out any escaped characters in the value, only for the preview
value = StripEscapedCharacters(value);
// update the ListViewItem with the loaded value, based off REG value type // update the ListViewItem with the loaded value, based off REG value type
switch (registryValue.Type) switch (registryValue.Type)
{ {
case "ERROR": case "ERROR":
// do nothing // do nothing
break; break;
case "REG_SZ":
if (value == "\"")
{
// Value is most likely missing an end quote
registryValue.Type = "ERROR";
value = resourceLoader.GetString("InvalidString");
}
else
{
for (int i = 1; i < value.Length; i++)
{
if (value[i - 1] == '\\')
{
// Only allow these escape characters
if (value[i] != '"' && value[i] != '\\')
{
registryValue.Type = "ERROR";
value = resourceLoader.GetString("InvalidString");
break;
}
i++;
}
if (value[i - 1] != '\\' && value[i] == '"')
{
// Don't allow non-escaped quotes
registryValue.Type = "ERROR";
value = resourceLoader.GetString("InvalidString");
break;
}
}
if (registryValue.Type != "ERROR")
{
// Clean out any escaped characters in the value, only for the preview
value = StripEscapedCharacters(value);
}
}
registryValue.Value = value;
break;
case "REG_BINARY": case "REG_BINARY":
case "REG_NONE": case "REG_NONE":
if (value.Length <= 0) if (value.Length <= 0)
{ {
value = resourceLoader.GetString("ZeroLength"); value = resourceLoader.GetString("ZeroLength");
} }
else
{
try
{
// Hexes are usually two characters (00), it's invalid if less or more than 2
var bytes = value.Split(',').Select(
c => c.Length == 2 ? byte.Parse(c, NumberStyles.HexNumber, CultureInfo.InvariantCulture) : throw null);
value = string.Join(' ', bytes.Select(b => b.ToString("x2", CultureInfo.CurrentCulture)));
}
catch
{
registryValue.Type = "ERROR";
value = resourceLoader.GetString("InvalidBinary");
}
}
registryValue.Value = value; registryValue.Value = value;
@ -436,6 +492,19 @@ namespace RegistryPreview
if (value.Length <= 0) if (value.Length <= 0)
{ {
registryValue.Type = "ERROR"; registryValue.Type = "ERROR";
value = resourceLoader.GetString("InvalidDword");
}
else
{
if (uint.TryParse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint dword))
{
value = $"0x{dword:x8} ({dword})";
}
else
{
registryValue.Type = "ERROR";
value = resourceLoader.GetString("InvalidDword");
}
} }
registryValue.Value = value; registryValue.Value = value;
@ -444,8 +513,55 @@ namespace RegistryPreview
case "REG_QWORD": case "REG_QWORD":
if (value.Length <= 0) if (value.Length <= 0)
{ {
registryValue.Type = "ERROR";
value = resourceLoader.GetString("InvalidQword"); value = resourceLoader.GetString("InvalidQword");
} }
else
{
try
{
// Hexes are usually two characters (00), it's invalid if less or more than 2
var bytes = value.Split(',').Select(
c => c.Length == 2 ? byte.Parse(c, NumberStyles.HexNumber, CultureInfo.InvariantCulture) : throw null).ToArray();
ulong qword = BitConverter.ToUInt64(bytes);
value = $"0x{qword:x8} ({qword})";
}
catch
{
registryValue.Type = "ERROR";
value = resourceLoader.GetString("InvalidQword");
}
}
registryValue.Value = value;
break;
case "REG_EXPAND_SZ":
case "REG_MULTI_SZ":
try
{
// Hexes are usually two characters (00), it's invalid if less or more than 2
var bytes = value.Split(',').Select(
c => c.Length == 2 ? byte.Parse(c, NumberStyles.HexNumber, CultureInfo.InvariantCulture) : throw null).ToArray();
if (registryValue.Type == "REG_MULTI_SZ")
{
// Replace zeros (00,00) with spaces
for (int i = 0; i < bytes.Length; i += 2)
{
if (bytes[i] == 0 && bytes[i + 1] == 0)
{
bytes[i] = 0x20;
}
}
}
value = Encoding.Unicode.GetString(bytes);
}
catch
{
registryValue.Type = "ERROR";
value = resourceLoader.GetString("InvalidString");
}
registryValue.Value = value; registryValue.Value = value;
break; break;
@ -1036,7 +1152,7 @@ namespace RegistryPreview
value = value.Remove(indexOf, value.Length - indexOf); value = value.Remove(indexOf, value.Length - indexOf);
} }
return value; return value.TrimEnd();
} }
/// <summary> /// <summary>

View File

@ -33,7 +33,7 @@ namespace RegistryPreview
switch (Type) switch (Type)
{ {
case "REG_SZ": case "REG_SZ":
case "REG_EXAND_SZ": case "REG_EXPAND_SZ":
case "REG_MULTI_SZ": case "REG_MULTI_SZ":
return uriStringValue; return uriStringValue;
case "ERROR": case "ERROR":

View File

@ -138,6 +138,12 @@
<data name="FilterRegistryName" xml:space="preserve"> <data name="FilterRegistryName" xml:space="preserve">
<value>Registry files (*.reg)</value> <value>Registry files (*.reg)</value>
</data> </data>
<data name="InvalidBinary" xml:space="preserve">
<value>(Invalid binary value)</value>
</data>
<data name="InvalidDword" xml:space="preserve">
<value>(Invalid DWORD (32-bit) value)</value>
</data>
<data name="InvalidQword" xml:space="preserve"> <data name="InvalidQword" xml:space="preserve">
<value>(Invalid QWORD (64-bit) value)</value> <value>(Invalid QWORD (64-bit) value)</value>
</data> </data>
@ -147,6 +153,9 @@
<data name="InvalidRegistryFileTitle" xml:space="preserve"> <data name="InvalidRegistryFileTitle" xml:space="preserve">
<value>File was not a Registry file</value> <value>File was not a Registry file</value>
</data> </data>
<data name="InvalidString" xml:space="preserve">
<value>(Invalid string value)</value>
</data>
<data name="LargeRegistryFile" xml:space="preserve"> <data name="LargeRegistryFile" xml:space="preserve">
<value> is larger than 10MB which is too large for this application.</value> <value> is larger than 10MB which is too large for this application.</value>
</data> </data>
@ -228,7 +237,7 @@
<value>User Account Control</value> <value>User Account Control</value>
</data> </data>
<data name="ValueColumn.Header" xml:space="preserve"> <data name="ValueColumn.Header" xml:space="preserve">
<value>Value</value> <value>Data</value>
</data> </data>
<data name="WriteButton.Label" xml:space="preserve"> <data name="WriteButton.Label" xml:space="preserve">
<value>Write to Registry</value> <value>Write to Registry</value>

View File

@ -3,17 +3,28 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Library namespace Microsoft.PowerToys.Settings.UI.Library
{ {
public class PluginAdditionalOption public class PluginAdditionalOption
{ {
public enum SelectionType public enum AdditionalOptionType
{ {
Checkbox = 0, Checkbox = 0,
Combobox = 1, Combobox = 1,
Textbox = 2,
Numberbox = 3,
CheckboxAndCombobox = 11,
CheckboxAndTextbox = 12,
CheckboxAndNumberbox = 13,
} }
/// <summary>
/// Gets or sets the layout type of the option in settings ui (Optional; Default is checkbox)
/// </summary>
public AdditionalOptionType PluginOptionType { get; set; }
public string Key { get; set; } public string Key { get; set; }
public string DisplayLabel { get; set; } public string DisplayLabel { get; set; }
@ -21,14 +32,64 @@ namespace Microsoft.PowerToys.Settings.UI.Library
/// <summary> /// <summary>
/// Gets or sets a value to show a description of this setting in the settings ui. (Optional) /// Gets or sets a value to show a description of this setting in the settings ui. (Optional)
/// </summary> /// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string DisplayDescription { get; set; } public string DisplayDescription { get; set; }
/// <summary>
/// Gets or sets a value to show a label for the second setting if two combined settings are shown
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string SecondDisplayLabel { get; set; }
/// <summary>
/// Gets or sets a value to show a description for the second setting in the settings ui if two combined settings are shown. (Optional)
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string SecondDisplayDescription { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the checkbox is set or not set
/// </summary>
public bool Value { get; set; } public bool Value { get; set; }
public int ComboBoxValue { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<string> ComboBoxOptions { get; set; } public List<string> ComboBoxOptions { get; set; }
public int Option { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string TextValue { get; set; }
public int SelectionTypeValue { get; set; } /// <summary>
/// Gets or sets the value that specifies the maximum number of characters allowed for user input in the text box. (Optional; Default is 0 which means no limit.)
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public int? TextBoxMaxLength { get; set; }
public double NumberValue { get; set; }
/// <summary>
/// Gets or sets a minimal value for the number box. (Optional; Default is Double.MinValue)
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public double? NumberBoxMin { get; set; }
/// <summary>
/// Gets or sets a maximal value for the number box. (Optional; Default is Double.MaxValue)
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public double? NumberBoxMax { get; set; }
/// <summary>
/// Gets or sets the value for small changes of the number box. (Optional; Default is 1)
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public double? NumberBoxSmallChange { get; set; }
/// <summary>
/// Gets or sets the value for large changes of the number box. (Optional; Default is 10)
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public double? NumberBoxLargeChange { get; set; }
} }
} }

View File

@ -33,6 +33,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
var options = new JsonSerializerOptions var options = new JsonSerializerOptions
{ {
WriteIndented = true, WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
}; };
ArgumentNullException.ThrowIfNull(settingsUtils); ArgumentNullException.ThrowIfNull(settingsUtils);

View File

@ -360,6 +360,7 @@
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate x:DataType="ViewModels:PluginAdditionalOptionViewModel"> <DataTemplate x:DataType="ViewModels:PluginAdditionalOptionViewModel">
<StackPanel HorizontalAlignment="Stretch" Orientation="Vertical"> <StackPanel HorizontalAlignment="Stretch" Orientation="Vertical">
<!-- Checkbox setting -->
<controls:SettingsCard <controls:SettingsCard
MinHeight="0" MinHeight="0"
Margin="1" Margin="1"
@ -367,23 +368,172 @@
Background="Transparent" Background="Transparent"
BorderThickness="0,0,0,0" BorderThickness="0,0,0,0"
ContentAlignment="Left" ContentAlignment="Left"
CornerRadius="0"> CornerRadius="0"
<StackPanel Orientation="Vertical"> Visibility="{x:Bind Path=ShowCheckBox, Converter={StaticResource BoolToVisibilityConverter}}">
<custom:CheckBoxWithDescriptionControl <custom:CheckBoxWithDescriptionControl
Margin="56,0,0,0" Margin="56,0,0,0"
Description="{x:Bind Path=DisplayDescription}" Description="{x:Bind Path=DisplayDescription}"
Header="{x:Bind Path=DisplayLabel}" Header="{x:Bind Path=DisplayLabel}"
IsChecked="{x:Bind Path=Value, Mode=TwoWay}" IsChecked="{x:Bind Path=Value, Mode=TwoWay}" />
Visibility="{x:Bind Path=ShowCheckBox, Converter={StaticResource BoolToVisibilityConverter}}" /> </controls:SettingsCard>
<!-- ComboBox setting -->
<controls:SettingsCard
MinHeight="0"
Margin="56,8,45,8"
Background="Transparent"
BorderThickness="0,0,0,0"
CornerRadius="0"
Description="{x:Bind Path=DisplayDescription}"
Header="{x:Bind Path=DisplayLabel}"
Visibility="{x:Bind Path=ShowComboBox, Converter={StaticResource BoolToVisibilityConverter}}">
<ComboBox <ComboBox
MinWidth="{StaticResource SettingActionControlMinWidth}"
ItemsSource="{x:Bind Path=ComboBoxOptions}"
SelectedIndex="{x:Bind Path=ComboBoxValue, Mode=TwoWay}" />
</controls:SettingsCard>
<!-- TextBox setting -->
<controls:SettingsCard
MinHeight="0"
Margin="56,8,45,8"
Background="Transparent"
BorderThickness="0,0,0,0"
CornerRadius="0"
Description="{x:Bind Path=DisplayDescription}"
Header="{x:Bind Path=DisplayLabel}"
Visibility="{x:Bind Path=ShowTextBox, Converter={StaticResource BoolToVisibilityConverter}}">
<TextBox
MinWidth="{StaticResource SettingActionControlMinWidth}"
MaxWidth="450"
MaxLength="{x:Bind Path=TextBoxMaxLength, Mode=OneWay}"
Text="{x:Bind Path=TextValue, Mode=TwoWay}" />
</controls:SettingsCard>
<!-- NumberBox setting -->
<controls:SettingsCard
MinHeight="0"
Margin="56,8,45,8"
Background="Transparent"
BorderThickness="0,0,0,0"
CornerRadius="0"
Description="{x:Bind Path=DisplayDescription}"
Header="{x:Bind Path=DisplayLabel}"
Visibility="{x:Bind Path=ShowNumberBox, Converter={StaticResource BoolToVisibilityConverter}}">
<NumberBox
MinWidth="{StaticResource SettingActionControlMinWidth}"
LargeChange="{x:Bind Path=NumberBoxLargeChange, Mode=OneWay}"
Maximum="{x:Bind Path=NumberBoxMax, Mode=OneWay}"
Minimum="{x:Bind Path=NumberBoxMin, Mode=OneWay}"
SmallChange="{x:Bind Path=NumberBoxSmallChange, Mode=OneWay}"
SpinButtonPlacementMode="Compact"
Value="{x:Bind Path=NumberValue, Mode=TwoWay}" />
</controls:SettingsCard>
<!-- Checkbox And ComboBox setting -->
<StackPanel
HorizontalAlignment="Stretch"
Orientation="Vertical"
Visibility="{x:Bind Path=ShowCheckboxAndCombobox, Converter={StaticResource BoolToVisibilityConverter}}">
<controls:SettingsCard
MinHeight="0"
Margin="1"
Padding="0,6,0,4"
Background="Transparent"
BorderThickness="0,0,0,0"
ContentAlignment="Left"
CornerRadius="0">
<custom:CheckBoxWithDescriptionControl
Margin="56,0,0,0" Margin="56,0,0,0"
Description="{x:Bind Path=DisplayDescription}" Description="{x:Bind Path=DisplayDescription}"
Header="{x:Bind Path=DisplayLabel}" Header="{x:Bind Path=DisplayLabel}"
ItemsSource="{x:Bind Path=ComboBoxOptions}" IsChecked="{x:Bind Path=Value, Mode=TwoWay}" />
SelectedIndex="{x:Bind Path=Option, Mode=TwoWay}"
Visibility="{x:Bind Path=ShowComboBox, Converter={StaticResource BoolToVisibilityConverter}}" />
</StackPanel>
</controls:SettingsCard> </controls:SettingsCard>
<controls:SettingsCard
MinHeight="0"
Margin="84,0,45,8"
Background="Transparent"
BorderThickness="0,0,0,0"
CornerRadius="0"
Description="{x:Bind Path=SecondDisplayDescription}"
Header="{x:Bind Path=SecondDisplayLabel}"
IsEnabled="{x:Bind Path=SecondSettingIsEnabled, Mode=OneWay}">
<ComboBox
MinWidth="{StaticResource SettingActionControlMinWidth}"
ItemsSource="{x:Bind Path=ComboBoxOptions}"
SelectedIndex="{x:Bind Path=ComboBoxValue, Mode=TwoWay}" />
</controls:SettingsCard>
</StackPanel>
<!-- Checkbox And TextBox setting -->
<StackPanel
HorizontalAlignment="Stretch"
Orientation="Vertical"
Visibility="{x:Bind Path=ShowCheckboxAndTextbox, Converter={StaticResource BoolToVisibilityConverter}}">
<controls:SettingsCard
MinHeight="0"
Margin="1"
Padding="0,6,0,4"
Background="Transparent"
BorderThickness="0,0,0,0"
ContentAlignment="Left"
CornerRadius="0">
<custom:CheckBoxWithDescriptionControl
Margin="56,0,0,0"
Description="{x:Bind Path=DisplayDescription}"
Header="{x:Bind Path=DisplayLabel}"
IsChecked="{x:Bind Path=Value, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard
MinHeight="0"
Margin="84,0,45,8"
Background="Transparent"
BorderThickness="0,0,0,0"
CornerRadius="0"
Description="{x:Bind Path=SecondDisplayDescription}"
Header="{x:Bind Path=SecondDisplayLabel}"
IsEnabled="{x:Bind Path=SecondSettingIsEnabled, Mode=OneWay}">
<TextBox
MinWidth="{StaticResource SettingActionControlMinWidth}"
MaxWidth="450"
MaxLength="{x:Bind Path=TextBoxMaxLength, Mode=OneWay}"
Text="{x:Bind Path=TextValue, Mode=TwoWay}" />
</controls:SettingsCard>
</StackPanel>
<!-- Checkbox And NumberBox setting -->
<StackPanel
HorizontalAlignment="Stretch"
Orientation="Vertical"
Visibility="{x:Bind Path=ShowCheckboxAndNumberbox, Converter={StaticResource BoolToVisibilityConverter}}">
<controls:SettingsCard
MinHeight="0"
Margin="1"
Padding="0,6,0,4"
Background="Transparent"
BorderThickness="0,0,0,0"
ContentAlignment="Left"
CornerRadius="0">
<custom:CheckBoxWithDescriptionControl
Margin="56,0,0,0"
Description="{x:Bind Path=DisplayDescription}"
Header="{x:Bind Path=DisplayLabel}"
IsChecked="{x:Bind Path=Value, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard
MinHeight="0"
Margin="84,0,45,8"
Background="Transparent"
BorderThickness="0,0,0,0"
CornerRadius="0"
Description="{x:Bind Path=SecondDisplayDescription}"
Header="{x:Bind Path=SecondDisplayLabel}"
IsEnabled="{x:Bind Path=SecondSettingIsEnabled, Mode=OneWay}">
<NumberBox
MinWidth="{StaticResource SettingActionControlMinWidth}"
LargeChange="{x:Bind Path=NumberBoxLargeChange, Mode=OneWay}"
Maximum="{x:Bind Path=NumberBoxMax, Mode=OneWay}"
Minimum="{x:Bind Path=NumberBoxMin, Mode=OneWay}"
SmallChange="{x:Bind Path=NumberBoxSmallChange, Mode=OneWay}"
SpinButtonPlacementMode="Compact"
Value="{x:Bind Path=NumberValue, Mode=TwoWay}" />
</controls:SettingsCard>
</StackPanel>
<!-- Separator line -->
<Rectangle <Rectangle
Height="1" Height="1"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"

View File

@ -18,10 +18,19 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
_additionalOption = additionalOption; _additionalOption = additionalOption;
} }
// Labels of single and first setting of combined types
public string DisplayLabel => _additionalOption.DisplayLabel; public string DisplayLabel => _additionalOption.DisplayLabel;
public string DisplayDescription => _additionalOption.DisplayDescription; public string DisplayDescription => _additionalOption.DisplayDescription;
// Labels of second setting of combined types
public string SecondDisplayLabel => _additionalOption.SecondDisplayLabel;
public string SecondDisplayDescription => _additionalOption.SecondDisplayDescription;
// Bool checkbox setting
public bool ShowCheckBox => _additionalOption.PluginOptionType == PluginAdditionalOption.AdditionalOptionType.Checkbox;
public bool Value public bool Value
{ {
get => _additionalOption.Value; get => _additionalOption.Value;
@ -31,29 +40,83 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{ {
_additionalOption.Value = value; _additionalOption.Value = value;
NotifyPropertyChanged(); NotifyPropertyChanged();
NotifyPropertyChanged(nameof(SecondSettingIsEnabled));
} }
} }
} }
// ComboBox setting
public bool ShowComboBox => _additionalOption.PluginOptionType == PluginAdditionalOption.AdditionalOptionType.Combobox &&
_additionalOption.ComboBoxOptions != null && _additionalOption.ComboBoxOptions.Count > 0;
public List<string> ComboBoxOptions => _additionalOption.ComboBoxOptions; public List<string> ComboBoxOptions => _additionalOption.ComboBoxOptions;
public int Option public int ComboBoxValue
{ {
get => _additionalOption.Option; get => _additionalOption.ComboBoxValue;
set set
{ {
if (value != _additionalOption.Option) if (value != _additionalOption.ComboBoxValue)
{ {
_additionalOption.Option = value; _additionalOption.ComboBoxValue = value;
NotifyPropertyChanged(); NotifyPropertyChanged();
} }
} }
} }
public bool ShowComboBox => _additionalOption.SelectionTypeValue == (int)PluginAdditionalOption.SelectionType.Combobox && _additionalOption.ComboBoxOptions != null && _additionalOption.ComboBoxOptions.Count > 0; // TextBox setting
public bool ShowTextBox => _additionalOption.PluginOptionType == PluginAdditionalOption.AdditionalOptionType.Textbox;
public bool ShowCheckBox => _additionalOption.SelectionTypeValue == (int)PluginAdditionalOption.SelectionType.Checkbox; public int TextBoxMaxLength => (_additionalOption.TextBoxMaxLength == null) ? 0 : _additionalOption.TextBoxMaxLength.Value; // 0 ist the default and means no limit.
public string TextValue
{
get => _additionalOption.TextValue;
set
{
if (value != _additionalOption.TextValue)
{
_additionalOption.TextValue = value;
NotifyPropertyChanged();
}
}
}
// NumberBox setting
public bool ShowNumberBox => _additionalOption.PluginOptionType == PluginAdditionalOption.AdditionalOptionType.Numberbox;
public double NumberBoxMin => (_additionalOption.NumberBoxMin == null) ? double.MinValue : _additionalOption.NumberBoxMin.Value;
public double NumberBoxMax => (_additionalOption.NumberBoxMax == null) ? double.MaxValue : _additionalOption.NumberBoxMax.Value;
public double NumberBoxSmallChange => (_additionalOption.NumberBoxSmallChange == null) ? 1 : _additionalOption.NumberBoxSmallChange.Value;
public double NumberBoxLargeChange => (_additionalOption.NumberBoxLargeChange == null) ? 10 : _additionalOption.NumberBoxLargeChange.Value;
public double NumberValue
{
get => _additionalOption.NumberValue;
set
{
if (value != _additionalOption.NumberValue)
{
_additionalOption.NumberValue = value;
NotifyPropertyChanged();
}
}
}
// Show combined settings cards
public bool ShowCheckboxAndCombobox => _additionalOption.PluginOptionType == PluginAdditionalOption.AdditionalOptionType.CheckboxAndCombobox;
public bool ShowCheckboxAndTextbox => _additionalOption.PluginOptionType == PluginAdditionalOption.AdditionalOptionType.CheckboxAndTextbox;
public bool ShowCheckboxAndNumberbox => _additionalOption.PluginOptionType == PluginAdditionalOption.AdditionalOptionType.CheckboxAndNumberbox;
// Enabled state of ComboBox, TextBox, NumberBox (If combined with checkbox then checkbox value decides it.)
public bool SecondSettingIsEnabled => (int)_additionalOption.PluginOptionType > 10 ? _additionalOption.Value : true;
// Handle property changes
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")