[FancyZones] Child windows support (#16507)

This commit is contained in:
Seraphima Zykova 2022-02-23 17:25:28 +03:00 committed by GitHub
parent d9c98bbc29
commit 8edfb8fe80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 1204 additions and 1477 deletions

View File

@ -2020,6 +2020,7 @@ SYSLIB
syslog syslog
SYSMENU SYSMENU
systemd systemd
SYSTEMAPPS
SYSTEMTIME SYSTEMTIME
systemverilog systemverilog
Tadele Tadele

View File

@ -3,7 +3,7 @@
#include <string> #include <string>
// Checks if a process path is included in a list of strings. // Checks if a process path is included in a list of strings.
bool find_app_name_in_path(const std::wstring& where, const std::vector<std::wstring>& what) inline bool find_app_name_in_path(const std::wstring& where, const std::vector<std::wstring>& what)
{ {
for (const auto& row : what) for (const auto& row : what)
{ {
@ -17,3 +17,16 @@ bool find_app_name_in_path(const std::wstring& where, const std::vector<std::wst
} }
return false; return false;
} }
inline bool find_folder_in_path(const std::wstring& where, const std::vector<std::wstring>& what)
{
for (const auto& row : what)
{
const auto pos = where.rfind(row);
if (pos != std::wstring::npos)
{
return true;
}
}
return false;
}

View File

@ -16,11 +16,9 @@
FancyZonesApp::FancyZonesApp(const std::wstring& appName, const std::wstring& appKey) FancyZonesApp::FancyZonesApp(const std::wstring& appName, const std::wstring& appKey)
{ {
DPIAware::EnableDPIAwarenessForThisProcess(); DPIAware::EnableDPIAwarenessForThisProcess();
m_settings = MakeFancyZonesSettings(reinterpret_cast<HINSTANCE>(&__ImageBase), appName.c_str(), appKey.c_str());
InitializeWinhookEventIds(); InitializeWinhookEventIds();
m_app = MakeFancyZones(reinterpret_cast<HINSTANCE>(&__ImageBase), m_settings, std::bind(&FancyZonesApp::DisableModule, this)); m_app = MakeFancyZones(reinterpret_cast<HINSTANCE>(&__ImageBase), std::bind(&FancyZonesApp::DisableModule, this));
InitHooks(); InitHooks();

View File

@ -19,7 +19,6 @@ private:
winrt::com_ptr<IFancyZones> m_app; winrt::com_ptr<IFancyZones> m_app;
HWINEVENTHOOK m_objectLocationWinEventHook = nullptr; HWINEVENTHOOK m_objectLocationWinEventHook = nullptr;
std::vector<HWINEVENTHOOK> m_staticWinEventHooks; std::vector<HWINEVENTHOOK> m_staticWinEventHooks;
winrt::com_ptr<IFancyZonesSettings> m_settings;
void DisableModule() noexcept; void DisableModule() noexcept;

View File

@ -0,0 +1,59 @@
#include "pch.h"
#include "Colors.h"
#include <winrt/Windows.UI.ViewManagement.h>
#include <FancyZonesLib/Settings.h>
#include <FancyZonesLib/util.h>
namespace Colors
{
COLORREF currentAccentColor;
COLORREF currentBackgroundColor;
bool GetSystemTheme() noexcept
{
winrt::Windows::UI::ViewManagement::UISettings settings;
auto accentValue = settings.GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::Accent);
auto accentColor = RGB(accentValue.R, accentValue.G, accentValue.B);
auto backgroundValue = settings.GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::Background);
auto backgroundColor = RGB(backgroundValue.R, backgroundValue.G, backgroundValue.B);
if (currentAccentColor != accentColor || currentBackgroundColor != backgroundColor)
{
currentAccentColor = accentColor;
currentBackgroundColor = backgroundColor;
return true;
}
return false;
}
ZoneColors GetZoneColors() noexcept
{
if (FancyZonesSettings::settings().systemTheme)
{
GetSystemTheme();
auto numberColor = currentBackgroundColor == RGB(0, 0, 0) ? RGB(255, 255, 255) : RGB(0, 0, 0);
return ZoneColors{
.primaryColor = currentBackgroundColor,
.borderColor = currentAccentColor,
.highlightColor = currentAccentColor,
.numberColor = numberColor,
.highlightOpacity = FancyZonesSettings::settings().zoneHighlightOpacity
};
}
else
{
return ZoneColors{
.primaryColor = FancyZonesUtils::HexToRGB(FancyZonesSettings::settings().zoneColor),
.borderColor = FancyZonesUtils::HexToRGB(FancyZonesSettings::settings().zoneBorderColor),
.highlightColor = FancyZonesUtils::HexToRGB(FancyZonesSettings::settings().zoneHighlightColor),
.numberColor = FancyZonesUtils::HexToRGB(FancyZonesSettings::settings().zoneNumberColor),
.highlightOpacity = FancyZonesSettings::settings().zoneHighlightOpacity
};
}
}
}

View File

@ -0,0 +1,17 @@
#pragma once
#include <windef.h>
namespace Colors
{
struct ZoneColors
{
COLORREF primaryColor;
COLORREF borderColor;
COLORREF highlightColor;
COLORREF numberColor;
int highlightOpacity;
};
ZoneColors GetZoneColors() noexcept;
}

View File

@ -21,9 +21,11 @@
#include <FancyZonesLib/FancyZonesWinHookEventIDs.h> #include <FancyZonesLib/FancyZonesWinHookEventIDs.h>
#include <FancyZonesLib/MonitorUtils.h> #include <FancyZonesLib/MonitorUtils.h>
#include <FancyZonesLib/Settings.h> #include <FancyZonesLib/Settings.h>
#include <FancyZonesLib/SettingsObserver.h>
#include <FancyZonesLib/ZoneSet.h> #include <FancyZonesLib/ZoneSet.h>
#include <FancyZonesLib/WorkArea.h> #include <FancyZonesLib/WorkArea.h>
#include <FancyZonesLib/WindowMoveHandler.h> #include <FancyZonesLib/WindowMoveHandler.h>
#include <FancyZonesLib/WindowUtils.h>
#include <FancyZonesLib/util.h> #include <FancyZonesLib/util.h>
#include "on_thread_executor.h" #include "on_thread_executor.h"
@ -33,7 +35,6 @@
#include "util.h" #include "util.h"
#include <FancyZonesLib/SecondaryMouseButtonsHook.h> #include <FancyZonesLib/SecondaryMouseButtonsHook.h>
#include <winrt/Windows.UI.ViewManagement.h>
enum class DisplayChangeType enum class DisplayChangeType
{ {
@ -50,18 +51,15 @@ namespace NonLocalizable
const wchar_t FZEditorExecutablePath[] = L"modules\\FancyZones\\PowerToys.FancyZonesEditor.exe"; const wchar_t FZEditorExecutablePath[] = L"modules\\FancyZones\\PowerToys.FancyZonesEditor.exe";
} }
struct FancyZones : public winrt::implements<FancyZones, IFancyZones, IFancyZonesCallback> struct FancyZones : public winrt::implements<FancyZones, IFancyZones, IFancyZonesCallback>, public SettingsObserver
{ {
public: public:
FancyZones(HINSTANCE hinstance, const winrt::com_ptr<IFancyZonesSettings>& settings, std::function<void()> disableModuleCallback) noexcept : FancyZones(HINSTANCE hinstance, std::function<void()> disableModuleCallback) noexcept :
SettingsObserver({ SettingId::EditorHotkey, SettingId::PrevTabHotkey, SettingId::NextTabHotkey, SettingId::SpanZonesAcrossMonitors }),
m_hinstance(hinstance), m_hinstance(hinstance),
m_settings(settings), m_windowMoveHandler([this]() {
m_windowMoveHandler(settings, [this]() {
PostMessageW(m_window, WM_PRIV_LOCATIONCHANGE, NULL, NULL); PostMessageW(m_window, WM_PRIV_LOCATIONCHANGE, NULL, NULL);
}), }),
m_settingsFileWatcher(FancyZonesDataInstance().GetSettingsFileName(), [this]() {
PostMessageW(m_window, WM_PRIV_SETTINGS_CHANGED, NULL, NULL);
}),
m_virtualDesktop([this]() { m_virtualDesktop([this]() {
PostMessage(m_window, WM_PRIV_VD_INIT, 0, 0); PostMessage(m_window, WM_PRIV_VD_INIT, 0, 0);
}, },
@ -71,6 +69,8 @@ public:
{ {
this->disableModuleCallback = std::move(disableModuleCallback); this->disableModuleCallback = std::move(disableModuleCallback);
FancyZonesSettings::instance().LoadSettings();
FancyZonesDataInstance().ReplaceZoneSettingsFileFromOlderVersions(); FancyZonesDataInstance().ReplaceZoneSettingsFileFromOlderVersions();
LayoutTemplates::instance().LoadData(); LayoutTemplates::instance().LoadData();
CustomLayouts::instance().LoadData(); CustomLayouts::instance().LoadData();
@ -87,23 +87,17 @@ public:
void MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen) noexcept void MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen) noexcept
{ {
if (m_settings->GetSettings()->spanZonesAcrossMonitors) if (FancyZonesSettings::settings().spanZonesAcrossMonitors)
{ {
monitor = NULL; monitor = NULL;
} }
// If accent color or theme is changed need to update colors for zones
if (m_settings->GetSettings()->systemTheme && GetSystemTheme())
{
m_workAreaHandler.UpdateZoneColors(GetZoneColors());
}
m_windowMoveHandler.MoveSizeStart(window, monitor, ptScreen, m_workAreaHandler.GetWorkAreasByDesktopId(m_currentDesktopId)); m_windowMoveHandler.MoveSizeStart(window, monitor, ptScreen, m_workAreaHandler.GetWorkAreasByDesktopId(m_currentDesktopId));
} }
void MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen) noexcept void MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen) noexcept
{ {
if (m_settings->GetSettings()->spanZonesAcrossMonitors) if (FancyZonesSettings::settings().spanZonesAcrossMonitors)
{ {
monitor = NULL; monitor = NULL;
} }
@ -174,8 +168,7 @@ private:
void RegisterVirtualDesktopUpdates() noexcept; void RegisterVirtualDesktopUpdates() noexcept;
void UpdateHotkey(int hotkeyId, const PowerToysSettings::HotkeyObject& hotkeyObject, bool enable) noexcept; void UpdateHotkey(int hotkeyId, const PowerToysSettings::HotkeyObject& hotkeyObject, bool enable) noexcept;
void OnSettingsChanged() noexcept;
std::pair<winrt::com_ptr<IWorkArea>, ZoneIndexSet> GetAppZoneHistoryInfo(HWND window, HMONITOR monitor, const std::unordered_map<HMONITOR, winrt::com_ptr<IWorkArea>>& workAreaMap) noexcept; std::pair<winrt::com_ptr<IWorkArea>, ZoneIndexSet> GetAppZoneHistoryInfo(HWND window, HMONITOR monitor, const std::unordered_map<HMONITOR, winrt::com_ptr<IWorkArea>>& workAreaMap) noexcept;
void MoveWindowIntoZone(HWND window, winrt::com_ptr<IWorkArea> workArea, const ZoneIndexSet& zoneIndexSet) noexcept; void MoveWindowIntoZone(HWND window, winrt::com_ptr<IWorkArea> workArea, const ZoneIndexSet& zoneIndexSet) noexcept;
bool MoveToAppLastZone(HWND window, HMONITOR active, HMONITOR primary) noexcept; bool MoveToAppLastZone(HWND window, HMONITOR active, HMONITOR primary) noexcept;
@ -190,8 +183,7 @@ private:
std::vector<HMONITOR> GetMonitorsSorted() noexcept; std::vector<HMONITOR> GetMonitorsSorted() noexcept;
HMONITOR WorkAreaKeyFromWindow(HWND window) noexcept; HMONITOR WorkAreaKeyFromWindow(HWND window) noexcept;
bool GetSystemTheme() const noexcept; virtual void SettingsUpdate(SettingId type) override;
ZoneColors GetZoneColors() const noexcept;
const HINSTANCE m_hinstance{}; const HINSTANCE m_hinstance{};
@ -200,9 +192,6 @@ private:
MonitorWorkAreaHandler m_workAreaHandler; MonitorWorkAreaHandler m_workAreaHandler;
VirtualDesktop m_virtualDesktop; VirtualDesktop m_virtualDesktop;
FileWatcher m_settingsFileWatcher;
winrt::com_ptr<IFancyZonesSettings> m_settings{};
GUID m_previousDesktopId{}; // UUID of previously active virtual desktop. GUID m_previousDesktopId{}; // UUID of previously active virtual desktop.
GUID m_currentDesktopId{}; // UUID of the current virtual desktop. GUID m_currentDesktopId{}; // UUID of the current virtual desktop.
wil::unique_handle m_terminateEditorEvent; // Handle of FancyZonesEditor.exe we launch and wait on wil::unique_handle m_terminateEditorEvent; // Handle of FancyZonesEditor.exe we launch and wait on
@ -231,8 +220,6 @@ private:
}; };
std::function<void()> FancyZones::disableModuleCallback = {}; std::function<void()> FancyZones::disableModuleCallback = {};
COLORREF currentAccentColor;
COLORREF currentBackgroundColor;
// IFancyZones // IFancyZones
IFACEMETHODIMP_(void) IFACEMETHODIMP_(void)
@ -253,11 +240,22 @@ FancyZones::Run() noexcept
return; return;
} }
RegisterHotKey(m_window, static_cast<int>(HotkeyId::Editor), m_settings->GetSettings()->editorHotkey.get_modifiers(), m_settings->GetSettings()->editorHotkey.get_code()); if (!RegisterHotKey(m_window, static_cast<int>(HotkeyId::Editor), FancyZonesSettings::settings().editorHotkey.get_modifiers(), FancyZonesSettings::settings().editorHotkey.get_code()))
if (m_settings->GetSettings()->windowSwitching)
{ {
RegisterHotKey(m_window, static_cast<int>(HotkeyId::NextTab), m_settings->GetSettings()->nextTabHotkey.get_modifiers(), m_settings->GetSettings()->nextTabHotkey.get_code()); Logger::error(L"Failed to register hotkey: {}", get_last_error_or_default(GetLastError()));
RegisterHotKey(m_window, static_cast<int>(HotkeyId::PrevTab), m_settings->GetSettings()->prevTabHotkey.get_modifiers(), m_settings->GetSettings()->prevTabHotkey.get_code()); }
if (FancyZonesSettings::settings().windowSwitching)
{
if (!RegisterHotKey(m_window, static_cast<int>(HotkeyId::NextTab), FancyZonesSettings::settings().nextTabHotkey.get_modifiers(), FancyZonesSettings::settings().nextTabHotkey.get_code()))
{
Logger::error(L"Failed to register hotkey: {}", get_last_error_or_default(GetLastError()));
}
if (!RegisterHotKey(m_window, static_cast<int>(HotkeyId::PrevTab), FancyZonesSettings::settings().prevTabHotkey.get_modifiers(), FancyZonesSettings::settings().prevTabHotkey.get_code()))
{
Logger::error(L"Failed to register hotkey: {}", get_last_error_or_default(GetLastError()));
}
} }
m_virtualDesktop.Init(); m_virtualDesktop.Init();
@ -389,8 +387,8 @@ bool FancyZones::MoveToAppLastZone(HWND window, HMONITOR active, HMONITOR primar
void FancyZones::WindowCreated(HWND window) noexcept void FancyZones::WindowCreated(HWND window) noexcept
{ {
const bool moveToAppLastZone = m_settings->GetSettings()->appLastZone_moveWindows; const bool moveToAppLastZone = FancyZonesSettings::settings().appLastZone_moveWindows;
const bool openOnActiveMonitor = m_settings->GetSettings()->openWindowOnActiveMonitor; const bool openOnActiveMonitor = FancyZonesSettings::settings().openWindowOnActiveMonitor;
if (!moveToAppLastZone && !openOnActiveMonitor) if (!moveToAppLastZone && !openOnActiveMonitor)
{ {
// Nothing to do here then. // Nothing to do here then.
@ -407,7 +405,7 @@ void FancyZones::WindowCreated(HWND window) noexcept
// Avoid processing splash screens, already stamped (zoned) windows, or those windows // Avoid processing splash screens, already stamped (zoned) windows, or those windows
// that belong to excluded applications list. // that belong to excluded applications list.
const bool isSplashScreen = FancyZonesUtils::IsSplashScreen(window); const bool isSplashScreen = FancyZonesWindowUtils::IsSplashScreen(window);
if (isSplashScreen) if (isSplashScreen)
{ {
return; return;
@ -425,7 +423,7 @@ void FancyZones::WindowCreated(HWND window) noexcept
return; return;
} }
const bool isCandidateForLastKnownZone = FancyZonesUtils::IsCandidateForZoning(window, m_settings->GetSettings()->excludedAppsArray); const bool isCandidateForLastKnownZone = FancyZonesWindowUtils::IsCandidateForZoning(window);
if (!isCandidateForLastKnownZone) if (!isCandidateForLastKnownZone)
{ {
return; return;
@ -477,7 +475,7 @@ FancyZones::OnKeyDown(PKBDLLHOOKSTRUCT info) noexcept
} }
} }
if (m_settings->GetSettings()->quickLayoutSwitch) if (FancyZonesSettings::settings().quickLayoutSwitch)
{ {
int digitPressed = -1; int digitPressed = -1;
if ('0' <= info->vkCode && info->vkCode <= '9') if ('0' <= info->vkCode && info->vkCode <= '9')
@ -526,7 +524,7 @@ void FancyZones::ToggleEditor() noexcept
HMONITOR targetMonitor{}; HMONITOR targetMonitor{};
const bool use_cursorpos_editor_startupscreen = m_settings->GetSettings()->use_cursorpos_editor_startupscreen; const bool use_cursorpos_editor_startupscreen = FancyZonesSettings::settings().use_cursorpos_editor_startupscreen;
if (use_cursorpos_editor_startupscreen) if (use_cursorpos_editor_startupscreen)
{ {
POINT currentCursorPos{}; POINT currentCursorPos{};
@ -569,7 +567,7 @@ void FancyZones::ToggleEditor() noexcept
std::wstring params; std::wstring params;
const std::wstring divider = L"/"; const std::wstring divider = L"/";
params += std::to_wstring(GetCurrentProcessId()) + divider; /* Process id */ params += std::to_wstring(GetCurrentProcessId()) + divider; /* Process id */
const bool spanZonesAcrossMonitors = m_settings->GetSettings()->spanZonesAcrossMonitors; const bool spanZonesAcrossMonitors = FancyZonesSettings::settings().spanZonesAcrossMonitors;
params += std::to_wstring(spanZonesAcrossMonitors) + divider; /* Span zones */ params += std::to_wstring(spanZonesAcrossMonitors) + divider; /* Span zones */
std::vector<std::pair<HMONITOR, MONITORINFOEX>> allMonitors; std::vector<std::pair<HMONITOR, MONITORINFOEX>> allMonitors;
@ -794,7 +792,7 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa
} }
else if (message == WM_PRIV_SETTINGS_CHANGED) else if (message == WM_PRIV_SETTINGS_CHANGED)
{ {
OnSettingsChanged(); FancyZonesSettings::instance().LoadSettings();
} }
else else
{ {
@ -845,7 +843,7 @@ void FancyZones::OnDisplayChange(DisplayChangeType changeType) noexcept
if ((changeType == DisplayChangeType::WorkArea) || (changeType == DisplayChangeType::DisplayChange)) if ((changeType == DisplayChangeType::WorkArea) || (changeType == DisplayChangeType::DisplayChange))
{ {
if (m_settings->GetSettings()->displayChange_moveWindows) if (FancyZonesSettings::settings().displayChange_moveWindows)
{ {
UpdateWindowsPositions(); UpdateWindowsPositions();
} }
@ -895,7 +893,7 @@ void FancyZones::AddWorkArea(HMONITOR monitor, const std::wstring& deviceId) noe
parentId = parentArea->UniqueId(); parentId = parentArea->UniqueId();
} }
auto workArea = MakeWorkArea(m_hinstance, monitor, uniqueId, parentId, GetZoneColors(), m_settings->GetSettings()->overlappingZonesAlgorithm, m_settings->GetSettings()->showZoneNumber); auto workArea = MakeWorkArea(m_hinstance, monitor, uniqueId, parentId);
if (workArea) if (workArea)
{ {
m_workAreaHandler.AddWorkArea(m_currentDesktopId, monitor, workArea); m_workAreaHandler.AddWorkArea(m_currentDesktopId, monitor, workArea);
@ -920,7 +918,7 @@ LRESULT CALLBACK FancyZones::s_WndProc(HWND window, UINT message, WPARAM wparam,
void FancyZones::UpdateWorkAreas() noexcept void FancyZones::UpdateWorkAreas() noexcept
{ {
if (m_settings->GetSettings()->spanZonesAcrossMonitors) if (FancyZonesSettings::settings().spanZonesAcrossMonitors)
{ {
AddWorkArea(nullptr, {}); AddWorkArea(nullptr, {});
} }
@ -984,7 +982,7 @@ bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexce
HMONITOR current = WorkAreaKeyFromWindow(window); HMONITOR current = WorkAreaKeyFromWindow(window);
std::vector<HMONITOR> monitorInfo = GetMonitorsSorted(); std::vector<HMONITOR> monitorInfo = GetMonitorsSorted();
if (current && monitorInfo.size() > 1 && m_settings->GetSettings()->moveWindowAcrossMonitors) if (current && monitorInfo.size() > 1 && FancyZonesSettings::settings().moveWindowAcrossMonitors)
{ {
// Multi monitor environment. // Multi monitor environment.
auto currMonitorInfo = std::find(std::begin(monitorInfo), std::end(monitorInfo), current); auto currMonitorInfo = std::find(std::begin(monitorInfo), std::end(monitorInfo), current);
@ -1019,13 +1017,13 @@ bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexce
{ {
auto workArea = m_workAreaHandler.GetWorkArea(m_currentDesktopId, current); auto workArea = m_workAreaHandler.GetWorkArea(m_currentDesktopId, current);
// Single monitor environment, or combined multi-monitor environment. // Single monitor environment, or combined multi-monitor environment.
if (m_settings->GetSettings()->restoreSize) if (FancyZonesSettings::settings().restoreSize)
{ {
bool moved = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */, workArea); bool moved = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */, workArea);
if (!moved) if (!moved)
{ {
FancyZonesUtils::RestoreWindowOrigin(window); FancyZonesWindowUtils::RestoreWindowOrigin(window);
FancyZonesUtils::RestoreWindowSize(window); FancyZonesWindowUtils::RestoreWindowSize(window);
} }
else else
{ {
@ -1053,7 +1051,7 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept
auto allMonitors = FancyZonesUtils::GetAllMonitorRects<&MONITORINFOEX::rcWork>(); auto allMonitors = FancyZonesUtils::GetAllMonitorRects<&MONITORINFOEX::rcWork>();
if (current && allMonitors.size() > 1 && m_settings->GetSettings()->moveWindowAcrossMonitors) if (current && allMonitors.size() > 1 && FancyZonesSettings::settings().moveWindowAcrossMonitors)
{ {
// Multi monitor environment. // Multi monitor environment.
// First, try to stay on the same monitor // First, try to stay on the same monitor
@ -1179,7 +1177,7 @@ bool FancyZones::OnSnapHotkey(DWORD vkCode) noexcept
{ {
// We already checked in ShouldProcessSnapHotkey whether the foreground window is a candidate for zoning // We already checked in ShouldProcessSnapHotkey whether the foreground window is a candidate for zoning
auto window = GetForegroundWindow(); auto window = GetForegroundWindow();
if (m_settings->GetSettings()->moveWindowsBasedOnPosition) if (FancyZonesSettings::settings().moveWindowsBasedOnPosition)
{ {
return OnSnapHotkeyBasedOnPosition(window, vkCode); return OnSnapHotkeyBasedOnPosition(window, vkCode);
} }
@ -1231,6 +1229,11 @@ void FancyZones::RegisterVirtualDesktopUpdates() noexcept
void FancyZones::UpdateHotkey(int hotkeyId, const PowerToysSettings::HotkeyObject& hotkeyObject, bool enable) noexcept void FancyZones::UpdateHotkey(int hotkeyId, const PowerToysSettings::HotkeyObject& hotkeyObject, bool enable) noexcept
{ {
if (!m_window)
{
return;
}
UnregisterHotKey(m_window, hotkeyId); UnregisterHotKey(m_window, hotkeyId);
if (!enable) if (!enable)
@ -1248,28 +1251,34 @@ void FancyZones::UpdateHotkey(int hotkeyId, const PowerToysSettings::HotkeyObjec
} }
} }
void FancyZones::OnSettingsChanged() noexcept void FancyZones::SettingsUpdate(SettingId id)
{ {
_TRACER_; switch (id)
m_settings->ReloadSettings(); {
case SettingId::EditorHotkey:
// Update the hotkeys {
UpdateHotkey(static_cast<int>(HotkeyId::Editor), m_settings->GetSettings()->editorHotkey, true); UpdateHotkey(static_cast<int>(HotkeyId::Editor), FancyZonesSettings::settings().editorHotkey, true);
}
auto windowSwitching = m_settings->GetSettings()->windowSwitching; break;
UpdateHotkey(static_cast<int>(HotkeyId::NextTab), m_settings->GetSettings()->nextTabHotkey, windowSwitching); case SettingId::PrevTabHotkey:
UpdateHotkey(static_cast<int>(HotkeyId::PrevTab), m_settings->GetSettings()->prevTabHotkey, windowSwitching); {
UpdateHotkey(static_cast<int>(HotkeyId::PrevTab), FancyZonesSettings::settings().prevTabHotkey, FancyZonesSettings::settings().windowSwitching);
// Needed if we toggled spanZonesAcrossMonitors }
m_workAreaHandler.Clear(); break;
case SettingId::NextTabHotkey:
// update zone colors {
m_workAreaHandler.UpdateZoneColors(GetZoneColors()); UpdateHotkey(static_cast<int>(HotkeyId::NextTab), FancyZonesSettings::settings().nextTabHotkey, FancyZonesSettings::settings().windowSwitching);
}
// update overlapping algorithm break;
m_workAreaHandler.UpdateOverlappingAlgorithm(m_settings->GetSettings()->overlappingZonesAlgorithm); case SettingId::SpanZonesAcrossMonitors:
{
PostMessageW(m_window, WM_PRIV_VD_INIT, NULL, NULL); m_workAreaHandler.Clear();
PostMessageW(m_window, WM_PRIV_VD_INIT, NULL, NULL);
}
break;
default:
break;
}
} }
void FancyZones::OnEditorExitEvent() noexcept void FancyZones::OnEditorExitEvent() noexcept
@ -1287,14 +1296,14 @@ void FancyZones::UpdateZoneSets() noexcept
workArea->UpdateActiveZoneSet(); workArea->UpdateActiveZoneSet();
} }
auto moveWindows = m_settings->GetSettings()->zoneSetChange_moveWindows; auto moveWindows = FancyZonesSettings::settings().zoneSetChange_moveWindows;
UpdateWindowsPositions(!moveWindows); UpdateWindowsPositions(!moveWindows);
} }
bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept
{ {
auto window = GetForegroundWindow(); auto window = GetForegroundWindow();
if (m_settings->GetSettings()->overrideSnapHotkeys && FancyZonesUtils::IsCandidateForZoning(window, m_settings->GetSettings()->excludedAppsArray)) if (FancyZonesSettings::settings().overrideSnapHotkeys && FancyZonesWindowUtils::IsCandidateForZoning(window))
{ {
HMONITOR monitor = WorkAreaKeyFromWindow(window); HMONITOR monitor = WorkAreaKeyFromWindow(window);
@ -1303,7 +1312,7 @@ bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept
{ {
if (vkCode == VK_UP || vkCode == VK_DOWN) if (vkCode == VK_UP || vkCode == VK_DOWN)
{ {
return m_settings->GetSettings()->moveWindowsBasedOnPosition; return FancyZonesSettings::settings().moveWindowsBasedOnPosition;
} }
else else
{ {
@ -1346,7 +1355,7 @@ void FancyZones::ApplyQuickLayout(int key) noexcept
void FancyZones::FlashZones() noexcept void FancyZones::FlashZones() noexcept
{ {
if (m_settings->GetSettings()->flashZonesOnQuickSwitch && !m_windowMoveHandler.IsDragEnabled()) if (FancyZonesSettings::settings().flashZonesOnQuickSwitch && !m_windowMoveHandler.IsDragEnabled())
{ {
for (auto [monitor, workArea] : m_workAreaHandler.GetWorkAreasByDesktopId(m_currentDesktopId)) for (auto [monitor, workArea] : m_workAreaHandler.GetWorkAreasByDesktopId(m_currentDesktopId))
{ {
@ -1385,7 +1394,7 @@ std::vector<std::pair<HMONITOR, RECT>> FancyZones::GetRawMonitorData() noexcept
HMONITOR FancyZones::WorkAreaKeyFromWindow(HWND window) noexcept HMONITOR FancyZones::WorkAreaKeyFromWindow(HWND window) noexcept
{ {
if (m_settings->GetSettings()->spanZonesAcrossMonitors) if (FancyZonesSettings::settings().spanZonesAcrossMonitors)
{ {
return NULL; return NULL;
} }
@ -1395,61 +1404,7 @@ HMONITOR FancyZones::WorkAreaKeyFromWindow(HWND window) noexcept
} }
} }
bool FancyZones::GetSystemTheme() const noexcept winrt::com_ptr<IFancyZones> MakeFancyZones(HINSTANCE hinstance, std::function<void()> disableCallback) noexcept
{ {
winrt::Windows::UI::ViewManagement::UISettings settings; return winrt::make_self<FancyZones>(hinstance, disableCallback);
auto accentValue = settings.GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::Accent);
auto accentColor = RGB(accentValue.R, accentValue.G, accentValue.B);
auto backgroundValue = settings.GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::Background);
auto backgroundColor = RGB(backgroundValue.R, backgroundValue.G, backgroundValue.B);
if (currentAccentColor != accentColor || currentBackgroundColor != backgroundColor)
{
currentAccentColor = accentColor;
currentBackgroundColor = backgroundColor;
return true;
}
return false;
}
ZoneColors FancyZones::GetZoneColors() const noexcept
{
if (m_settings->GetSettings()->systemTheme)
{
GetSystemTheme();
auto numberColor = currentBackgroundColor == RGB(0, 0, 0) ? RGB(255, 255, 255) : RGB(0, 0, 0);
return ZoneColors{
.primaryColor = currentBackgroundColor,
.borderColor = currentAccentColor,
.highlightColor = currentAccentColor,
.numberColor = numberColor,
.highlightOpacity = m_settings->GetSettings()->zoneHighlightOpacity
};
}
else
{
return ZoneColors{
.primaryColor = FancyZonesUtils::HexToRGB(m_settings->GetSettings()->zoneColor),
.borderColor = FancyZonesUtils::HexToRGB(m_settings->GetSettings()->zoneBorderColor),
.highlightColor = FancyZonesUtils::HexToRGB(m_settings->GetSettings()->zoneHighlightColor),
.numberColor = FancyZonesUtils::HexToRGB(m_settings->GetSettings()->zoneNumberColor),
.highlightOpacity = m_settings->GetSettings()->zoneHighlightOpacity
};
}
}
winrt::com_ptr<IFancyZones> MakeFancyZones(HINSTANCE hinstance,
const winrt::com_ptr<IFancyZonesSettings>& settings,
std::function<void()> disableCallback) noexcept
{
if (!settings)
{
return nullptr;
}
return winrt::make_self<FancyZones>(hinstance, settings, disableCallback);
} }

View File

@ -52,4 +52,4 @@ interface __declspec(uuid("{2CB37E8F-87E6-4AEC-B4B2-E0FDC873343F}")) IFancyZones
(PKBDLLHOOKSTRUCT info) = 0; (PKBDLLHOOKSTRUCT info) = 0;
}; };
winrt::com_ptr<IFancyZones> MakeFancyZones(HINSTANCE hinstance, const winrt::com_ptr<IFancyZonesSettings>& settings, std::function<void()> disableCallback) noexcept; winrt::com_ptr<IFancyZones> MakeFancyZones(HINSTANCE hinstance, std::function<void()> disableCallback) noexcept;

View File

@ -66,19 +66,23 @@
<None Include="resource.base.h" /> <None Include="resource.base.h" />
<ClInclude Include="SecondaryMouseButtonsHook.h" /> <ClInclude Include="SecondaryMouseButtonsHook.h" />
<ClInclude Include="Settings.h" /> <ClInclude Include="Settings.h" />
<ClInclude Include="SettingsConstants.h" />
<ClInclude Include="SettingsObserver.h" />
<ClInclude Include="trace.h" /> <ClInclude Include="trace.h" />
<ClInclude Include="util.h" /> <ClInclude Include="util.h" />
<ClInclude Include="VirtualDesktop.h" /> <ClInclude Include="VirtualDesktop.h" />
<ClInclude Include="WindowMoveHandler.h" /> <ClInclude Include="WindowMoveHandler.h" />
<ClInclude Include="FancyZonesWindowProperties.h" /> <ClInclude Include="FancyZonesWindowProperties.h" />
<ClInclude Include="WindowUtils.h" />
<ClInclude Include="Zone.h" /> <ClInclude Include="Zone.h" />
<ClInclude Include="ZoneColors.h" /> <ClInclude Include="Colors.h" />
<ClInclude Include="ZoneIndexSetBitmask.h" /> <ClInclude Include="ZoneIndexSetBitmask.h" />
<ClInclude Include="ZoneSet.h" /> <ClInclude Include="ZoneSet.h" />
<ClInclude Include="WorkArea.h" /> <ClInclude Include="WorkArea.h" />
<ClInclude Include="ZonesOverlay.h" /> <ClInclude Include="ZonesOverlay.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="Colors.cpp" />
<ClCompile Include="FancyZonesData\AppZoneHistory.cpp"> <ClCompile Include="FancyZonesData\AppZoneHistory.cpp">
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">../pch.h</PrecompiledHeaderFile> <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">../pch.h</PrecompiledHeaderFile>
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">../pch.h</PrecompiledHeaderFile> <PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">../pch.h</PrecompiledHeaderFile>
@ -117,6 +121,7 @@
<ClCompile Include="util.cpp" /> <ClCompile Include="util.cpp" />
<ClCompile Include="VirtualDesktop.cpp" /> <ClCompile Include="VirtualDesktop.cpp" />
<ClCompile Include="WindowMoveHandler.cpp" /> <ClCompile Include="WindowMoveHandler.cpp" />
<ClCompile Include="WindowUtils.cpp" />
<ClCompile Include="Zone.cpp" /> <ClCompile Include="Zone.cpp" />
<ClCompile Include="ZoneSet.cpp" /> <ClCompile Include="ZoneSet.cpp" />
<ClCompile Include="WorkArea.cpp" /> <ClCompile Include="WorkArea.cpp" />

View File

@ -81,7 +81,7 @@
<ClInclude Include="MonitorUtils.h"> <ClInclude Include="MonitorUtils.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="ZoneColors.h"> <ClInclude Include="Colors.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="FancyZonesWindowProperties.h"> <ClInclude Include="FancyZonesWindowProperties.h">
@ -117,6 +117,15 @@
<ClInclude Include="ZoneIndexSetBitmask.h"> <ClInclude Include="ZoneIndexSetBitmask.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="SettingsObserver.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="SettingsConstants.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="WindowUtils.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="pch.cpp"> <ClCompile Include="pch.cpp">
@ -194,6 +203,12 @@
<ClCompile Include="FancyZonesWindowProperties.cpp"> <ClCompile Include="FancyZonesWindowProperties.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="Colors.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="WindowUtils.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="packages.config" /> <None Include="packages.config" />

View File

@ -1,7 +1,7 @@
#include "pch.h" #include "pch.h"
#include "MonitorUtils.h" #include "MonitorUtils.h"
#include <FancyZonesLib/util.h> #include <FancyZonesLib/WindowUtils.h>
namespace MonitorUtils namespace MonitorUtils
{ {
@ -68,7 +68,7 @@ namespace MonitorUtils
if (GetMonitorInfo(monitor, &destMi)) if (GetMonitorInfo(monitor, &destMi))
{ {
RECT newPosition = FitOnScreen(placement.rcNormalPosition, originMi.rcWork, destMi.rcWork); RECT newPosition = FitOnScreen(placement.rcNormalPosition, originMi.rcWork, destMi.rcWork);
FancyZonesUtils::SizeWindowToRect(window, newPosition); FancyZonesWindowUtils::SizeWindowToRect(window, newPosition);
} }
} }
} }

View File

@ -129,25 +129,3 @@ void MonitorWorkAreaHandler::Clear()
{ {
workAreaMap.clear(); workAreaMap.clear();
} }
void MonitorWorkAreaHandler::UpdateZoneColors(const ZoneColors& colors)
{
for (const auto& workArea : workAreaMap)
{
for (const auto& workAreaPtr : workArea.second)
{
workAreaPtr.second->SetZoneColors(colors);
}
}
}
void MonitorWorkAreaHandler::UpdateOverlappingAlgorithm(OverlappingZonesAlgorithm overlappingAlgorithm)
{
for (const auto& workArea : workAreaMap)
{
for (const auto& workAreaPtr : workArea.second)
{
workAreaPtr.second->SetOverlappingZonesAlgorithm(overlappingAlgorithm);
}
}
}

View File

@ -86,16 +86,6 @@ public:
* Clear all persisted work area related data. * Clear all persisted work area related data.
*/ */
void Clear(); void Clear();
/**
* Update zone colors after settings changed
*/
void UpdateZoneColors(const ZoneColors& colors);
/**
* Update overlapping algorithm after settings changed
*/
void UpdateOverlappingAlgorithm(OverlappingZonesAlgorithm overlappingAlgorithm);
private: private:
// Work area is uniquely defined by monitor and virtual desktop id. // Work area is uniquely defined by monitor and virtual desktop id.

View File

@ -1,10 +1,15 @@
#include "pch.h" #include "pch.h"
#include <common/SettingsAPI/settings_objects.h> #include "Settings.h"
#include <common/utils/resources.h>
#include "FancyZonesLib/Settings.h" #include <common/logger/call_tracer.h>
#include "FancyZonesLib/FancyZones.h" #include <common/logger/logger.h>
#include "trace.h" #include <common/SettingsAPI/FileWatcher.h>
#include <common/utils/resources.h>
#include <common/utils/string_utils.h>
#include <FancyZonesLib/FancyZonesWinHookEventIDs.h>
#include <FancyZonesLib/SettingsObserver.h>
#include <FancyZonesLib/trace.h>
// Non-Localizable strings // Non-Localizable strings
namespace NonLocalizable namespace NonLocalizable
@ -27,6 +32,8 @@ namespace NonLocalizable
const wchar_t ShowOnAllMonitorsID[] = L"fancyzones_show_on_all_monitors"; const wchar_t ShowOnAllMonitorsID[] = L"fancyzones_show_on_all_monitors";
const wchar_t SpanZonesAcrossMonitorsID[] = L"fancyzones_span_zones_across_monitors"; const wchar_t SpanZonesAcrossMonitorsID[] = L"fancyzones_span_zones_across_monitors";
const wchar_t MakeDraggedWindowTransparentID[] = L"fancyzones_makeDraggedWindowTransparent"; const wchar_t MakeDraggedWindowTransparentID[] = L"fancyzones_makeDraggedWindowTransparent";
const wchar_t AllowPopupWindowSnapID[] = L"fancyzones_allowPopupWindowSnap";
const wchar_t AllowChildWindowSnapID[] = L"fancyzones_allowChildWindowSnap";
const wchar_t SystemThemeID[] = L"fancyzones_systemTheme"; const wchar_t SystemThemeID[] = L"fancyzones_systemTheme";
const wchar_t ZoneColorID[] = L"fancyzones_zoneColor"; const wchar_t ZoneColorID[] = L"fancyzones_zoneColor";
@ -40,214 +47,208 @@ namespace NonLocalizable
const wchar_t ExcludedAppsID[] = L"fancyzones_excluded_apps"; const wchar_t ExcludedAppsID[] = L"fancyzones_excluded_apps";
const wchar_t ZoneHighlightOpacityID[] = L"fancyzones_highlight_opacity"; const wchar_t ZoneHighlightOpacityID[] = L"fancyzones_highlight_opacity";
const wchar_t ShowZoneNumberID[] = L"fancyzones_showZoneNumber"; const wchar_t ShowZoneNumberID[] = L"fancyzones_showZoneNumber";
const wchar_t ToggleEditorActionID[] = L"ToggledFZEditor";
const wchar_t IconKeyID[] = L"pt-fancy-zones";
const wchar_t OverviewURL[] = L"https://aka.ms/PowerToysOverview_FancyZones";
const wchar_t VideoURL[] = L"https://youtu.be/rTtGzZYAXgY";
const wchar_t PowerToysIssuesURL[] = L"https://aka.ms/powerToysReportBug";
} }
struct FancyZonesSettings : winrt::implements<FancyZonesSettings, IFancyZonesSettings> FancyZonesSettings::FancyZonesSettings()
{ {
public: const std::wstring& settingsFileName = GetSettingsFileName();
FancyZonesSettings(HINSTANCE hinstance, PCWSTR name, PCWSTR key) : m_settingsFileWatcher = std::make_unique<FileWatcher>(settingsFileName, [&]() {
m_hinstance(hinstance), PostMessageW(HWND_BROADCAST, WM_PRIV_SETTINGS_CHANGED, NULL, NULL);
m_moduleName(name), });
m_moduleKey(key) }
FancyZonesSettings& FancyZonesSettings::instance()
{
static FancyZonesSettings instance;
return instance;
}
void FancyZonesSettings::AddObserver(SettingsObserver& observer)
{
m_observers.insert(&observer);
}
void FancyZonesSettings::RemoveObserver(SettingsObserver& observer)
{
auto iter = m_observers.find(&observer);
if (iter != m_observers.end())
{ {
LoadSettings(name, true); m_observers.erase(iter);
} }
}
IFACEMETHODIMP_(bool) void FancyZonesSettings::SetBoolFlag(const PowerToysSettings::PowerToyValues& values, const wchar_t* id, SettingId notificationId, bool& out)
GetConfig(_Out_ PWSTR buffer, _Out_ int* buffer_sizeg) noexcept;
IFACEMETHODIMP_(void)
SetConfig(PCWSTR config) noexcept;
IFACEMETHODIMP_(void)
ReloadSettings() noexcept;
IFACEMETHODIMP_(const Settings*)
GetSettings() const noexcept { return &m_settings; }
private:
void LoadSettings(PCWSTR config, bool fromFile) noexcept;
void SaveSettings() noexcept;
const HINSTANCE m_hinstance;
std::wstring m_moduleName{};
std::wstring m_moduleKey{};
Settings m_settings;
struct
{
PCWSTR name;
bool* value;
int resourceId;
} m_configBools[19] = {
{ NonLocalizable::ShiftDragID, &m_settings.shiftDrag, IDS_SETTING_DESCRIPTION_SHIFTDRAG },
{ NonLocalizable::MouseSwitchID, &m_settings.mouseSwitch, IDS_SETTING_DESCRIPTION_MOUSESWITCH },
{ NonLocalizable::OverrideSnapHotKeysID, &m_settings.overrideSnapHotkeys, IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS },
{ NonLocalizable::MoveWindowAcrossMonitorsID, &m_settings.moveWindowAcrossMonitors, IDS_SETTING_DESCRIPTION_MOVE_WINDOW_ACROSS_MONITORS },
{ NonLocalizable::MoveWindowsBasedOnPositionID, &m_settings.moveWindowsBasedOnPosition, IDS_SETTING_DESCRIPTION_MOVE_WINDOWS_BASED_ON_POSITION },
{ NonLocalizable::DisplayChangeMoveWindowsID, &m_settings.displayChange_moveWindows, IDS_SETTING_DESCRIPTION_DISPLAYCHANGE_MOVEWINDOWS },
{ NonLocalizable::ZoneSetChangeMoveWindowsID, &m_settings.zoneSetChange_moveWindows, IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS },
{ NonLocalizable::AppLastZoneMoveWindowsID, &m_settings.appLastZone_moveWindows, IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS },
{ NonLocalizable::OpenWindowOnActiveMonitorID, &m_settings.openWindowOnActiveMonitor, IDS_SETTING_DESCRIPTION_OPEN_WINDOW_ON_ACTIVE_MONITOR },
{ NonLocalizable::RestoreSizeID, &m_settings.restoreSize, IDS_SETTING_DESCRIPTION_RESTORESIZE },
{ NonLocalizable::QuickLayoutSwitch, &m_settings.quickLayoutSwitch, IDS_SETTING_DESCRIPTION_QUICKLAYOUTSWITCH },
{ NonLocalizable::FlashZonesOnQuickSwitch, &m_settings.flashZonesOnQuickSwitch, IDS_SETTING_DESCRIPTION_FLASHZONESONQUICKSWITCH },
{ NonLocalizable::UseCursorPosEditorStartupScreenID, &m_settings.use_cursorpos_editor_startupscreen, IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN },
{ NonLocalizable::ShowOnAllMonitorsID, &m_settings.showZonesOnAllMonitors, IDS_SETTING_DESCRIPTION_SHOW_FANCY_ZONES_ON_ALL_MONITORS },
{ NonLocalizable::SpanZonesAcrossMonitorsID, &m_settings.spanZonesAcrossMonitors, IDS_SETTING_DESCRIPTION_SPAN_ZONES_ACROSS_MONITORS },
{ NonLocalizable::MakeDraggedWindowTransparentID, &m_settings.makeDraggedWindowTransparent, IDS_SETTING_DESCRIPTION_MAKE_DRAGGED_WINDOW_TRANSPARENT },
{ NonLocalizable::WindowSwitchingToggleID, &m_settings.windowSwitching, IDS_SETTING_WINDOW_SWITCHING_TOGGLE_LABEL },
{ NonLocalizable::SystemThemeID, &m_settings.systemTheme, IDS_SETTING_DESCRIPTION_SYSTEM_THEME },
{ NonLocalizable::ShowZoneNumberID, &m_settings.showZoneNumber, IDS_SETTING_DESCRIPTION_SHOW_ZONE_NUMBER },
};
};
IFACEMETHODIMP_(bool)
FancyZonesSettings::GetConfig(_Out_ PWSTR buffer, _Out_ int* buffer_size) noexcept
{ {
PowerToysSettings::Settings settings(m_hinstance, m_moduleName); if (const auto val = values.get_bool_value(id))
// Pass a string literal or a resource id to Settings::set_description().
settings.set_description(IDS_SETTING_DESCRIPTION);
settings.set_icon_key(NonLocalizable::IconKeyID);
settings.set_overview_link(NonLocalizable::OverviewURL);
settings.set_video_link(NonLocalizable::VideoURL);
// Add a custom action property. When using this settings type, the "PowertoyModuleIface::call_custom_action()"
// method should be overridden as well.
settings.add_custom_action(
NonLocalizable::ToggleEditorActionID, // action name.
IDS_SETTING_LAUNCH_EDITOR_LABEL,
IDS_SETTING_LAUNCH_EDITOR_BUTTON,
IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION);
settings.add_hotkey(NonLocalizable::EditorHotkeyID, IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL, m_settings.editorHotkey);
settings.add_hotkey(NonLocalizable::NextTabHotkeyID, IDS_SETTING_NEXT_TAB_HOTKEY_LABEL, m_settings.nextTabHotkey);
settings.add_hotkey(NonLocalizable::PrevTabHotkeyID, IDS_SETTING_PREV_TAB_HOTKEY_LABEL, m_settings.prevTabHotkey);
for (auto const& setting : m_configBools)
{ {
settings.add_bool_toggle(setting.name, setting.resourceId, *setting.value); if (out != *val)
{
out = *val;
NotifyObservers(notificationId);
}
} }
settings.add_color_picker(NonLocalizable::ZoneHighlightColorID, IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR, m_settings.zoneHighlightColor);
settings.add_color_picker(NonLocalizable::ZoneColorID, IDS_SETTING_DESCRIPTION_ZONECOLOR, m_settings.zoneColor);
settings.add_color_picker(NonLocalizable::ZoneBorderColorID, IDS_SETTING_DESCRIPTION_ZONE_BORDER_COLOR, m_settings.zoneBorderColor);
settings.add_int_spinner(NonLocalizable::ZoneHighlightOpacityID, IDS_SETTINGS_HIGHLIGHT_OPACITY, m_settings.zoneHighlightOpacity, 0, 100, 1);
settings.add_multiline_string(NonLocalizable::ExcludedAppsID, IDS_SETTING_EXCLUDED_APPS_DESCRIPTION, m_settings.excludedApps);
return settings.serialize_to_buffer(buffer, buffer_size);
} }
IFACEMETHODIMP_(void) void FancyZonesSettings::LoadSettings()
FancyZonesSettings::SetConfig(PCWSTR serializedPowerToysSettingsJson) noexcept
{ {
LoadSettings(serializedPowerToysSettingsJson, false /*fromFile*/); _TRACER_;
SaveSettings();
}
IFACEMETHODIMP_(void)
FancyZonesSettings::ReloadSettings() noexcept
{
LoadSettings(m_moduleKey.c_str(), true /*fromFile*/);
}
void FancyZonesSettings::LoadSettings(PCWSTR config, bool fromFile) noexcept
{
try try
{ {
PowerToysSettings::PowerToyValues values = fromFile ? auto jsonOpt = json::from_file(GetSettingsFileName());
PowerToysSettings::PowerToyValues::load_from_settings_file(m_moduleKey) : if (!jsonOpt)
PowerToysSettings::PowerToyValues::from_json_string(config, m_moduleKey);
for (auto const& setting : m_configBools)
{ {
if (const auto val = values.get_bool_value(setting.name)) Logger::warn(L"Failed to read from settings file");
{ return;
*setting.value = *val;
}
} }
PowerToysSettings::PowerToyValues values = PowerToysSettings::PowerToyValues::from_json_string(jsonOpt->Stringify(), NonLocalizable::ModuleKey);
// flags
SetBoolFlag(values, NonLocalizable::ShiftDragID, SettingId::ShiftDrag, m_settings.shiftDrag);
SetBoolFlag(values, NonLocalizable::MouseSwitchID, SettingId::MouseSwitch, m_settings.mouseSwitch);
SetBoolFlag(values, NonLocalizable::OverrideSnapHotKeysID, SettingId::OverrideSnapHotkeys, m_settings.overrideSnapHotkeys);
SetBoolFlag(values, NonLocalizable::MoveWindowAcrossMonitorsID, SettingId::MoveWindowAcrossMonitors, m_settings.moveWindowAcrossMonitors);
SetBoolFlag(values, NonLocalizable::MoveWindowsBasedOnPositionID, SettingId::MoveWindowsBasedOnPosition, m_settings.moveWindowsBasedOnPosition);
SetBoolFlag(values, NonLocalizable::DisplayChangeMoveWindowsID, SettingId::DisplayChangeMoveWindows, m_settings.displayChange_moveWindows);
SetBoolFlag(values, NonLocalizable::ZoneSetChangeMoveWindowsID, SettingId::ZoneSetChangeMoveWindows, m_settings.zoneSetChange_moveWindows);
SetBoolFlag(values, NonLocalizable::AppLastZoneMoveWindowsID, SettingId::AppLastZoneMoveWindows, m_settings.appLastZone_moveWindows);
SetBoolFlag(values, NonLocalizable::OpenWindowOnActiveMonitorID, SettingId::OpenWindowOnActiveMonitor, m_settings.openWindowOnActiveMonitor);
SetBoolFlag(values, NonLocalizable::RestoreSizeID, SettingId::RestoreWindowSize, m_settings.restoreSize);
SetBoolFlag(values, NonLocalizable::QuickLayoutSwitch, SettingId::QuickLayoutSwitch, m_settings.quickLayoutSwitch);
SetBoolFlag(values, NonLocalizable::FlashZonesOnQuickSwitch, SettingId::FlashZonesOnQuickSwitch, m_settings.flashZonesOnQuickSwitch);
SetBoolFlag(values, NonLocalizable::UseCursorPosEditorStartupScreenID, SettingId::LaunchEditorOnScreenWhereCursorPlaced, m_settings.use_cursorpos_editor_startupscreen);
SetBoolFlag(values, NonLocalizable::ShowOnAllMonitorsID, SettingId::ShowOnAllMonitors, m_settings.showZonesOnAllMonitors);
SetBoolFlag(values, NonLocalizable::SpanZonesAcrossMonitorsID, SettingId::SpanZonesAcrossMonitors, m_settings.spanZonesAcrossMonitors);
SetBoolFlag(values, NonLocalizable::MakeDraggedWindowTransparentID, SettingId::MakeDraggedWindowsTransparent, m_settings.makeDraggedWindowTransparent);
SetBoolFlag(values, NonLocalizable::WindowSwitchingToggleID, SettingId::WindowSwitching, m_settings.windowSwitching);
SetBoolFlag(values, NonLocalizable::SystemThemeID, SettingId::SystemTheme, m_settings.systemTheme);
SetBoolFlag(values, NonLocalizable::ShowZoneNumberID, SettingId::ShowZoneNumber, m_settings.showZoneNumber);
SetBoolFlag(values, NonLocalizable::AllowPopupWindowSnapID, SettingId::AllowSnapPopupWindows, m_settings.allowSnapPopupWindows);
SetBoolFlag(values, NonLocalizable::AllowChildWindowSnapID, SettingId::AllowSnapChildWindows, m_settings.allowSnapChildWindows);
// colors
if (auto val = values.get_string_value(NonLocalizable::ZoneColorID)) if (auto val = values.get_string_value(NonLocalizable::ZoneColorID))
{ {
m_settings.zoneColor = std::move(*val); if (m_settings.zoneColor != *val)
{
m_settings.zoneColor = std::move(*val);
NotifyObservers(SettingId::ZoneColor);
}
} }
if (auto val = values.get_string_value(NonLocalizable::ZoneBorderColorID)) if (auto val = values.get_string_value(NonLocalizable::ZoneBorderColorID))
{ {
m_settings.zoneBorderColor = std::move(*val); if (m_settings.zoneBorderColor != *val)
{
m_settings.zoneBorderColor = std::move(*val);
NotifyObservers(SettingId::ZoneBorderColor);
}
} }
if (auto val = values.get_string_value(NonLocalizable::ZoneHighlightColorID)) if (auto val = values.get_string_value(NonLocalizable::ZoneHighlightColorID))
{ {
m_settings.zoneHighlightColor = std::move(*val); if (m_settings.zoneHighlightColor != *val)
{
m_settings.zoneHighlightColor = std::move(*val);
NotifyObservers(SettingId::ZoneHighlightColor);
}
} }
if (auto val = values.get_string_value(NonLocalizable::ZoneNumberColorID)) if (auto val = values.get_string_value(NonLocalizable::ZoneNumberColorID))
{ {
m_settings.zoneNumberColor = std::move(*val); if (m_settings.zoneNumberColor != *val)
}
if (const auto val = values.get_json(NonLocalizable::EditorHotkeyID))
{
m_settings.editorHotkey = PowerToysSettings::HotkeyObject::from_json(*val);
}
if (const auto val = values.get_json(NonLocalizable::NextTabHotkeyID))
{
m_settings.nextTabHotkey = PowerToysSettings::HotkeyObject::from_json(*val);
}
if (const auto val = values.get_json(NonLocalizable::PrevTabHotkeyID))
{
m_settings.prevTabHotkey = PowerToysSettings::HotkeyObject::from_json(*val);
}
if (auto val = values.get_string_value(NonLocalizable::ExcludedAppsID))
{
m_settings.excludedApps = std::move(*val);
m_settings.excludedAppsArray.clear();
auto excludedUppercase = m_settings.excludedApps;
CharUpperBuffW(excludedUppercase.data(), (DWORD)excludedUppercase.length());
std::wstring_view view(excludedUppercase);
while (view.starts_with('\n') || view.starts_with('\r'))
{ {
view.remove_prefix(1); m_settings.zoneNumberColor = std::move(*val);
} NotifyObservers(SettingId::ZoneNumberColor);
while (!view.empty())
{
auto pos = (std::min)(view.find_first_of(L"\r\n"), view.length());
m_settings.excludedAppsArray.emplace_back(view.substr(0, pos));
view.remove_prefix(pos);
while (view.starts_with('\n') || view.starts_with('\r'))
{
view.remove_prefix(1);
}
} }
} }
if (auto val = values.get_int_value(NonLocalizable::ZoneHighlightOpacityID)) if (auto val = values.get_int_value(NonLocalizable::ZoneHighlightOpacityID))
{ {
m_settings.zoneHighlightOpacity = *val; if (m_settings.zoneHighlightOpacity != *val)
{
m_settings.zoneHighlightOpacity = std::move(*val);
NotifyObservers(SettingId::ZoneHighlightOpacity);
}
} }
// hotkeys
if (const auto val = values.get_json(NonLocalizable::EditorHotkeyID))
{
auto hotkey = PowerToysSettings::HotkeyObject::from_json(*val);
if (m_settings.editorHotkey.get_modifiers() != hotkey.get_modifiers() || m_settings.editorHotkey.get_key() != hotkey.get_key() || m_settings.editorHotkey.get_code() != hotkey.get_code())
{
m_settings.editorHotkey = std::move(hotkey);
NotifyObservers(SettingId::EditorHotkey);
}
}
if (const auto val = values.get_json(NonLocalizable::NextTabHotkeyID))
{
auto hotkey = PowerToysSettings::HotkeyObject::from_json(*val);
if (m_settings.nextTabHotkey.get_modifiers() != hotkey.get_modifiers() || m_settings.nextTabHotkey.get_key() != hotkey.get_key() || m_settings.nextTabHotkey.get_code() != hotkey.get_code())
{
m_settings.nextTabHotkey = PowerToysSettings::HotkeyObject::from_json(*val);
NotifyObservers(SettingId::NextTabHotkey);
}
}
if (const auto val = values.get_json(NonLocalizable::PrevTabHotkeyID))
{
auto hotkey = PowerToysSettings::HotkeyObject::from_json(*val);
if (m_settings.prevTabHotkey.get_modifiers() != hotkey.get_modifiers() || m_settings.prevTabHotkey.get_key() != hotkey.get_key() || m_settings.prevTabHotkey.get_code() != hotkey.get_code())
{
m_settings.prevTabHotkey = PowerToysSettings::HotkeyObject::from_json(*val);
NotifyObservers(SettingId::PrevTabHotkey);
}
}
// excluded apps
if (auto val = values.get_string_value(NonLocalizable::ExcludedAppsID))
{
std::wstring apps = std::move(*val);
std::vector<std::wstring> excludedApps;
auto excludedUppercase = apps;
CharUpperBuffW(excludedUppercase.data(), (DWORD)excludedUppercase.length());
std::wstring_view view(excludedUppercase);
view = left_trim<wchar_t>(trim<wchar_t>(view));
while (!view.empty())
{
auto pos = (std::min)(view.find_first_of(L"\r\n"), view.length());
excludedApps.emplace_back(view.substr(0, pos));
view.remove_prefix(pos);
view = left_trim<wchar_t>(trim<wchar_t>(view));
}
if (m_settings.excludedAppsArray != excludedApps)
{
m_settings.excludedApps = apps;
m_settings.excludedAppsArray = excludedApps;
NotifyObservers(SettingId::ExcludedApps);
}
}
// algorithms
if (auto val = values.get_int_value(NonLocalizable::OverlappingZonesAlgorithmID)) if (auto val = values.get_int_value(NonLocalizable::OverlappingZonesAlgorithmID))
{ {
// Avoid undefined behavior // Avoid undefined behavior
if (*val >= 0 || *val < (int)OverlappingZonesAlgorithm::EnumElements) if (*val >= 0 || *val < (int)OverlappingZonesAlgorithm::EnumElements)
{ {
m_settings.overlappingZonesAlgorithm = (OverlappingZonesAlgorithm)*val; auto algorithm = (OverlappingZonesAlgorithm)*val;
if (m_settings.overlappingZonesAlgorithm != algorithm)
{
m_settings.overlappingZonesAlgorithm = algorithm;
NotifyObservers(SettingId::OverlappingZonesAlgorithm);
}
} }
} }
} }
catch (...) catch (const winrt::hresult_error& e)
{ {
// Failure to load settings does not break FancyZones functionality. Display error message and continue with default settings. // Failure to load settings does not break FancyZones functionality. Display error message and continue with default settings.
Logger::error(L"Failed to read settings. {}", e.message());
MessageBox(NULL, MessageBox(NULL,
GET_RESOURCE_STRING(IDS_FANCYZONES_SETTINGS_LOAD_ERROR).c_str(), GET_RESOURCE_STRING(IDS_FANCYZONES_SETTINGS_LOAD_ERROR).c_str(),
GET_RESOURCE_STRING(IDS_POWERTOYS_FANCYZONES).c_str(), GET_RESOURCE_STRING(IDS_POWERTOYS_FANCYZONES).c_str(),
@ -255,42 +256,13 @@ void FancyZonesSettings::LoadSettings(PCWSTR config, bool fromFile) noexcept
} }
} }
void FancyZonesSettings::SaveSettings() noexcept void FancyZonesSettings::NotifyObservers(SettingId id) const
{ {
try for (auto observer : m_observers)
{ {
PowerToysSettings::PowerToyValues values(m_moduleName, m_moduleKey); if (observer->WantsToBeNotified(id))
for (auto const& setting : m_configBools)
{ {
values.add_property(setting.name, *setting.value); observer->SettingsUpdate(id);
} }
values.add_property(NonLocalizable::ZoneColorID, m_settings.zoneColor);
values.add_property(NonLocalizable::ZoneBorderColorID, m_settings.zoneBorderColor);
values.add_property(NonLocalizable::ZoneHighlightColorID, m_settings.zoneHighlightColor);
values.add_property(NonLocalizable::ZoneNumberColorID, m_settings.zoneNumberColor);
values.add_property(NonLocalizable::ZoneHighlightOpacityID, m_settings.zoneHighlightOpacity);
values.add_property(NonLocalizable::OverlappingZonesAlgorithmID, (int)m_settings.overlappingZonesAlgorithm);
values.add_property(NonLocalizable::EditorHotkeyID, m_settings.editorHotkey.get_json());
values.add_property(NonLocalizable::NextTabHotkeyID, m_settings.nextTabHotkey.get_json());
values.add_property(NonLocalizable::PrevTabHotkeyID, m_settings.prevTabHotkey.get_json());
values.add_property(NonLocalizable::ExcludedAppsID, m_settings.excludedApps);
values.save_to_settings_file();
}
catch (...)
{
// Failure to save settings does not break FancyZones functionality. Display error message and continue with currently cached settings.
std::wstring errorMessage = GET_RESOURCE_STRING(IDS_FANCYZONES_SETTINGS_LOAD_ERROR) + L" " + NonLocalizable::PowerToysIssuesURL;
MessageBox(NULL,
errorMessage.c_str(),
GET_RESOURCE_STRING(IDS_POWERTOYS_FANCYZONES).c_str(),
MB_OK);
} }
} }
winrt::com_ptr<IFancyZonesSettings> MakeFancyZonesSettings(HINSTANCE hinstance, PCWSTR name, PCWSTR key) noexcept
{
return winrt::make_self<FancyZonesSettings>(hinstance, name, key);
}

View File

@ -1,7 +1,16 @@
#pragma once #pragma once
#include <unordered_set>
#include <common/SettingsAPI/settings_helpers.h>
#include <common/SettingsAPI/settings_objects.h> #include <common/SettingsAPI/settings_objects.h>
#include <FancyZonesLib/ModuleConstants.h>
#include <FancyZonesLib/SettingsConstants.h>
class SettingsObserver;
class FileWatcher;
enum struct OverlappingZonesAlgorithm : int enum struct OverlappingZonesAlgorithm : int
{ {
Smallest = 0, Smallest = 0,
@ -34,6 +43,8 @@ struct Settings
bool makeDraggedWindowTransparent = true; bool makeDraggedWindowTransparent = true;
bool systemTheme = true; bool systemTheme = true;
bool showZoneNumber = true; bool showZoneNumber = true;
bool allowSnapPopupWindows = false;
bool allowSnapChildWindows = false;
std::wstring zoneColor = L"#AACDFF"; std::wstring zoneColor = L"#AACDFF";
std::wstring zoneBorderColor = L"#FFFFFF"; std::wstring zoneBorderColor = L"#FFFFFF";
std::wstring zoneHighlightColor = L"#008CFF"; std::wstring zoneHighlightColor = L"#008CFF";
@ -48,12 +59,38 @@ struct Settings
std::vector<std::wstring> excludedAppsArray; std::vector<std::wstring> excludedAppsArray;
}; };
interface __declspec(uuid("{BA4E77C4-6F44-4C5D-93D3-CBDE880495C2}")) IFancyZonesSettings : public IUnknown class FancyZonesSettings
{ {
IFACEMETHOD_(bool, GetConfig)(_Out_ PWSTR buffer, _Out_ int *buffer_size) = 0; public:
IFACEMETHOD_(void, SetConfig)(PCWSTR serializedPowerToysSettings) = 0; static FancyZonesSettings& instance();
IFACEMETHOD_(void, ReloadSettings)() = 0; static inline const Settings& settings()
IFACEMETHOD_(const Settings*, GetSettings)() const = 0; {
}; return instance().m_settings;
}
winrt::com_ptr<IFancyZonesSettings> MakeFancyZonesSettings(HINSTANCE hinstance, PCWSTR name, PCWSTR key) noexcept; inline static std::wstring GetSettingsFileName()
{
std::wstring saveFolderPath = PTSettingsHelper::get_module_save_folder_location(NonLocalizable::ModuleKey);
#if defined(UNIT_TESTS)
return saveFolderPath + L"\\test-settings.json";
#endif
return saveFolderPath + L"\\settings.json";
}
void AddObserver(SettingsObserver& observer);
void RemoveObserver(SettingsObserver& observer);
void LoadSettings();
private:
FancyZonesSettings();
~FancyZonesSettings() = default;
Settings m_settings;
std::unique_ptr<FileWatcher> m_settingsFileWatcher;
std::unordered_set<SettingsObserver*> m_observers;
void SetBoolFlag(const PowerToysSettings::PowerToyValues& values, const wchar_t* id, SettingId notificationId, bool& out);
void NotifyObservers(SettingId id) const;
};

View File

@ -0,0 +1,36 @@
#pragma once
enum class SettingId
{
ShiftDrag = 0,
MouseSwitch,
OverrideSnapHotkeys,
MoveWindowAcrossMonitors,
MoveWindowsBasedOnPosition,
OverlappingZonesAlgorithm,
DisplayChangeMoveWindows,
ZoneSetChangeMoveWindows,
AppLastZoneMoveWindows,
OpenWindowOnActiveMonitor,
RestoreWindowSize,
QuickLayoutSwitch,
FlashZonesOnQuickSwitch,
LaunchEditorOnScreenWhereCursorPlaced,
ShowOnAllMonitors,
SpanZonesAcrossMonitors,
MakeDraggedWindowsTransparent,
ZoneHighlightColor,
ZoneHighlightOpacity,
ZoneBorderColor,
ZoneColor,
ZoneNumberColor,
SystemTheme,
ShowZoneNumber,
EditorHotkey,
WindowSwitching,
NextTabHotkey,
PrevTabHotkey,
ExcludedApps,
AllowSnapPopupWindows,
AllowSnapChildWindows,
};

View File

@ -0,0 +1,31 @@
#pragma once
#include <unordered_set>
#include <FancyZonesLib/Settings.h>
#include <FancyZonesLib/SettingsConstants.h>
class SettingsObserver
{
public:
SettingsObserver(std::unordered_set<SettingId> observedSettings) :
m_observedSettings(std::move(observedSettings))
{
FancyZonesSettings::instance().AddObserver(*this);
}
virtual ~SettingsObserver()
{
FancyZonesSettings::instance().RemoveObserver(*this);
}
virtual void SettingsUpdate(SettingId type) {}
bool WantsToBeNotified(SettingId type) const noexcept
{
return m_observedSettings.contains(type);
}
protected:
std::unordered_set<SettingId> m_observedSettings;
};

View File

@ -10,7 +10,7 @@
#include "FancyZonesData/AppZoneHistory.h" #include "FancyZonesData/AppZoneHistory.h"
#include "Settings.h" #include "Settings.h"
#include "WorkArea.h" #include "WorkArea.h"
#include "util.h" #include <FancyZonesLib/WindowUtils.h>
// Non-Localizable strings // Non-Localizable strings
namespace NonLocalizable namespace NonLocalizable
@ -49,8 +49,7 @@ namespace WindowMoveHandlerUtils
} }
} }
WindowMoveHandler::WindowMoveHandler(const winrt::com_ptr<IFancyZonesSettings>& settings, const std::function<void()>& keyUpdateCallback) : WindowMoveHandler::WindowMoveHandler(const std::function<void()>& keyUpdateCallback) :
m_settings(settings),
m_mouseState(false), m_mouseState(false),
m_mouseHook(std::bind(&WindowMoveHandler::OnMouseDown, this)), m_mouseHook(std::bind(&WindowMoveHandler::OnMouseDown, this)),
m_shiftKeyState(keyUpdateCallback), m_shiftKeyState(keyUpdateCallback),
@ -61,13 +60,13 @@ WindowMoveHandler::WindowMoveHandler(const winrt::com_ptr<IFancyZonesSettings>&
void WindowMoveHandler::MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen, const std::unordered_map<HMONITOR, winrt::com_ptr<IWorkArea>>& workAreaMap) noexcept void WindowMoveHandler::MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen, const std::unordered_map<HMONITOR, winrt::com_ptr<IWorkArea>>& workAreaMap) noexcept
{ {
if (!FancyZonesUtils::IsCandidateForZoning(window, m_settings->GetSettings()->excludedAppsArray) || WindowMoveHandlerUtils::IsCursorTypeIndicatingSizeEvent()) if (!FancyZonesWindowUtils::IsCandidateForZoning(window) || WindowMoveHandlerUtils::IsCursorTypeIndicatingSizeEvent())
{ {
return; return;
} }
m_draggedWindowInfo.hasNoVisibleOwner = FancyZonesUtils::HasNoVisibleOwner(window); m_draggedWindowInfo.hasNoVisibleOwner = !FancyZonesWindowUtils::HasVisibleOwner(window);
m_draggedWindowInfo.isStandardWindow = FancyZonesUtils::IsStandardWindow(window); m_draggedWindowInfo.isStandardWindow = FancyZonesWindowUtils::IsStandardWindow(window) && (!FancyZonesWindowUtils::IsPopupWindow(window) || FancyZonesSettings::settings().allowSnapPopupWindows);
m_inDragging = true; m_inDragging = true;
auto iter = workAreaMap.find(monitor); auto iter = workAreaMap.find(monitor);
@ -78,7 +77,7 @@ void WindowMoveHandler::MoveSizeStart(HWND window, HMONITOR monitor, POINT const
m_draggedWindow = window; m_draggedWindow = window;
if (m_settings->GetSettings()->mouseSwitch) if (FancyZonesSettings::settings().mouseSwitch)
{ {
m_mouseHook.enable(); m_mouseHook.enable();
} }
@ -97,7 +96,7 @@ void WindowMoveHandler::MoveSizeStart(HWND window, HMONITOR monitor, POINT const
m_draggedWindowWorkArea = iter->second; m_draggedWindowWorkArea = iter->second;
SetWindowTransparency(m_draggedWindow); SetWindowTransparency(m_draggedWindow);
m_draggedWindowWorkArea->MoveSizeEnter(m_draggedWindow); m_draggedWindowWorkArea->MoveSizeEnter(m_draggedWindow);
if (m_settings->GetSettings()->showZonesOnAllMonitors) if (FancyZonesSettings::settings().showZonesOnAllMonitors)
{ {
for (auto [keyMonitor, workArea] : workAreaMap) for (auto [keyMonitor, workArea] : workAreaMap)
{ {
@ -172,7 +171,7 @@ void WindowMoveHandler::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen,
{ {
// The drag has moved to a different monitor. // The drag has moved to a different monitor.
m_draggedWindowWorkArea->ClearSelectedZones(); m_draggedWindowWorkArea->ClearSelectedZones();
if (!m_settings->GetSettings()->showZonesOnAllMonitors) if (!FancyZonesSettings::settings().showZonesOnAllMonitors)
{ {
m_draggedWindowWorkArea->HideZonesOverlay(); m_draggedWindowWorkArea->HideZonesOverlay();
} }
@ -219,12 +218,12 @@ void WindowMoveHandler::MoveSizeEnd(HWND window, POINT const& ptScreen, const st
auto workArea = std::move(m_draggedWindowWorkArea); auto workArea = std::move(m_draggedWindowWorkArea);
ResetWindowTransparency(); ResetWindowTransparency();
bool hasNoVisibleOwner = FancyZonesUtils::HasNoVisibleOwner(window); bool hasNoVisibleOwner = !FancyZonesWindowUtils::HasVisibleOwner(window);
bool isStandardWindow = FancyZonesUtils::IsStandardWindow(window); bool isStandardWindow = FancyZonesWindowUtils::IsStandardWindow(window);
if ((isStandardWindow == false && hasNoVisibleOwner == true && if ((isStandardWindow == false && hasNoVisibleOwner == true &&
m_draggedWindowInfo.isStandardWindow == true && m_draggedWindowInfo.hasNoVisibleOwner == true) || m_draggedWindowInfo.isStandardWindow == true && m_draggedWindowInfo.hasNoVisibleOwner == true) ||
FancyZonesUtils::IsWindowMaximized(window)) FancyZonesWindowUtils::IsWindowMaximized(window))
{ {
// Abort the zoning, this is a Chromium based tab that is merged back with an existing window // Abort the zoning, this is a Chromium based tab that is merged back with an existing window
// or if the window is maximized by Windows when the cursor hits the screen top border // or if the window is maximized by Windows when the cursor hits the screen top border
@ -236,15 +235,15 @@ void WindowMoveHandler::MoveSizeEnd(HWND window, POINT const& ptScreen, const st
} }
else else
{ {
if (m_settings->GetSettings()->restoreSize) if (FancyZonesSettings::settings().restoreSize)
{ {
if (WindowMoveHandlerUtils::IsCursorTypeIndicatingSizeEvent()) if (WindowMoveHandlerUtils::IsCursorTypeIndicatingSizeEvent())
{ {
::RemoveProp(window, ZonedWindowProperties::PropertyRestoreSizeID); ::RemoveProp(window, ZonedWindowProperties::PropertyRestoreSizeID);
} }
else if (!FancyZonesUtils::IsWindowMaximized(window)) else if (!FancyZonesWindowUtils::IsWindowMaximized(window))
{ {
FancyZonesUtils::RestoreWindowSize(window); FancyZonesWindowUtils::RestoreWindowSize(window);
} }
} }
@ -312,10 +311,9 @@ void WindowMoveHandler::WarnIfElevationIsRequired(HWND window) noexcept
{ {
using namespace notifications; using namespace notifications;
using namespace NonLocalizable; using namespace NonLocalizable;
using namespace FancyZonesUtils;
static bool warning_shown = false; static bool warning_shown = false;
if (!is_process_elevated() && IsProcessOfWindowElevated(window)) if (!is_process_elevated() && FancyZonesWindowUtils::IsProcessOfWindowElevated(window))
{ {
m_dragEnabled = false; m_dragEnabled = false;
if (!warning_shown && !is_toast_disabled(CantDragElevatedDontShowAgainRegistryPath, CantDragElevatedDisableIntervalInDays)) if (!warning_shown && !is_toast_disabled(CantDragElevatedDontShowAgainRegistryPath, CantDragElevatedDisableIntervalInDays))
@ -335,7 +333,7 @@ void WindowMoveHandler::WarnIfElevationIsRequired(HWND window) noexcept
void WindowMoveHandler::UpdateDragState() noexcept void WindowMoveHandler::UpdateDragState() noexcept
{ {
if (m_settings->GetSettings()->shiftDrag) if (FancyZonesSettings::settings().shiftDrag)
{ {
m_dragEnabled = (m_shiftKeyState.state() ^ m_mouseState); m_dragEnabled = (m_shiftKeyState.state() ^ m_mouseState);
} }
@ -347,7 +345,7 @@ void WindowMoveHandler::UpdateDragState() noexcept
void WindowMoveHandler::SetWindowTransparency(HWND window) noexcept void WindowMoveHandler::SetWindowTransparency(HWND window) noexcept
{ {
if (m_settings->GetSettings()->makeDraggedWindowTransparent) if (FancyZonesSettings::settings().makeDraggedWindowTransparent)
{ {
m_windowTransparencyProperties.draggedWindowExstyle = GetWindowLong(window, GWL_EXSTYLE); m_windowTransparencyProperties.draggedWindowExstyle = GetWindowLong(window, GWL_EXSTYLE);
@ -364,7 +362,7 @@ void WindowMoveHandler::SetWindowTransparency(HWND window) noexcept
void WindowMoveHandler::ResetWindowTransparency() noexcept void WindowMoveHandler::ResetWindowTransparency() noexcept
{ {
if (m_settings->GetSettings()->makeDraggedWindowTransparent && m_windowTransparencyProperties.draggedWindow != nullptr) if (FancyZonesSettings::settings().makeDraggedWindowTransparent && m_windowTransparencyProperties.draggedWindow != nullptr)
{ {
SetLayeredWindowAttributes(m_windowTransparencyProperties.draggedWindow, m_windowTransparencyProperties.draggedWindowCrKey, m_windowTransparencyProperties.draggedWindowInitialAlpha, m_windowTransparencyProperties.draggedWindowDwFlags); SetLayeredWindowAttributes(m_windowTransparencyProperties.draggedWindow, m_windowTransparencyProperties.draggedWindowCrKey, m_windowTransparencyProperties.draggedWindowInitialAlpha, m_windowTransparencyProperties.draggedWindowDwFlags);
SetWindowLong(m_windowTransparencyProperties.draggedWindow, GWL_EXSTYLE, m_windowTransparencyProperties.draggedWindowExstyle); SetWindowLong(m_windowTransparencyProperties.draggedWindow, GWL_EXSTYLE, m_windowTransparencyProperties.draggedWindowExstyle);

View File

@ -12,7 +12,7 @@ interface IWorkArea;
class WindowMoveHandler class WindowMoveHandler
{ {
public: public:
WindowMoveHandler(const winrt::com_ptr<IFancyZonesSettings>& settings, const std::function<void()>& keyUpdateCallback); WindowMoveHandler(const std::function<void()>& keyUpdateCallback);
void MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen, const std::unordered_map<HMONITOR, winrt::com_ptr<IWorkArea>>& workAreaMap) noexcept; void MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen, const std::unordered_map<HMONITOR, winrt::com_ptr<IWorkArea>>& workAreaMap) noexcept;
void MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, const std::unordered_map<HMONITOR, winrt::com_ptr<IWorkArea>>& workAreaMap) noexcept; void MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, const std::unordered_map<HMONITOR, winrt::com_ptr<IWorkArea>>& workAreaMap) noexcept;
@ -64,8 +64,6 @@ private:
void SetWindowTransparency(HWND window) noexcept; void SetWindowTransparency(HWND window) noexcept;
void ResetWindowTransparency() noexcept; void ResetWindowTransparency() noexcept;
winrt::com_ptr<IFancyZonesSettings> m_settings{};
bool m_inDragging{}; // Whether or not a move/size operation is currently active bool m_inDragging{}; // Whether or not a move/size operation is currently active
HWND m_draggedWindow{}; // The window that is being moved/sized HWND m_draggedWindow{}; // The window that is being moved/sized
MoveSizeWindowInfo m_draggedWindowInfo; // MoveSizeWindowInfo of the window at the moment when dragging started MoveSizeWindowInfo m_draggedWindowInfo; // MoveSizeWindowInfo of the window at the moment when dragging started

View File

@ -0,0 +1,448 @@
#include "pch.h"
#include "WindowUtils.h"
#include <common/display/dpi_aware.h>
#include <common/utils/process_path.h>
#include <common/utils/window.h>
#include <common/utils/excluded_apps.h>
#include <FancyZonesLib/FancyZonesWindowProperties.h>
#include <FancyZonesLib/Settings.h>
// Non-Localizable strings
namespace NonLocalizable
{
const wchar_t PowerToysAppFZEditor[] = L"POWERTOYS.FANCYZONESEDITOR.EXE";
const wchar_t SplashClassName[] = L"MsoSplash";
const wchar_t CoreWindow[] = L"Windows.UI.Core.CoreWindow";
const wchar_t SearchUI[] = L"SearchUI.exe";
const wchar_t SystemAppsFolder[] = L"SYSTEMAPPS";
}
namespace
{
BOOL CALLBACK saveDisplayToVector(HMONITOR monitor, HDC hdc, LPRECT rect, LPARAM data)
{
reinterpret_cast<std::vector<HMONITOR>*>(data)->emplace_back(monitor);
return true;
}
bool allMonitorsHaveSameDpiScaling()
{
std::vector<HMONITOR> monitors;
EnumDisplayMonitors(NULL, NULL, saveDisplayToVector, reinterpret_cast<LPARAM>(&monitors));
if (monitors.size() < 2)
{
return true;
}
UINT firstMonitorDpiX;
UINT firstMonitorDpiY;
if (S_OK != GetDpiForMonitor(monitors[0], MDT_EFFECTIVE_DPI, &firstMonitorDpiX, &firstMonitorDpiY))
{
return false;
}
for (int i = 1; i < monitors.size(); i++)
{
UINT iteratedMonitorDpiX;
UINT iteratedMonitorDpiY;
if (S_OK != GetDpiForMonitor(monitors[i], MDT_EFFECTIVE_DPI, &iteratedMonitorDpiX, &iteratedMonitorDpiY) ||
iteratedMonitorDpiX != firstMonitorDpiX)
{
return false;
}
}
return true;
}
void ScreenToWorkAreaCoords(HWND window, RECT& rect)
{
// First, find the correct monitor. The monitor cannot be found using the given rect itself, we must first
// translate it to relative workspace coordinates.
HMONITOR monitor = MonitorFromRect(&rect, MONITOR_DEFAULTTOPRIMARY);
MONITORINFOEXW monitorInfo{ sizeof(MONITORINFOEXW) };
GetMonitorInfoW(monitor, &monitorInfo);
auto xOffset = monitorInfo.rcWork.left - monitorInfo.rcMonitor.left;
auto yOffset = monitorInfo.rcWork.top - monitorInfo.rcMonitor.top;
auto referenceRect = rect;
referenceRect.left -= xOffset;
referenceRect.right -= xOffset;
referenceRect.top -= yOffset;
referenceRect.bottom -= yOffset;
// Now, this rect should be used to determine the monitor and thus taskbar size. This fixes
// scenarios where the zone lies approximately between two monitors, and the taskbar is on the left.
monitor = MonitorFromRect(&referenceRect, MONITOR_DEFAULTTOPRIMARY);
GetMonitorInfoW(monitor, &monitorInfo);
xOffset = monitorInfo.rcWork.left - monitorInfo.rcMonitor.left;
yOffset = monitorInfo.rcWork.top - monitorInfo.rcMonitor.top;
rect.left -= xOffset;
rect.right -= xOffset;
rect.top -= yOffset;
rect.bottom -= yOffset;
const auto level = DPIAware::GetAwarenessLevel(GetWindowDpiAwarenessContext(window));
const bool accountForUnawareness = level < DPIAware::PER_MONITOR_AWARE;
if (accountForUnawareness && !allMonitorsHaveSameDpiScaling())
{
rect.left = max(monitorInfo.rcMonitor.left, rect.left);
rect.right = min(monitorInfo.rcMonitor.right - xOffset, rect.right);
rect.top = max(monitorInfo.rcMonitor.top, rect.top);
rect.bottom = min(monitorInfo.rcMonitor.bottom - yOffset, rect.bottom);
}
}
}
bool FancyZonesWindowUtils::IsSplashScreen(HWND window)
{
wchar_t className[MAX_PATH];
if (GetClassName(window, className, MAX_PATH) == 0)
{
return false;
}
return wcscmp(NonLocalizable::SplashClassName, className) == 0;
}
bool FancyZonesWindowUtils::IsWindowMaximized(HWND window) noexcept
{
WINDOWPLACEMENT placement{};
if (GetWindowPlacement(window, &placement) &&
placement.showCmd == SW_SHOWMAXIMIZED)
{
return true;
}
return false;
}
bool FancyZonesWindowUtils::HasVisibleOwner(HWND window) noexcept
{
auto owner = GetWindow(window, GW_OWNER);
if (owner == nullptr)
{
return false; // There is no owner at all
}
if (!IsWindowVisible(owner))
{
return false; // Owner is invisible
}
RECT rect;
if (!GetWindowRect(owner, &rect))
{
return true; // Could not get the rect, return true (and filter out the window) just in case
}
// It is enough that the window is zero-sized in one dimension only.
return rect.top != rect.bottom && rect.left != rect.right;
}
bool FancyZonesWindowUtils::IsStandardWindow(HWND window)
{
if (GetAncestor(window, GA_ROOT) != window)
{
return false;
}
auto style = GetWindowLong(window, GWL_STYLE);
auto exStyle = GetWindowLong(window, GWL_EXSTYLE);
bool isToolWindow = (exStyle & WS_EX_TOOLWINDOW) == WS_EX_TOOLWINDOW;
bool isVisible = (style & WS_VISIBLE) == WS_VISIBLE;
if (isToolWindow || !isVisible)
{
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;
}
bool FancyZonesWindowUtils::IsPopupWindow(HWND window) noexcept
{
auto style = GetWindowLong(window, GWL_STYLE);
return ((style & WS_POPUP) == WS_POPUP);
}
bool FancyZonesWindowUtils::HasThickFrameAndMinimizeMaximizeButtons(HWND window) noexcept
{
auto style = GetWindowLong(window, GWL_STYLE);
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(window);
CharUpperBuffW(const_cast<std::wstring&>(processPath).data(), (DWORD)processPath.length());
if (IsExcludedByUser(processPath))
{
return false;
}
if (IsExcludedByDefault(processPath))
{
return false;
}
return true;
}
bool FancyZonesWindowUtils::IsProcessOfWindowElevated(HWND window)
{
DWORD pid = 0;
GetWindowThreadProcessId(window, &pid);
if (!pid)
{
return false;
}
wil::unique_handle hProcess{ OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
FALSE,
pid) };
wil::unique_handle token;
bool elevated = false;
if (OpenProcessToken(hProcess.get(), TOKEN_QUERY, &token))
{
TOKEN_ELEVATION elevation;
DWORD size;
if (GetTokenInformation(token.get(), TokenElevation, &elevation, sizeof(elevation), &size))
{
return elevation.TokenIsElevated != 0;
}
}
return false;
}
bool FancyZonesWindowUtils::IsExcludedByUser(const std::wstring& processPath) noexcept
{
return (find_app_name_in_path(processPath, FancyZonesSettings::settings().excludedAppsArray));
}
bool FancyZonesWindowUtils::IsExcludedByDefault(const std::wstring& processPath) noexcept
{
static std::vector<std::wstring> defaultExcludedFolders = { NonLocalizable::SystemAppsFolder };
if (find_folder_in_path(processPath, defaultExcludedFolders))
{
return true;
}
static std::vector<std::wstring> defaultExcludedApps = { NonLocalizable::PowerToysAppFZEditor, NonLocalizable::CoreWindow, NonLocalizable::SearchUI };
return (find_app_name_in_path(processPath, defaultExcludedApps));
}
void FancyZonesWindowUtils::SwitchToWindow(HWND window) noexcept
{
// Check if the window is minimized
if (IsIconic(window))
{
// Show the window since SetForegroundWindow fails on minimized windows
ShowWindow(window, SW_RESTORE);
}
// This is a hack to bypass the restriction on setting the foreground window
INPUT inputs[1] = { { .type = INPUT_MOUSE } };
SendInput(ARRAYSIZE(inputs), inputs, sizeof(INPUT));
SetForegroundWindow(window);
}
void FancyZonesWindowUtils::SizeWindowToRect(HWND window, RECT rect) noexcept
{
WINDOWPLACEMENT placement{};
::GetWindowPlacement(window, &placement);
// Wait if SW_SHOWMINIMIZED would be removed from window (Issue #1685)
for (int i = 0; i < 5 && (placement.showCmd == SW_SHOWMINIMIZED); ++i)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
::GetWindowPlacement(window, &placement);
}
// Do not restore minimized windows. We change their placement though so they restore to the correct zone.
if ((placement.showCmd != SW_SHOWMINIMIZED) &&
(placement.showCmd != SW_MINIMIZE))
{
placement.showCmd = SW_RESTORE;
}
// Remove maximized show command to make sure window is moved to the correct zone.
if (placement.showCmd == SW_SHOWMAXIMIZED)
{
placement.showCmd = SW_RESTORE;
placement.flags &= ~WPF_RESTORETOMAXIMIZED;
}
ScreenToWorkAreaCoords(window, rect);
placement.rcNormalPosition = rect;
placement.flags |= WPF_ASYNCWINDOWPLACEMENT;
::SetWindowPlacement(window, &placement);
// Do it again, allowing Windows to resize the window and set correct scaling
// This fixes Issue #365
::SetWindowPlacement(window, &placement);
}
void FancyZonesWindowUtils::SaveWindowSizeAndOrigin(HWND window) noexcept
{
HANDLE handle = GetPropW(window, ZonedWindowProperties::PropertyRestoreSizeID);
if (handle)
{
// Size already set, skip
return;
}
RECT rect;
if (GetWindowRect(window, &rect))
{
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
int originX = rect.left;
int originY = rect.top;
DPIAware::InverseConvert(MonitorFromWindow(window, MONITOR_DEFAULTTONULL), width, height);
DPIAware::InverseConvert(MonitorFromWindow(window, MONITOR_DEFAULTTONULL), originX, originY);
std::array<int, 2> windowSizeData = { width, height };
std::array<int, 2> windowOriginData = { originX, originY };
HANDLE rawData;
memcpy(&rawData, windowSizeData.data(), sizeof rawData);
SetPropW(window, ZonedWindowProperties::PropertyRestoreSizeID, rawData);
memcpy(&rawData, windowOriginData.data(), sizeof rawData);
SetPropW(window, ZonedWindowProperties::PropertyRestoreOriginID, rawData);
}
}
void FancyZonesWindowUtils::RestoreWindowSize(HWND window) noexcept
{
auto windowSizeData = GetPropW(window, ZonedWindowProperties::PropertyRestoreSizeID);
if (windowSizeData)
{
std::array<int, 2> windowSize;
memcpy(windowSize.data(), &windowSizeData, sizeof windowSize);
// {width, height}
DPIAware::Convert(MonitorFromWindow(window, MONITOR_DEFAULTTONULL), windowSize[0], windowSize[1]);
RECT rect;
if (GetWindowRect(window, &rect))
{
rect.right = rect.left + windowSize[0];
rect.bottom = rect.top + windowSize[1];
SizeWindowToRect(window, rect);
}
::RemoveProp(window, ZonedWindowProperties::PropertyRestoreSizeID);
}
}
void FancyZonesWindowUtils::RestoreWindowOrigin(HWND window) noexcept
{
auto windowOriginData = GetPropW(window, ZonedWindowProperties::PropertyRestoreOriginID);
if (windowOriginData)
{
std::array<int, 2> windowOrigin;
memcpy(windowOrigin.data(), &windowOriginData, sizeof windowOrigin);
// {width, height}
DPIAware::Convert(MonitorFromWindow(window, MONITOR_DEFAULTTONULL), windowOrigin[0], windowOrigin[1]);
RECT rect;
if (GetWindowRect(window, &rect))
{
int xOffset = windowOrigin[0] - rect.left;
int yOffset = windowOrigin[1] - rect.top;
rect.left += xOffset;
rect.right += xOffset;
rect.top += yOffset;
rect.bottom += yOffset;
SizeWindowToRect(window, rect);
}
::RemoveProp(window, ZonedWindowProperties::PropertyRestoreOriginID);
}
}
RECT FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(HWND window, RECT rect, HWND windowOfRect) noexcept
{
RECT newWindowRect = rect;
RECT windowRect{};
::GetWindowRect(window, &windowRect);
// Take care of borders
RECT frameRect{};
if (SUCCEEDED(DwmGetWindowAttribute(window, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRect, sizeof(frameRect))))
{
LONG leftMargin = frameRect.left - windowRect.left;
LONG rightMargin = frameRect.right - windowRect.right;
LONG bottomMargin = frameRect.bottom - windowRect.bottom;
newWindowRect.left -= leftMargin;
newWindowRect.right -= rightMargin;
newWindowRect.bottom -= bottomMargin;
}
// Take care of windows that cannot be resized
if ((::GetWindowLong(window, GWL_STYLE) & WS_SIZEBOX) == 0)
{
newWindowRect.right = newWindowRect.left + (windowRect.right - windowRect.left);
newWindowRect.bottom = newWindowRect.top + (windowRect.bottom - windowRect.top);
}
// Convert to screen coordinates
MapWindowRect(windowOfRect, nullptr, &newWindowRect);
return newWindowRect;
}
void FancyZonesWindowUtils::MakeWindowTransparent(HWND window)
{
int const pos = -GetSystemMetrics(SM_CXVIRTUALSCREEN) - 8;
if (wil::unique_hrgn hrgn{ CreateRectRgn(pos, 0, (pos + 1), 1) })
{
DWM_BLURBEHIND bh = { DWM_BB_ENABLE | DWM_BB_BLURREGION, TRUE, hrgn.get(), FALSE };
DwmEnableBlurBehindWindow(window, &bh);
}
}

View File

@ -0,0 +1,30 @@
#pragma once
#include <Windows.h>
#include "gdiplus.h"
#include <string>
#include <vector>
namespace FancyZonesWindowUtils
{
bool IsSplashScreen(HWND window);
bool IsWindowMaximized(HWND window) noexcept;
bool HasVisibleOwner(HWND window) noexcept;
bool IsStandardWindow(HWND window);
bool IsPopupWindow(HWND window) noexcept;
bool HasThickFrameAndMinimizeMaximizeButtons(HWND window) noexcept;
bool IsCandidateForZoning(HWND window);
bool IsProcessOfWindowElevated(HWND window); // If HWND is already dead, we assume it wasn't elevated
bool IsExcludedByUser(const std::wstring& processPath) noexcept;
bool IsExcludedByDefault(const std::wstring& processPath) noexcept;
void SwitchToWindow(HWND window) noexcept;
void SizeWindowToRect(HWND window, RECT rect) noexcept; // Parameter rect must be in screen coordinates (e.g. obtained from GetWindowRect)
void SaveWindowSizeAndOrigin(HWND window) noexcept;
void RestoreWindowSize(HWND window) noexcept;
void RestoreWindowOrigin(HWND window) noexcept;
void MakeWindowTransparent(HWND window);
RECT AdjustRectForSizeWindowToRect(HWND window, RECT rect, HWND windowOfRect) noexcept; // Parameter rect is in windowOfRect coordinates
}

View File

@ -7,11 +7,12 @@
#include "FancyZonesData/AppliedLayouts.h" #include "FancyZonesData/AppliedLayouts.h"
#include "FancyZonesData/AppZoneHistory.h" #include "FancyZonesData/AppZoneHistory.h"
#include "FancyZonesDataTypes.h" #include "FancyZonesDataTypes.h"
#include "SettingsObserver.h"
#include "ZonesOverlay.h" #include "ZonesOverlay.h"
#include "trace.h" #include "trace.h"
#include "util.h"
#include "on_thread_executor.h" #include "on_thread_executor.h"
#include "Settings.h" #include "Settings.h"
#include <FancyZonesLib/WindowUtils.h>
#include <ShellScalingApi.h> #include <ShellScalingApi.h>
#include <mutex> #include <mutex>
@ -65,7 +66,7 @@ namespace
{ {
HWND window = CreateWindowExW(WS_EX_TOOLWINDOW, NonLocalizable::ToolWindowClassName, L"", WS_POPUP, position.left(), position.top(), position.width(), position.height(), nullptr, nullptr, hinstance, owner); HWND window = CreateWindowExW(WS_EX_TOOLWINDOW, NonLocalizable::ToolWindowClassName, L"", WS_POPUP, position.left(), position.top(), position.width(), position.height(), nullptr, nullptr, hinstance, owner);
Logger::info("Creating new ZonesOverlay window, hWnd = {}", (void*)window); Logger::info("Creating new ZonesOverlay window, hWnd = {}", (void*)window);
MakeWindowTransparent(window); FancyZonesWindowUtils::MakeWindowTransparent(window);
// According to ShowWindow docs, we must call it with SW_SHOWNORMAL the first time // According to ShowWindow docs, we must call it with SW_SHOWNORMAL the first time
ShowWindow(window, SW_SHOWNORMAL); ShowWindow(window, SW_SHOWNORMAL);
@ -110,7 +111,7 @@ public:
WorkArea(HINSTANCE hinstance); WorkArea(HINSTANCE hinstance);
~WorkArea(); ~WorkArea();
bool Init(HINSTANCE hinstance, HMONITOR monitor, const FancyZonesDataTypes::DeviceIdData& uniqueId, const FancyZonesDataTypes::DeviceIdData& parentUniqueId, const ZoneColors& zoneColors, OverlappingZonesAlgorithm overlappingAlgorithm, const bool showZoneText); bool Init(HINSTANCE hinstance, HMONITOR monitor, const FancyZonesDataTypes::DeviceIdData& uniqueId, const FancyZonesDataTypes::DeviceIdData& parentUniqueId);
IFACEMETHODIMP MoveSizeEnter(HWND window) noexcept; IFACEMETHODIMP MoveSizeEnter(HWND window) noexcept;
IFACEMETHODIMP MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool selectManyZones) noexcept; IFACEMETHODIMP MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool selectManyZones) noexcept;
@ -145,10 +146,6 @@ public:
ClearSelectedZones() noexcept; ClearSelectedZones() noexcept;
IFACEMETHODIMP_(void) IFACEMETHODIMP_(void)
FlashZones() noexcept; FlashZones() noexcept;
IFACEMETHODIMP_(void)
SetZoneColors(const ZoneColors& colors) noexcept;
IFACEMETHODIMP_(void)
SetOverlappingZonesAlgorithm(OverlappingZonesAlgorithm overlappingAlgorithm) noexcept;
protected: protected:
static LRESULT CALLBACK s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept; static LRESULT CALLBACK s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept;
@ -171,9 +168,6 @@ private:
WPARAM m_keyLast{}; WPARAM m_keyLast{};
size_t m_keyCycle{}; size_t m_keyCycle{};
std::unique_ptr<ZonesOverlay> m_zonesOverlay; std::unique_ptr<ZonesOverlay> m_zonesOverlay;
ZoneColors m_zoneColors;
OverlappingZonesAlgorithm m_overlappingAlgorithm;
bool m_showZoneText;
}; };
WorkArea::WorkArea(HINSTANCE hinstance) WorkArea::WorkArea(HINSTANCE hinstance)
@ -192,12 +186,8 @@ WorkArea::~WorkArea()
windowPool.FreeZonesOverlayWindow(m_window); windowPool.FreeZonesOverlayWindow(m_window);
} }
bool WorkArea::Init(HINSTANCE hinstance, HMONITOR monitor, const FancyZonesDataTypes::DeviceIdData& uniqueId, const FancyZonesDataTypes::DeviceIdData& parentUniqueId, const ZoneColors& zoneColors, OverlappingZonesAlgorithm overlappingAlgorithm, const bool showZoneText) bool WorkArea::Init(HINSTANCE hinstance, HMONITOR monitor, const FancyZonesDataTypes::DeviceIdData& uniqueId, const FancyZonesDataTypes::DeviceIdData& parentUniqueId)
{ {
m_zoneColors = zoneColors;
m_overlappingAlgorithm = overlappingAlgorithm;
m_showZoneText = showZoneText;
Rect workAreaRect; Rect workAreaRect;
m_monitor = monitor; m_monitor = monitor;
if (monitor) if (monitor)
@ -278,7 +268,7 @@ IFACEMETHODIMP WorkArea::MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled,
if (redraw) if (redraw)
{ {
m_zonesOverlay->DrawActiveZoneSet(m_zoneSet->GetZones(), m_highlightZone, m_zoneColors, m_showZoneText); m_zonesOverlay->DrawActiveZoneSet(m_zoneSet->GetZones(), m_highlightZone, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber);
} }
return S_OK; return S_OK;
@ -297,7 +287,7 @@ IFACEMETHODIMP WorkArea::MoveSizeEnd(HWND window, POINT const& ptScreen) noexcep
MapWindowPoints(nullptr, m_window, &ptClient, 1); MapWindowPoints(nullptr, m_window, &ptClient, 1);
m_zoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, m_highlightZone); m_zoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, m_highlightZone);
if (FancyZonesUtils::HasNoVisibleOwner(window)) if (!FancyZonesWindowUtils::HasVisibleOwner(window))
{ {
SaveWindowProcessToZoneIndex(window); SaveWindowProcessToZoneIndex(window);
} }
@ -331,7 +321,7 @@ WorkArea::MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool
{ {
if (m_zoneSet->MoveWindowIntoZoneByDirectionAndIndex(window, m_window, vkCode, cycle)) if (m_zoneSet->MoveWindowIntoZoneByDirectionAndIndex(window, m_window, vkCode, cycle))
{ {
if (FancyZonesUtils::HasNoVisibleOwner(window)) if (!FancyZonesWindowUtils::HasVisibleOwner(window))
{ {
SaveWindowProcessToZoneIndex(window); SaveWindowProcessToZoneIndex(window);
} }
@ -408,7 +398,7 @@ WorkArea::ShowZonesOverlay() noexcept
if (m_window) if (m_window)
{ {
SetAsTopmostWindow(); SetAsTopmostWindow();
m_zonesOverlay->DrawActiveZoneSet(m_zoneSet->GetZones(), m_highlightZone, m_zoneColors, m_showZoneText); m_zonesOverlay->DrawActiveZoneSet(m_zoneSet->GetZones(), m_highlightZone, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber);
m_zonesOverlay->Show(); m_zonesOverlay->Show();
} }
} }
@ -428,11 +418,11 @@ WorkArea::HideZonesOverlay() noexcept
IFACEMETHODIMP_(void) IFACEMETHODIMP_(void)
WorkArea::UpdateActiveZoneSet() noexcept WorkArea::UpdateActiveZoneSet() noexcept
{ {
CalculateZoneSet(m_overlappingAlgorithm); CalculateZoneSet(FancyZonesSettings::settings().overlappingZonesAlgorithm);
if (m_window) if (m_window)
{ {
m_highlightZone.clear(); m_highlightZone.clear();
m_zonesOverlay->DrawActiveZoneSet(m_zoneSet->GetZones(), m_highlightZone, m_zoneColors, m_showZoneText); m_zonesOverlay->DrawActiveZoneSet(m_zoneSet->GetZones(), m_highlightZone, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber);
} }
} }
@ -451,7 +441,7 @@ WorkArea::ClearSelectedZones() noexcept
if (m_highlightZone.size()) if (m_highlightZone.size())
{ {
m_highlightZone.clear(); m_highlightZone.clear();
m_zonesOverlay->DrawActiveZoneSet(m_zoneSet->GetZones(), m_highlightZone, m_zoneColors, m_showZoneText); m_zonesOverlay->DrawActiveZoneSet(m_zoneSet->GetZones(), m_highlightZone, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber);
} }
} }
@ -461,24 +451,11 @@ WorkArea::FlashZones() noexcept
if (m_window) if (m_window)
{ {
SetAsTopmostWindow(); SetAsTopmostWindow();
m_zonesOverlay->DrawActiveZoneSet(m_zoneSet->GetZones(), {}, m_zoneColors, m_showZoneText); m_zonesOverlay->DrawActiveZoneSet(m_zoneSet->GetZones(), {}, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber);
m_zonesOverlay->Flash(); m_zonesOverlay->Flash();
} }
} }
IFACEMETHODIMP_(void)
WorkArea::SetZoneColors(const ZoneColors& colors) noexcept
{
m_zoneColors = colors;
}
IFACEMETHODIMP_(void)
WorkArea::SetOverlappingZonesAlgorithm(OverlappingZonesAlgorithm overlappingAlgorithm) noexcept
{
m_overlappingAlgorithm = overlappingAlgorithm;
}
#pragma region private #pragma region private
void WorkArea::InitializeZoneSets(const FancyZonesDataTypes::DeviceIdData& parentUniqueId) noexcept void WorkArea::InitializeZoneSets(const FancyZonesDataTypes::DeviceIdData& parentUniqueId) noexcept
@ -502,7 +479,7 @@ void WorkArea::InitializeZoneSets(const FancyZonesDataTypes::DeviceIdData& paren
} }
} }
CalculateZoneSet(m_overlappingAlgorithm); CalculateZoneSet(FancyZonesSettings::settings().overlappingZonesAlgorithm);
} }
void WorkArea::CalculateZoneSet(OverlappingZonesAlgorithm overlappingAlgorithm) noexcept void WorkArea::CalculateZoneSet(OverlappingZonesAlgorithm overlappingAlgorithm) noexcept
@ -631,10 +608,10 @@ LRESULT CALLBACK WorkArea::s_WndProc(HWND window, UINT message, WPARAM wparam, L
DefWindowProc(window, message, wparam, lparam); DefWindowProc(window, message, wparam, lparam);
} }
winrt::com_ptr<IWorkArea> MakeWorkArea(HINSTANCE hinstance, HMONITOR monitor, const FancyZonesDataTypes::DeviceIdData& uniqueId, const FancyZonesDataTypes::DeviceIdData& parentUniqueId, const ZoneColors& zoneColors, OverlappingZonesAlgorithm overlappingAlgorithm, const bool showZoneText) noexcept winrt::com_ptr<IWorkArea> MakeWorkArea(HINSTANCE hinstance, HMONITOR monitor, const FancyZonesDataTypes::DeviceIdData& uniqueId, const FancyZonesDataTypes::DeviceIdData& parentUniqueId) noexcept
{ {
auto self = winrt::make_self<WorkArea>(hinstance); auto self = winrt::make_self<WorkArea>(hinstance);
if (self->Init(hinstance, monitor, uniqueId, parentUniqueId, zoneColors, overlappingAlgorithm, showZoneText)) if (self->Init(hinstance, monitor, uniqueId, parentUniqueId))
{ {
return self; return self;
} }

View File

@ -1,7 +1,6 @@
#pragma once #pragma once
#include "FancyZones.h" #include "FancyZones.h"
#include "FancyZonesLib/ZoneSet.h" #include "FancyZonesLib/ZoneSet.h"
#include "FancyZonesLib/ZoneColors.h"
#include "FancyZonesLib/FancyZonesDataTypes.h" #include "FancyZonesLib/FancyZonesDataTypes.h"
/** /**
@ -135,14 +134,6 @@ interface __declspec(uuid("{7F017528-8110-4FB3-BE41-F472969C2560}")) IWorkArea :
* Display the layout on the screen and then hide it. * Display the layout on the screen and then hide it.
*/ */
IFACEMETHOD_(void, FlashZones)() = 0; IFACEMETHOD_(void, FlashZones)() = 0;
/*
* Set zone colors
*/
IFACEMETHOD_(void, SetZoneColors)(const ZoneColors& colors) = 0;
/*
* Set overlapping algorithm
*/
IFACEMETHOD_(void, SetOverlappingZonesAlgorithm)(OverlappingZonesAlgorithm overlappingAlgorithm) = 0;
}; };
winrt::com_ptr<IWorkArea> MakeWorkArea(HINSTANCE hinstance, HMONITOR monitor, const FancyZonesDataTypes::DeviceIdData& uniqueId, const FancyZonesDataTypes::DeviceIdData& parentUniqueId, const ZoneColors& zoneColors, OverlappingZonesAlgorithm overlappingAlgorithm, const bool showZoneText) noexcept; winrt::com_ptr<IWorkArea> MakeWorkArea(HINSTANCE hinstance, HMONITOR monitor, const FancyZonesDataTypes::DeviceIdData& uniqueId, const FancyZonesDataTypes::DeviceIdData& parentUniqueId) noexcept;

View File

@ -1,11 +0,0 @@
#pragma once
#include <windef.h>
struct ZoneColors
{
COLORREF primaryColor;
COLORREF borderColor;
COLORREF highlightColor;
COLORREF numberColor;
int highlightOpacity;
};

View File

@ -7,7 +7,8 @@
#include "FancyZonesWindowProperties.h" #include "FancyZonesWindowProperties.h"
#include "Settings.h" #include "Settings.h"
#include "Zone.h" #include "Zone.h"
#include "util.h" #include <FancyZonesLib/util.h>
#include <FancyZonesLib/WindowUtils.h>
#include <common/logger/logger.h> #include <common/logger/logger.h>
#include <common/display/dpi_aware.h> #include <common/display/dpi_aware.h>
@ -344,10 +345,10 @@ ZoneSet::MoveWindowIntoZoneByIndexSet(HWND window, HWND workAreaWindow, const Zo
{ {
if (!suppressMove) if (!suppressMove)
{ {
SaveWindowSizeAndOrigin(window); FancyZonesWindowUtils::SaveWindowSizeAndOrigin(window);
auto rect = AdjustRectForSizeWindowToRect(window, size, workAreaWindow); auto rect = FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(window, size, workAreaWindow);
SizeWindowToRect(window, rect); FancyZonesWindowUtils::SizeWindowToRect(window, rect);
} }
FancyZonesWindowProperties::StampZoneIndexProperty(window, indexSet); FancyZonesWindowProperties::StampZoneIndexProperty(window, indexSet);
@ -597,7 +598,7 @@ ZoneSet::CycleTabs(HWND window, bool reverse) noexcept
continue; continue;
} }
SwitchToWindow(next); FancyZonesWindowUtils::SwitchToWindow(next);
break; break;
} }

View File

@ -282,7 +282,7 @@ void ZonesOverlay::Flash()
void ZonesOverlay::DrawActiveZoneSet(const IZoneSet::ZonesMap& zones, void ZonesOverlay::DrawActiveZoneSet(const IZoneSet::ZonesMap& zones,
const ZoneIndexSet& highlightZones, const ZoneIndexSet& highlightZones,
const ZoneColors& colors, const Colors::ZoneColors& colors,
const bool showZoneText) const bool showZoneText)
{ {
_TRACER_; _TRACER_;

View File

@ -11,7 +11,7 @@
#include "Zone.h" #include "Zone.h"
#include "ZoneSet.h" #include "ZoneSet.h"
#include "FancyZones.h" #include "FancyZones.h"
#include "ZoneColors.h" #include "Colors.h"
class ZonesOverlay class ZonesOverlay
{ {
@ -68,6 +68,6 @@ public:
void Flash(); void Flash();
void DrawActiveZoneSet(const IZoneSet::ZonesMap& zones, void DrawActiveZoneSet(const IZoneSet::ZonesMap& zones,
const ZoneIndexSet& highlightZones, const ZoneIndexSet& highlightZones,
const ZoneColors& colors, const Colors::ZoneColors& colors,
const bool showZoneText); const bool showZoneText);
}; };

View File

@ -1,6 +1,5 @@
#include "pch.h" #include "pch.h"
#include "util.h" #include "util.h"
#include "Settings.h"
#include <common/display/dpi_aware.h> #include <common/display/dpi_aware.h>
#include <common/utils/process_path.h> #include <common/utils/process_path.h>
@ -9,35 +8,10 @@
#include <array> #include <array>
#include <complex> #include <complex>
#include <wil/Resource.h>
#include <fancyzones/FancyZonesLib/FancyZonesDataTypes.h> #include <common/display/dpi_aware.h>
#include <fancyzones/FancyZonesLib/FancyZonesWindowProperties.h>
// Non-Localizable strings #include <FancyZonesLib/FancyZonesWindowProperties.h>
namespace NonLocalizable
{
const wchar_t PowerToysAppFZEditor[] = L"POWERTOYS.FANCYZONESEDITOR.EXE";
const wchar_t SplashClassName[] = L"MsoSplash";
}
namespace
{
bool IsZonableByProcessPath(const std::wstring& processPath, const std::vector<std::wstring>& excludedApps)
{
// Filter out user specified apps
CharUpperBuffW(const_cast<std::wstring&>(processPath).data(), (DWORD)processPath.length());
if (find_app_name_in_path(processPath, excludedApps))
{
return false;
}
if (find_app_name_in_path(processPath, { NonLocalizable::PowerToysAppFZEditor }))
{
return false;
}
return true;
}
}
namespace FancyZonesUtils namespace FancyZonesUtils
{ {
@ -200,337 +174,6 @@ namespace FancyZonesUtils
monitorInfo = std::move(sortedMonitorInfo); monitorInfo = std::move(sortedMonitorInfo);
} }
BOOL CALLBACK saveDisplayToVector(HMONITOR monitor, HDC hdc, LPRECT rect, LPARAM data)
{
reinterpret_cast<std::vector<HMONITOR>*>(data)->emplace_back(monitor);
return true;
}
bool allMonitorsHaveSameDpiScaling()
{
std::vector<HMONITOR> monitors;
EnumDisplayMonitors(NULL, NULL, saveDisplayToVector, reinterpret_cast<LPARAM>(&monitors));
if (monitors.size() < 2)
{
return true;
}
UINT firstMonitorDpiX;
UINT firstMonitorDpiY;
if (S_OK != GetDpiForMonitor(monitors[0], MDT_EFFECTIVE_DPI, &firstMonitorDpiX, &firstMonitorDpiY))
{
return false;
}
for (int i = 1; i < monitors.size(); i++)
{
UINT iteratedMonitorDpiX;
UINT iteratedMonitorDpiY;
if (S_OK != GetDpiForMonitor(monitors[i], MDT_EFFECTIVE_DPI, &iteratedMonitorDpiX, &iteratedMonitorDpiY) ||
iteratedMonitorDpiX != firstMonitorDpiX)
{
return false;
}
}
return true;
}
void ScreenToWorkAreaCoords(HWND window, RECT& rect)
{
// First, find the correct monitor. The monitor cannot be found using the given rect itself, we must first
// translate it to relative workspace coordinates.
HMONITOR monitor = MonitorFromRect(&rect, MONITOR_DEFAULTTOPRIMARY);
MONITORINFOEXW monitorInfo{ sizeof(MONITORINFOEXW) };
GetMonitorInfoW(monitor, &monitorInfo);
auto xOffset = monitorInfo.rcWork.left - monitorInfo.rcMonitor.left;
auto yOffset = monitorInfo.rcWork.top - monitorInfo.rcMonitor.top;
auto referenceRect = rect;
referenceRect.left -= xOffset;
referenceRect.right -= xOffset;
referenceRect.top -= yOffset;
referenceRect.bottom -= yOffset;
// Now, this rect should be used to determine the monitor and thus taskbar size. This fixes
// scenarios where the zone lies approximately between two monitors, and the taskbar is on the left.
monitor = MonitorFromRect(&referenceRect, MONITOR_DEFAULTTOPRIMARY);
GetMonitorInfoW(monitor, &monitorInfo);
xOffset = monitorInfo.rcWork.left - monitorInfo.rcMonitor.left;
yOffset = monitorInfo.rcWork.top - monitorInfo.rcMonitor.top;
rect.left -= xOffset;
rect.right -= xOffset;
rect.top -= yOffset;
rect.bottom -= yOffset;
const auto level = DPIAware::GetAwarenessLevel(GetWindowDpiAwarenessContext(window));
const bool accountForUnawareness = level < DPIAware::PER_MONITOR_AWARE;
if (accountForUnawareness && !allMonitorsHaveSameDpiScaling())
{
rect.left = max(monitorInfo.rcMonitor.left, rect.left);
rect.right = min(monitorInfo.rcMonitor.right - xOffset, rect.right);
rect.top = max(monitorInfo.rcMonitor.top, rect.top);
rect.bottom = min(monitorInfo.rcMonitor.bottom - yOffset, rect.bottom);
}
}
RECT AdjustRectForSizeWindowToRect(HWND window, RECT rect, HWND windowOfRect) noexcept
{
RECT newWindowRect = rect;
RECT windowRect{};
::GetWindowRect(window, &windowRect);
// Take care of borders
RECT frameRect{};
if (SUCCEEDED(DwmGetWindowAttribute(window, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRect, sizeof(frameRect))))
{
LONG leftMargin = frameRect.left - windowRect.left;
LONG rightMargin = frameRect.right - windowRect.right;
LONG bottomMargin = frameRect.bottom - windowRect.bottom;
newWindowRect.left -= leftMargin;
newWindowRect.right -= rightMargin;
newWindowRect.bottom -= bottomMargin;
}
// Take care of windows that cannot be resized
if ((::GetWindowLong(window, GWL_STYLE) & WS_SIZEBOX) == 0)
{
newWindowRect.right = newWindowRect.left + (windowRect.right - windowRect.left);
newWindowRect.bottom = newWindowRect.top + (windowRect.bottom - windowRect.top);
}
// Convert to screen coordinates
MapWindowRect(windowOfRect, nullptr, &newWindowRect);
return newWindowRect;
}
void SizeWindowToRect(HWND window, RECT rect) noexcept
{
WINDOWPLACEMENT placement{};
::GetWindowPlacement(window, &placement);
// Wait if SW_SHOWMINIMIZED would be removed from window (Issue #1685)
for (int i = 0; i < 5 && (placement.showCmd == SW_SHOWMINIMIZED); ++i)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
::GetWindowPlacement(window, &placement);
}
// Do not restore minimized windows. We change their placement though so they restore to the correct zone.
if ((placement.showCmd != SW_SHOWMINIMIZED) &&
(placement.showCmd != SW_MINIMIZE))
{
placement.showCmd = SW_RESTORE;
}
// Remove maximized show command to make sure window is moved to the correct zone.
if (placement.showCmd == SW_SHOWMAXIMIZED)
{
placement.showCmd = SW_RESTORE;
placement.flags &= ~WPF_RESTORETOMAXIMIZED;
}
ScreenToWorkAreaCoords(window, rect);
placement.rcNormalPosition = rect;
placement.flags |= WPF_ASYNCWINDOWPLACEMENT;
::SetWindowPlacement(window, &placement);
// Do it again, allowing Windows to resize the window and set correct scaling
// This fixes Issue #365
::SetWindowPlacement(window, &placement);
}
void SwitchToWindow(HWND window) noexcept
{
// Check if the window is minimized
if (IsIconic(window))
{
// Show the window since SetForegroundWindow fails on minimized windows
ShowWindow(window, SW_RESTORE);
}
// This is a hack to bypass the restriction on setting the foreground window
INPUT inputs[1] = { { .type = INPUT_MOUSE } };
SendInput(ARRAYSIZE(inputs), inputs, sizeof(INPUT));
SetForegroundWindow(window);
}
bool HasNoVisibleOwner(HWND window) noexcept
{
auto owner = GetWindow(window, GW_OWNER);
if (owner == nullptr)
{
return true; // There is no owner at all
}
if (!IsWindowVisible(owner))
{
return true; // Owner is invisible
}
RECT rect;
if (!GetWindowRect(owner, &rect))
{
return false; // Could not get the rect, return true (and filter out the window) just in case
}
// It is enough that the window is zero-sized in one dimension only.
return rect.top == rect.bottom || rect.left == rect.right;
}
bool IsStandardWindow(HWND window)
{
if (GetAncestor(window, GA_ROOT) != window || !IsWindowVisible(window))
{
return false;
}
auto style = GetWindowLong(window, GWL_STYLE);
auto exStyle = GetWindowLong(window, GWL_EXSTYLE);
// WS_POPUP need to have a border or minimize/maximize buttons,
// otherwise the window is "not interesting"
if ((style & WS_POPUP) == WS_POPUP &&
(style & WS_THICKFRAME) == 0 &&
(style & WS_MINIMIZEBOX) == 0 &&
(style & WS_MAXIMIZEBOX) == 0)
{
return false;
}
if ((style & WS_CHILD) == WS_CHILD ||
(style & WS_DISABLED) == WS_DISABLED ||
(exStyle & WS_EX_TOOLWINDOW) == WS_EX_TOOLWINDOW ||
(exStyle & WS_EX_NOACTIVATE) == WS_EX_NOACTIVATE)
{
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;
}
auto process_path = get_process_path(window);
// Check for Cortana:
if (strcmp(class_name.data(), "Windows.UI.Core.CoreWindow") == 0 &&
process_path.ends_with(L"SearchUI.exe"))
{
return false;
}
return true;
}
bool IsCandidateForZoning(HWND window, const std::vector<std::wstring>& excludedApps) noexcept
{
auto zonable = IsStandardWindow(window) && HasNoVisibleOwner(window);
if (!zonable)
{
return false;
}
return IsZonableByProcessPath(get_process_path(window), excludedApps);
}
bool IsWindowMaximized(HWND window) noexcept
{
WINDOWPLACEMENT placement{};
if (GetWindowPlacement(window, &placement) &&
placement.showCmd == SW_SHOWMAXIMIZED)
{
return true;
}
return false;
}
void SaveWindowSizeAndOrigin(HWND window) noexcept
{
HANDLE handle = GetPropW(window, ZonedWindowProperties::PropertyRestoreSizeID);
if (handle)
{
// Size already set, skip
return;
}
RECT rect;
if (GetWindowRect(window, &rect))
{
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
int originX = rect.left;
int originY = rect.top;
DPIAware::InverseConvert(MonitorFromWindow(window, MONITOR_DEFAULTTONULL), width, height);
DPIAware::InverseConvert(MonitorFromWindow(window, MONITOR_DEFAULTTONULL), originX, originY);
std::array<int, 2> windowSizeData = { width, height };
std::array<int, 2> windowOriginData = { originX, originY };
HANDLE rawData;
memcpy(&rawData, windowSizeData.data(), sizeof rawData);
SetPropW(window, ZonedWindowProperties::PropertyRestoreSizeID, rawData);
memcpy(&rawData, windowOriginData.data(), sizeof rawData);
SetPropW(window, ZonedWindowProperties::PropertyRestoreOriginID, rawData);
}
}
void RestoreWindowSize(HWND window) noexcept
{
auto windowSizeData = GetPropW(window, ZonedWindowProperties::PropertyRestoreSizeID);
if (windowSizeData)
{
std::array<int, 2> windowSize;
memcpy(windowSize.data(), &windowSizeData, sizeof windowSize);
// {width, height}
DPIAware::Convert(MonitorFromWindow(window, MONITOR_DEFAULTTONULL), windowSize[0], windowSize[1]);
RECT rect;
if (GetWindowRect(window, &rect))
{
rect.right = rect.left + windowSize[0];
rect.bottom = rect.top + windowSize[1];
SizeWindowToRect(window, rect);
}
::RemoveProp(window, ZonedWindowProperties::PropertyRestoreSizeID);
}
}
void RestoreWindowOrigin(HWND window) noexcept
{
auto windowOriginData = GetPropW(window, ZonedWindowProperties::PropertyRestoreOriginID);
if (windowOriginData)
{
std::array<int, 2> windowOrigin;
memcpy(windowOrigin.data(), &windowOriginData, sizeof windowOrigin);
// {width, height}
DPIAware::Convert(MonitorFromWindow(window, MONITOR_DEFAULTTONULL), windowOrigin[0], windowOrigin[1]);
RECT rect;
if (GetWindowRect(window, &rect))
{
int xOffset = windowOrigin[0] - rect.left;
int yOffset = windowOrigin[1] - rect.top;
rect.left += xOffset;
rect.right += xOffset;
rect.top += yOffset;
rect.bottom += yOffset;
SizeWindowToRect(window, rect);
}
::RemoveProp(window, ZonedWindowProperties::PropertyRestoreOriginID);
}
}
bool IsValidGuid(const std::wstring& str) bool IsValidGuid(const std::wstring& str)
{ {
GUID id; GUID id;
@ -716,44 +359,4 @@ namespace FancyZonesUtils
return windowRect; return windowRect;
} }
bool IsProcessOfWindowElevated(HWND window)
{
DWORD pid = 0;
GetWindowThreadProcessId(window, &pid);
if (!pid)
{
return false;
}
wil::unique_handle hProcess{ OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
FALSE,
pid) };
wil::unique_handle token;
bool elevated = false;
if (OpenProcessToken(hProcess.get(), TOKEN_QUERY, &token))
{
TOKEN_ELEVATION elevation;
DWORD size;
if (GetTokenInformation(token.get(), TokenElevation, &elevation, sizeof(elevation), &size))
{
return elevation.TokenIsElevated != 0;
}
}
return false;
}
bool IsSplashScreen(HWND window)
{
wchar_t className[MAX_PATH];
if (GetClassName(window, className, MAX_PATH) == 0)
{
return false;
}
return wcscmp(NonLocalizable::SplashClassName, className) == 0;
}
} }

View File

@ -1,13 +1,7 @@
#pragma once #pragma once
#include "gdiplus.h"
#include <common/utils/string_utils.h> #include <common/utils/string_utils.h>
namespace FancyZonesDataTypes
{
struct DeviceIdData;
}
namespace FancyZonesUtils namespace FancyZonesUtils
{ {
struct Rect struct Rect
@ -40,16 +34,6 @@ namespace FancyZonesUtils
RECT m_rect{}; RECT m_rect{};
}; };
inline void MakeWindowTransparent(HWND window)
{
int const pos = -GetSystemMetrics(SM_CXVIRTUALSCREEN) - 8;
if (wil::unique_hrgn hrgn{ CreateRectRgn(pos, 0, (pos + 1), 1) })
{
DWM_BLURBEHIND bh = { DWM_BB_ENABLE | DWM_BB_BLURREGION, TRUE, hrgn.get(), FALSE };
DwmEnableBlurBehindWindow(window, &bh);
}
}
inline void InitRGB(_Out_ RGBQUAD* quad, BYTE alpha, COLORREF color) inline void InitRGB(_Out_ RGBQUAD* quad, BYTE alpha, COLORREF color)
{ {
ZeroMemory(quad, sizeof(*quad)); ZeroMemory(quad, sizeof(*quad));
@ -189,23 +173,6 @@ namespace FancyZonesUtils
UINT GetDpiForMonitor(HMONITOR monitor) noexcept; UINT GetDpiForMonitor(HMONITOR monitor) noexcept;
void OrderMonitors(std::vector<std::pair<HMONITOR, RECT>>& monitorInfo); void OrderMonitors(std::vector<std::pair<HMONITOR, RECT>>& monitorInfo);
// Parameter rect is in windowOfRect coordinates
RECT AdjustRectForSizeWindowToRect(HWND window, RECT rect, HWND windowOfRect) noexcept;
// Parameter rect must be in screen coordinates (e.g. obtained from GetWindowRect)
void SizeWindowToRect(HWND window, RECT rect) noexcept;
void SwitchToWindow(HWND window) noexcept;
bool HasNoVisibleOwner(HWND window) noexcept;
bool IsStandardWindow(HWND window);
bool IsCandidateForZoning(HWND window, const std::vector<std::wstring>& excludedApps) noexcept;
bool IsWindowMaximized(HWND window) noexcept;
void SaveWindowSizeAndOrigin(HWND window) noexcept;
void RestoreWindowSize(HWND window) noexcept;
void RestoreWindowOrigin(HWND window) noexcept;
bool IsValidGuid(const std::wstring& str); bool IsValidGuid(const std::wstring& str);
std::optional<GUID> GuidFromString(const std::wstring& str) noexcept; std::optional<GUID> GuidFromString(const std::wstring& str) noexcept;
std::optional<std::wstring> GuidToString(const GUID& guid) noexcept; std::optional<std::wstring> GuidToString(const GUID& guid) noexcept;
@ -215,10 +182,5 @@ namespace FancyZonesUtils
std::wstring TrimDeviceId(const std::wstring& deviceId); std::wstring TrimDeviceId(const std::wstring& deviceId);
RECT PrepareRectForCycling(RECT windowRect, RECT workAreaRect, DWORD vkCode) noexcept; RECT PrepareRectForCycling(RECT windowRect, RECT workAreaRect, DWORD vkCode) noexcept;
size_t ChooseNextZoneByPosition(DWORD vkCode, RECT windowRect, const std::vector<RECT>& zoneRects) noexcept; size_t ChooseNextZoneByPosition(DWORD vkCode, RECT windowRect, const std::vector<RECT>& zoneRects) noexcept;
// If HWND is already dead, we assume it wasn't elevated
bool IsProcessOfWindowElevated(HWND window);
bool IsSplashScreen(HWND window);
} }

View File

@ -55,14 +55,13 @@ public:
// These are the settings shown on the settings page along with their current values. // These are the settings shown on the settings page along with their current values.
virtual bool get_config(_Out_ PWSTR buffer, _Out_ int* buffer_size) override virtual bool get_config(_Out_ PWSTR buffer, _Out_ int* buffer_size) override
{ {
return m_settings->GetConfig(buffer, buffer_size); return false;
} }
// Passes JSON with the configuration settings for the powertoy. // Passes JSON with the configuration settings for the powertoy.
// This is called when the user hits Save on the settings page. // This is called when the user hits Save on the settings page.
virtual void set_config(PCWSTR config) override virtual void set_config(PCWSTR config) override
{ {
m_settings->SetConfig(config);
} }
// Signal from the Settings editor to call a custom action. // Signal from the Settings editor to call a custom action.
@ -111,15 +110,15 @@ public:
virtual void send_settings_telemetry() override virtual void send_settings_telemetry() override
{ {
Logger::info("Send settings telemetry"); Logger::info("Send settings telemetry");
Trace::SettingsTelemetry(*m_settings->GetSettings()); FancyZonesSettings::instance().LoadSettings();
Trace::SettingsTelemetry(FancyZonesSettings::settings());
} }
FancyZonesModuleInterface() FancyZonesModuleInterface()
{ {
app_name = GET_RESOURCE_STRING(IDS_FANCYZONES); app_name = GET_RESOURCE_STRING(IDS_FANCYZONES);
app_key = NonLocalizable::ModuleKey; app_key = NonLocalizable::ModuleKey;
m_settings = MakeFancyZonesSettings(reinterpret_cast<HINSTANCE>(&__ImageBase), FancyZonesModuleInterface::get_name(), FancyZonesModuleInterface::get_key());
m_toggleEditorEvent = CreateDefaultEvent(CommonSharedConstants::FANCY_ZONES_EDITOR_TOGGLE_EVENT); m_toggleEditorEvent = CreateDefaultEvent(CommonSharedConstants::FANCY_ZONES_EDITOR_TOGGLE_EVENT);
if (!m_toggleEditorEvent) if (!m_toggleEditorEvent)
{ {
@ -215,8 +214,6 @@ private:
// Handle to event used to invoke FancyZones Editor // Handle to event used to invoke FancyZones Editor
HANDLE m_toggleEditorEvent; HANDLE m_toggleEditorEvent;
winrt::com_ptr<IFancyZonesSettings> m_settings;
}; };
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()

View File

@ -16,44 +16,29 @@ namespace FancyZonesUnitTests
TEST_CLASS (FancyZonesUnitTests) TEST_CLASS (FancyZonesUnitTests)
{ {
HINSTANCE m_hInst; HINSTANCE m_hInst;
winrt::com_ptr<IFancyZonesSettings> m_settings;
const std::wstring_view m_moduleName = L"FancyZonesUnitTests";
const std::wstring_view m_modulekey = L"FancyZonesUnitTests";
TEST_METHOD_INITIALIZE(Init) TEST_METHOD_INITIALIZE(Init)
{
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
m_settings = MakeFancyZonesSettings(m_hInst, m_moduleName.data(), m_modulekey.data());
Assert::IsTrue(m_settings != nullptr);
}
TEST_METHOD_CLEANUP(CleanUp)
{ {
std::filesystem::remove_all(PTSettingsHelper::get_module_save_folder_location(m_moduleName)); m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
} }
TEST_METHOD (Create) TEST_METHOD (Create)
{ {
auto actual = MakeFancyZones(m_hInst, m_settings, nullptr); auto actual = MakeFancyZones(m_hInst, nullptr);
Assert::IsNotNull(actual.get()); Assert::IsNotNull(actual.get());
} }
TEST_METHOD (CreateWithEmptyHinstance)
{
auto actual = MakeFancyZones({}, m_settings, nullptr);
Assert::IsNotNull(actual.get());
}
TEST_METHOD (CreateWithNullHinstance) TEST_METHOD (CreateWithEmptyHinstance)
{ {
auto actual = MakeFancyZones(nullptr, m_settings, nullptr); auto actual = MakeFancyZones({}, nullptr);
Assert::IsNotNull(actual.get()); Assert::IsNotNull(actual.get());
} }
TEST_METHOD (CreateWithNullSettings) TEST_METHOD (CreateWithNullHinstance)
{ {
auto actual = MakeFancyZones(m_hInst, nullptr, nullptr); auto actual = MakeFancyZones(nullptr, nullptr);
Assert::IsNull(actual.get()); Assert::IsNotNull(actual.get());
} }
}; };
TEST_CLASS (FancyZonesIFancyZonesCallbackUnitTests) TEST_CLASS (FancyZonesIFancyZonesCallbackUnitTests)
@ -61,7 +46,6 @@ namespace FancyZonesUnitTests
HINSTANCE m_hInst{}; HINSTANCE m_hInst{};
std::wstring m_moduleName = L"FancyZonesUnitTests"; std::wstring m_moduleName = L"FancyZonesUnitTests";
std::wstring m_moduleKey = L"FancyZonesUnitTests"; std::wstring m_moduleKey = L"FancyZonesUnitTests";
winrt::com_ptr<IFancyZonesSettings> m_settings = nullptr;
winrt::com_ptr<IFancyZonesCallback> m_fzCallback = nullptr; winrt::com_ptr<IFancyZonesCallback> m_fzCallback = nullptr;
std::wstring serializedPowerToySettings(const Settings& settings) std::wstring serializedPowerToySettings(const Settings& settings)
@ -112,10 +96,7 @@ namespace FancyZonesUnitTests
TEST_METHOD_INITIALIZE(Init) TEST_METHOD_INITIALIZE(Init)
{ {
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr); m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
m_settings = MakeFancyZonesSettings(m_hInst, m_moduleName.c_str(), m_moduleKey.c_str()); auto fancyZones = MakeFancyZones(m_hInst, nullptr);
Assert::IsTrue(m_settings != nullptr);
auto fancyZones = MakeFancyZones(m_hInst, m_settings, nullptr);
Assert::IsTrue(fancyZones != nullptr); Assert::IsTrue(fancyZones != nullptr);
m_fzCallback = fancyZones.as<IFancyZonesCallback>(); m_fzCallback = fancyZones.as<IFancyZonesCallback>();

View File

@ -4,6 +4,7 @@
#include <FancyZonesLib/Settings.h> #include <FancyZonesLib/Settings.h>
#include <FancyZonesLib/FancyZones.h> #include <FancyZonesLib/FancyZones.h>
#include <FancyZonesLib/ModuleConstants.h>
#include <common/SettingsAPI/settings_helpers.h> #include <common/SettingsAPI/settings_helpers.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace Microsoft::VisualStudio::CppUnitTestFramework;
@ -58,503 +59,106 @@ namespace FancyZonesUnitTests
compareHotkeyObjects(expected.prevTabHotkey, actual.prevTabHotkey); compareHotkeyObjects(expected.prevTabHotkey, actual.prevTabHotkey);
} }
TEST_CLASS (FancyZonesSettingsCreationUnitTest) TEST_CLASS (FancyZonesSettingsUnitTest)
{ {
HINSTANCE m_hInst;
PCWSTR m_moduleName = L"FancyZonesUnitTests";
PCWSTR m_moduleKey = L"FancyZonesUnitTests";
std::wstring m_tmpName;
const Settings m_defaultSettings; const Settings m_defaultSettings;
TEST_METHOD_INITIALIZE(Init) TEST_METHOD_INITIALIZE(Init)
{
m_hInst = (HINSTANCE)GetModuleHandleW(nullptr);
m_tmpName = PTSettingsHelper::get_module_save_folder_location(m_moduleName) + L"\\settings.json";
}
TEST_METHOD_CLEANUP(Cleanup)
{
std::filesystem::remove_all(PTSettingsHelper::get_module_save_folder_location(m_moduleName));
}
TEST_METHOD (CreateWithHinstanceDefault)
{
auto actual = MakeFancyZonesSettings({}, m_moduleName, m_moduleKey);
Assert::IsTrue(actual != nullptr);
auto actualSettings = actual->GetSettings();
compareSettings(m_defaultSettings, *actualSettings);
}
TEST_METHOD (CreateWithHinstanceNullptr)
{
auto actual = MakeFancyZonesSettings(nullptr, m_moduleName, m_moduleKey);
Assert::IsTrue(actual != nullptr);
auto actualSettings = actual->GetSettings();
compareSettings(m_defaultSettings, *actualSettings);
}
TEST_METHOD (CreateWithNameEmpty)
{
auto actual = MakeFancyZonesSettings(m_hInst, L"", m_moduleKey);
Assert::IsTrue(actual != nullptr);
auto actualSettings = actual->GetSettings();
compareSettings(m_defaultSettings, *actualSettings);
}
TEST_METHOD (Create)
{
//prepare data
const Settings expected;
PowerToysSettings::PowerToyValues values(m_moduleName, m_moduleKey);
values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag);
values.add_property(L"fancyzones_mouseSwitch", expected.mouseSwitch);
values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows);
values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones);
values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows);
values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys);
values.add_property(L"fancyzones_moveWindowAcrossMonitors", expected.moveWindowAcrossMonitors);
values.add_property(L"fancyzones_moveWindowsBasedOnPosition", expected.moveWindowsBasedOnPosition);
values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows);
values.add_property(L"fancyzones_openWindowOnActiveMonitor", expected.openWindowOnActiveMonitor);
values.add_property(L"fancyzones_restoreSize", expected.restoreSize);
values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen);
values.add_property(L"fancyzones_show_on_all_monitors", expected.showZonesOnAllMonitors);
values.add_property(L"fancyzones_multi_monitor_mode", expected.spanZonesAcrossMonitors);
values.add_property(L"fancyzones_makeDraggedWindowTransparent", expected.makeDraggedWindowTransparent);
values.add_property(L"fancyzones_zoneColor", expected.zoneColor);
values.add_property(L"fancyzones_zoneBorderColor", expected.zoneBorderColor);
values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHighlightColor);
values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity);
values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json());
values.add_property(L"fancyzones_windowSwitching", expected.windowSwitching);
values.add_property(L"fancyzones_nextTab_hotkey", expected.nextTabHotkey.get_json());
values.add_property(L"fancyzones_prevTab_hotkey", expected.prevTabHotkey.get_json());
values.add_property(L"fancyzones_excluded_apps", expected.excludedApps);
values.save_to_settings_file();
auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName, m_moduleKey);
Assert::IsTrue(actual != nullptr);
auto actualSettings = actual->GetSettings();
compareSettings(expected, *actualSettings);
}
TEST_METHOD (CreateWithMultipleApps)
{
//prepare data
const Settings expected{
.excludedApps = L"app\r\napp1\r\napp2\r\nanother app",
.excludedAppsArray = { L"APP", L"APP1", L"APP2", L"ANOTHER APP" },
};
PowerToysSettings::PowerToyValues values(m_moduleName, m_moduleKey);
values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag);
values.add_property(L"fancyzones_mouseSwitch", expected.mouseSwitch);
values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows);
values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones);
values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows);
values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys);
values.add_property(L"fancyzones_moveWindowAcrossMonitors", expected.moveWindowAcrossMonitors);
values.add_property(L"fancyzones_moveWindowsBasedOnPosition", expected.moveWindowsBasedOnPosition);
values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows);
values.add_property(L"fancyzones_openWindowOnActiveMonitor", expected.openWindowOnActiveMonitor);
values.add_property(L"fancyzones_restoreSize", expected.restoreSize);
values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen);
values.add_property(L"fancyzones_show_on_all_monitors", expected.showZonesOnAllMonitors);
values.add_property(L"fancyzones_multi_monitor_mode", expected.spanZonesAcrossMonitors);
values.add_property(L"fancyzones_makeDraggedWindowTransparent", expected.makeDraggedWindowTransparent);
values.add_property(L"fancyzones_zoneColor", expected.zoneColor);
values.add_property(L"fancyzones_zoneBorderColor", expected.zoneBorderColor);
values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHighlightColor);
values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity);
values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json());
values.add_property(L"fancyzones_windowSwitching", expected.windowSwitching);
values.add_property(L"fancyzones_nextTab_hotkey", expected.nextTabHotkey.get_json());
values.add_property(L"fancyzones_prevTab_hotkey", expected.prevTabHotkey.get_json());
values.add_property(L"fancyzones_excluded_apps", expected.excludedApps);
values.save_to_settings_file();
auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName, m_moduleKey);
Assert::IsTrue(actual != nullptr);
auto actualSettings = actual->GetSettings();
compareSettings(expected, *actualSettings);
}
TEST_METHOD (CreateWithBoolValuesMissed)
{
const Settings expected{
.shiftDrag = m_defaultSettings.shiftDrag,
.mouseSwitch = m_defaultSettings.mouseSwitch,
.displayChange_moveWindows = m_defaultSettings.displayChange_moveWindows,
.zoneSetChange_flashZones = m_defaultSettings.zoneSetChange_flashZones,
.zoneSetChange_moveWindows = m_defaultSettings.zoneSetChange_moveWindows,
.overrideSnapHotkeys = m_defaultSettings.overrideSnapHotkeys,
.moveWindowAcrossMonitors = m_defaultSettings.moveWindowAcrossMonitors,
.moveWindowsBasedOnPosition = m_defaultSettings.moveWindowsBasedOnPosition,
.appLastZone_moveWindows = m_defaultSettings.appLastZone_moveWindows,
.openWindowOnActiveMonitor = m_defaultSettings.openWindowOnActiveMonitor,
.restoreSize = m_defaultSettings.restoreSize,
.use_cursorpos_editor_startupscreen = m_defaultSettings.use_cursorpos_editor_startupscreen,
.showZonesOnAllMonitors = m_defaultSettings.showZonesOnAllMonitors,
.spanZonesAcrossMonitors = m_defaultSettings.spanZonesAcrossMonitors,
.makeDraggedWindowTransparent = m_defaultSettings.makeDraggedWindowTransparent,
.zoneColor = L"FAFAFA",
.zoneBorderColor = L"CCDDEE",
.zoneHighlightColor = L"#00FFD7",
.zoneHighlightOpacity = 45,
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_OEM_3),
.nextTabHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_NEXT),
.prevTabHotkey = PowerToysSettings::HotkeyObject::from_settings(false, true, true, false, VK_PRIOR),
.excludedApps = L"app",
.excludedAppsArray = { L"APP" },
};
PowerToysSettings::PowerToyValues values(m_moduleName, m_moduleKey);
values.add_property(L"fancyzones_zoneColor", expected.zoneColor);
values.add_property(L"fancyzones_zoneBorderColor", expected.zoneBorderColor);
values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHighlightColor);
values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity);
values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json());
values.add_property(L"fancyzones_windowSwitching", expected.windowSwitching);
values.add_property(L"fancyzones_nextTab_hotkey", expected.nextTabHotkey.get_json());
values.add_property(L"fancyzones_prevTab_hotkey", expected.prevTabHotkey.get_json());
values.add_property(L"fancyzones_excluded_apps", expected.excludedApps);
values.save_to_settings_file();
auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName, m_moduleKey);
Assert::IsTrue(actual != nullptr);
auto actualSettings = actual->GetSettings();
compareSettings(expected, *actualSettings);
}
TEST_METHOD (CreateColorMissed)
{
//prepare data
const Settings expected;
PowerToysSettings::PowerToyValues values(m_moduleName, m_moduleKey);
values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag);
values.add_property(L"fancyzones_mouseSwitch", expected.mouseSwitch);
values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows);
values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones);
values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows);
values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys);
values.add_property(L"fancyzones_moveWindowAcrossMonitors", expected.moveWindowAcrossMonitors);
values.add_property(L"fancyzones_moveWindowsBasedOnPosition", expected.moveWindowsBasedOnPosition);
values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows);
values.add_property(L"fancyzones_openWindowOnActiveMonitor", expected.openWindowOnActiveMonitor);
values.add_property(L"fancyzones_restoreSize", expected.restoreSize);
values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen);
values.add_property(L"fancyzones_show_on_all_monitors", expected.showZonesOnAllMonitors);
values.add_property(L"fancyzones_multi_monitor_mode", expected.spanZonesAcrossMonitors);
values.add_property(L"fancyzones_makeDraggedWindowTransparent", expected.makeDraggedWindowTransparent);
values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity);
values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json());
values.add_property(L"fancyzones_windowSwitching", expected.windowSwitching);
values.add_property(L"fancyzones_nextTab_hotkey", expected.nextTabHotkey.get_json());
values.add_property(L"fancyzones_prevTab_hotkey", expected.prevTabHotkey.get_json());
values.add_property(L"fancyzones_excluded_apps", expected.excludedApps);
values.save_to_settings_file();
auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName, m_moduleKey);
Assert::IsTrue(actual != nullptr);
auto actualSettings = actual->GetSettings();
compareSettings(expected, *actualSettings);
}
TEST_METHOD (CreateOpacityMissed)
{
//prepare data
const Settings expected;
PowerToysSettings::PowerToyValues values(m_moduleName, m_moduleKey);
values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag);
values.add_property(L"fancyzones_mouseSwitch", expected.mouseSwitch);
values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows);
values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones);
values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows);
values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys);
values.add_property(L"fancyzones_moveWindowAcrossMonitors", expected.moveWindowAcrossMonitors);
values.add_property(L"fancyzones_moveWindowsBasedOnPosition", expected.moveWindowsBasedOnPosition);
values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows);
values.add_property(L"fancyzones_openWindowOnActiveMonitor", expected.openWindowOnActiveMonitor);
values.add_property(L"fancyzones_restoreSize", expected.restoreSize);
values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen);
values.add_property(L"fancyzones_show_on_all_monitors", expected.showZonesOnAllMonitors);
values.add_property(L"fancyzones_multi_monitor_mode", expected.spanZonesAcrossMonitors);
values.add_property(L"fancyzones_makeDraggedWindowTransparent", expected.makeDraggedWindowTransparent);
values.add_property(L"fancyzones_zoneColor", expected.zoneColor);
values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHighlightColor);
values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json());
values.add_property(L"fancyzones_windowSwitching", expected.windowSwitching);
values.add_property(L"fancyzones_nextTab_hotkey", expected.nextTabHotkey.get_json());
values.add_property(L"fancyzones_prevTab_hotkey", expected.prevTabHotkey.get_json());
values.add_property(L"fancyzones_excluded_apps", expected.excludedApps);
values.save_to_settings_file();
auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName, m_moduleKey);
Assert::IsTrue(actual != nullptr);
auto actualSettings = actual->GetSettings();
compareSettings(expected, *actualSettings);
}
TEST_METHOD (CreateHotkeyMissed)
{
//prepare data
const Settings expected;
PowerToysSettings::PowerToyValues values(m_moduleName, m_moduleKey);
values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag);
values.add_property(L"fancyzones_mouseSwitch", expected.mouseSwitch);
values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows);
values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones);
values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows);
values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys);
values.add_property(L"fancyzones_moveWindowAcrossMonitors", expected.moveWindowAcrossMonitors);
values.add_property(L"fancyzones_moveWindowsBasedOnPosition", expected.moveWindowsBasedOnPosition);
values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows);
values.add_property(L"fancyzones_openWindowOnActiveMonitor", expected.openWindowOnActiveMonitor);
values.add_property(L"fancyzones_restoreSize", expected.restoreSize);
values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen);
values.add_property(L"fancyzones_show_on_all_monitors", expected.showZonesOnAllMonitors);
values.add_property(L"fancyzones_multi_monitor_mode", expected.spanZonesAcrossMonitors);
values.add_property(L"fancyzones_makeDraggedWindowTransparent", expected.makeDraggedWindowTransparent);
values.add_property(L"fancyzones_zoneColor", expected.zoneColor);
values.add_property(L"fancyzones_zoneBorderColor", expected.zoneBorderColor);
values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHighlightColor);
values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity);
values.add_property(L"fancyzones_excluded_apps", expected.excludedApps);
values.save_to_settings_file();
auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName, m_moduleKey);
Assert::IsTrue(actual != nullptr);
auto actualSettings = actual->GetSettings();
compareSettings(expected, *actualSettings);
}
TEST_METHOD (CreateAppsMissed)
{
//prepare data
const Settings expected;
PowerToysSettings::PowerToyValues values(m_moduleName, m_moduleKey);
values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag);
values.add_property(L"fancyzones_mouseSwitch", expected.mouseSwitch);
values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows);
values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones);
values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows);
values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys);
values.add_property(L"fancyzones_moveWindowAcrossMonitors", expected.moveWindowAcrossMonitors);
values.add_property(L"fancyzones_moveWindowsBasedOnPosition", expected.moveWindowsBasedOnPosition);
values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows);
values.add_property(L"fancyzones_openWindowOnActiveMonitor", expected.openWindowOnActiveMonitor);
values.add_property(L"fancyzones_restoreSize", expected.restoreSize);
values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen);
values.add_property(L"fancyzones_show_on_all_monitors", expected.showZonesOnAllMonitors);
values.add_property(L"fancyzones_multi_monitor_mode", expected.spanZonesAcrossMonitors);
values.add_property(L"fancyzones_makeDraggedWindowTransparent", expected.makeDraggedWindowTransparent);
values.add_property(L"fancyzones_zoneColor", expected.zoneColor);
values.add_property(L"fancyzones_zoneBorderColor", expected.zoneBorderColor);
values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHighlightColor);
values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity);
values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json());
values.add_property(L"fancyzones_windowSwitching", expected.windowSwitching);
values.add_property(L"fancyzones_nextTab_hotkey", expected.nextTabHotkey.get_json());
values.add_property(L"fancyzones_prevTab_hotkey", expected.prevTabHotkey.get_json());
values.save_to_settings_file();
auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName, m_moduleKey);
Assert::IsTrue(actual != nullptr);
auto actualSettings = actual->GetSettings();
compareSettings(expected, *actualSettings);
}
TEST_METHOD (CreateWithEmptyJson)
{
json::to_file(m_tmpName, json::JsonObject());
auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName, m_moduleKey);
Assert::IsTrue(actual != nullptr);
auto actualSettings = actual->GetSettings();
compareSettings(m_defaultSettings, *actualSettings);
}
TEST_METHOD (CreateWithCorruptedJson)
{
std::wofstream{ m_tmpName.data(), std::ios::binary } << L"{ \"version\": \"1.0\", \"name\": \"";
auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName, m_moduleKey);
Assert::IsTrue(actual != nullptr);
auto actualSettings = actual->GetSettings();
compareSettings(m_defaultSettings, *actualSettings);
}
TEST_METHOD (CreateWithCyrillicSymbolsInJson)
{
std::wofstream{ m_tmpName.data(), std::ios::binary } << L"{ \"version\": \"1.0\", \"name\": \"ФансиЗонс\"}";
auto actual = MakeFancyZonesSettings(m_hInst, m_moduleName, m_moduleKey);
Assert::IsTrue(actual != nullptr);
auto actualSettings = actual->GetSettings();
compareSettings(m_defaultSettings, *actualSettings);
}
};
TEST_CLASS (FancyZonesSettingsUnitTests)
{
winrt::com_ptr<IFancyZonesSettings> m_settings = nullptr;
PCWSTR m_moduleName = L"FancyZonesUnitTests";
PCWSTR m_moduleKey = L"FancyZonesUnitTests";
std::wstring serializedPowerToySettings(const Settings& settings)
{ {
PowerToysSettings::Settings ptSettings(HINSTANCE{}, m_moduleName); // reset to defaults
ptSettings.set_description(IDS_SETTING_DESCRIPTION); PowerToysSettings::PowerToyValues values(NonLocalizable::ModuleKey, NonLocalizable::ModuleKey);
ptSettings.set_icon_key(L"pt-fancy-zones"); values.add_property(L"fancyzones_shiftDrag", m_defaultSettings.shiftDrag);
ptSettings.set_overview_link(L"https://aka.ms/PowerToysOverview_FancyZones"); values.add_property(L"fancyzones_mouseSwitch", m_defaultSettings.mouseSwitch);
ptSettings.set_video_link(L"https://youtu.be/rTtGzZYAXgY"); values.add_property(L"fancyzones_displayChange_moveWindows", m_defaultSettings.displayChange_moveWindows);
values.add_property(L"fancyzones_zoneSetChange_flashZones", m_defaultSettings.zoneSetChange_flashZones);
values.add_property(L"fancyzones_zoneSetChange_moveWindows", m_defaultSettings.zoneSetChange_moveWindows);
values.add_property(L"fancyzones_overrideSnapHotkeys", m_defaultSettings.overrideSnapHotkeys);
values.add_property(L"fancyzones_moveWindowAcrossMonitors", m_defaultSettings.moveWindowAcrossMonitors);
values.add_property(L"fancyzones_moveWindowsBasedOnPosition", m_defaultSettings.moveWindowsBasedOnPosition);
values.add_property(L"fancyzones_appLastZone_moveWindows", m_defaultSettings.appLastZone_moveWindows);
values.add_property(L"fancyzones_openWindowOnActiveMonitor", m_defaultSettings.openWindowOnActiveMonitor);
values.add_property(L"fancyzones_restoreSize", m_defaultSettings.restoreSize);
values.add_property(L"use_cursorpos_editor_startupscreen", m_defaultSettings.use_cursorpos_editor_startupscreen);
values.add_property(L"fancyzones_show_on_all_monitors", m_defaultSettings.showZonesOnAllMonitors);
values.add_property(L"fancyzones_multi_monitor_mode", m_defaultSettings.spanZonesAcrossMonitors);
values.add_property(L"fancyzones_makeDraggedWindowTransparent", m_defaultSettings.makeDraggedWindowTransparent);
values.add_property(L"fancyzones_zoneColor", m_defaultSettings.zoneColor);
values.add_property(L"fancyzones_zoneBorderColor", m_defaultSettings.zoneBorderColor);
values.add_property(L"fancyzones_zoneHighlightColor", m_defaultSettings.zoneHighlightColor);
values.add_property(L"fancyzones_highlight_opacity", m_defaultSettings.zoneHighlightOpacity);
values.add_property(L"fancyzones_editor_hotkey", m_defaultSettings.editorHotkey.get_json());
values.add_property(L"fancyzones_windowSwitching", m_defaultSettings.windowSwitching);
values.add_property(L"fancyzones_nextTab_hotkey", m_defaultSettings.nextTabHotkey.get_json());
values.add_property(L"fancyzones_prevTab_hotkey", m_defaultSettings.prevTabHotkey.get_json());
values.add_property(L"fancyzones_excluded_apps", m_defaultSettings.excludedApps);
ptSettings.add_custom_action( json::to_file(FancyZonesSettings::GetSettingsFileName(), values.get_raw_json());
L"ToggledFZEditor", // action name. FancyZonesSettings::instance().LoadSettings();
IDS_SETTING_LAUNCH_EDITOR_LABEL, }
IDS_SETTING_LAUNCH_EDITOR_BUTTON,
IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION); TEST_METHOD_CLEANUP(Cleanup)
ptSettings.add_hotkey(L"fancyzones_editor_hotkey", IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL, settings.editorHotkey); {
ptSettings.add_bool_toggle(L"fancyzones_windowSwitching", IDS_SETTING_WINDOW_SWITCHING_TOGGLE_LABEL, settings.windowSwitching); std::filesystem::remove(FancyZonesSettings::GetSettingsFileName());
ptSettings.add_hotkey(L"fancyzones_nextTab_hotkey", IDS_SETTING_NEXT_TAB_HOTKEY_LABEL, settings.nextTabHotkey);
ptSettings.add_hotkey(L"fancyzones_prevTab_hotkey", IDS_SETTING_PREV_TAB_HOTKEY_LABEL, settings.prevTabHotkey);
ptSettings.add_bool_toggle(L"fancyzones_shiftDrag", IDS_SETTING_DESCRIPTION_SHIFTDRAG, settings.shiftDrag);
ptSettings.add_bool_toggle(L"fancyzones_mouseSwitch", IDS_SETTING_DESCRIPTION_MOUSESWITCH, settings.mouseSwitch);
ptSettings.add_bool_toggle(L"fancyzones_overrideSnapHotkeys", IDS_SETTING_DESCRIPTION_OVERRIDE_SNAP_HOTKEYS, settings.overrideSnapHotkeys);
ptSettings.add_bool_toggle(L"fancyzones_moveWindowAcrossMonitors", IDS_SETTING_DESCRIPTION_MOVE_WINDOW_ACROSS_MONITORS, settings.moveWindowAcrossMonitors);
ptSettings.add_bool_toggle(L"fancyzones_moveWindowsBasedOnPosition", IDS_SETTING_DESCRIPTION_MOVE_WINDOWS_BASED_ON_POSITION, settings.moveWindowsBasedOnPosition);
ptSettings.add_bool_toggle(L"fancyzones_zoneSetChange_flashZones", IDS_SETTING_DESCRIPTION_ZONESETCHANGE_FLASHZONES, settings.zoneSetChange_flashZones);
ptSettings.add_bool_toggle(L"fancyzones_displayChange_moveWindows", IDS_SETTING_DESCRIPTION_DISPLAYCHANGE_MOVEWINDOWS, settings.displayChange_moveWindows);
ptSettings.add_bool_toggle(L"fancyzones_zoneSetChange_moveWindows", IDS_SETTING_DESCRIPTION_ZONESETCHANGE_MOVEWINDOWS, settings.zoneSetChange_moveWindows);
ptSettings.add_bool_toggle(L"fancyzones_appLastZone_moveWindows", IDS_SETTING_DESCRIPTION_APPLASTZONE_MOVEWINDOWS, settings.appLastZone_moveWindows);
ptSettings.add_bool_toggle(L"fancyzones_openWindowOnActiveMonitor", IDS_SETTING_DESCRIPTION_OPEN_WINDOW_ON_ACTIVE_MONITOR, settings.openWindowOnActiveMonitor);
ptSettings.add_bool_toggle(L"fancyzones_restoreSize", IDS_SETTING_DESCRIPTION_RESTORESIZE, settings.restoreSize);
ptSettings.add_bool_toggle(L"use_cursorpos_editor_startupscreen", IDS_SETTING_DESCRIPTION_USE_CURSORPOS_EDITOR_STARTUPSCREEN, settings.use_cursorpos_editor_startupscreen);
ptSettings.add_bool_toggle(L"fancyzones_show_on_all_monitors", IDS_SETTING_DESCRIPTION_SHOW_FANCY_ZONES_ON_ALL_MONITORS, settings.showZonesOnAllMonitors);
ptSettings.add_bool_toggle(L"fancyzones_multi_monitor_mode", IDS_SETTING_DESCRIPTION_SPAN_ZONES_ACROSS_MONITORS, settings.spanZonesAcrossMonitors);
ptSettings.add_bool_toggle(L"fancyzones_makeDraggedWindowTransparent", IDS_SETTING_DESCRIPTION_MAKE_DRAGGED_WINDOW_TRANSPARENT, settings.makeDraggedWindowTransparent);
ptSettings.add_int_spinner(L"fancyzones_highlight_opacity", IDS_SETTINGS_HIGHLIGHT_OPACITY, settings.zoneHighlightOpacity, 0, 100, 1);
ptSettings.add_color_picker(L"fancyzones_zoneColor", IDS_SETTING_DESCRIPTION_ZONECOLOR, settings.zoneColor);
ptSettings.add_color_picker(L"fancyzones_zoneBorderColor", IDS_SETTING_DESCRIPTION_ZONE_BORDER_COLOR, settings.zoneBorderColor);
ptSettings.add_color_picker(L"fancyzones_zoneHighlightColor", IDS_SETTING_DESCRIPTION_ZONEHIGHLIGHTCOLOR, settings.zoneHighlightColor);
ptSettings.add_multiline_string(L"fancyzones_excluded_apps", IDS_SETTING_EXCLUDED_APPS_DESCRIPTION, settings.excludedApps);
return ptSettings.serialize();
} }
TEST_METHOD_INITIALIZE(Init) TEST_METHOD (Parse)
{ {
HINSTANCE hInst = (HINSTANCE)GetModuleHandleW(nullptr); //prepare data
const Settings expected{
.excludedApps = L"app\r\napp1\r\napp2\r\nanother app",
.excludedAppsArray = { L"APP", L"APP1", L"APP2", L"ANOTHER APP" },
};
m_settings = MakeFancyZonesSettings(hInst, m_moduleName, m_moduleKey); PowerToysSettings::PowerToyValues values(NonLocalizable::ModuleKey, NonLocalizable::ModuleKey);
Assert::IsTrue(m_settings != nullptr); values.add_property(L"fancyzones_shiftDrag", expected.shiftDrag);
} values.add_property(L"fancyzones_mouseSwitch", expected.mouseSwitch);
values.add_property(L"fancyzones_displayChange_moveWindows", expected.displayChange_moveWindows);
values.add_property(L"fancyzones_zoneSetChange_flashZones", expected.zoneSetChange_flashZones);
values.add_property(L"fancyzones_zoneSetChange_moveWindows", expected.zoneSetChange_moveWindows);
values.add_property(L"fancyzones_overrideSnapHotkeys", expected.overrideSnapHotkeys);
values.add_property(L"fancyzones_moveWindowAcrossMonitors", expected.moveWindowAcrossMonitors);
values.add_property(L"fancyzones_moveWindowsBasedOnPosition", expected.moveWindowsBasedOnPosition);
values.add_property(L"fancyzones_appLastZone_moveWindows", expected.appLastZone_moveWindows);
values.add_property(L"fancyzones_openWindowOnActiveMonitor", expected.openWindowOnActiveMonitor);
values.add_property(L"fancyzones_restoreSize", expected.restoreSize);
values.add_property(L"use_cursorpos_editor_startupscreen", expected.use_cursorpos_editor_startupscreen);
values.add_property(L"fancyzones_show_on_all_monitors", expected.showZonesOnAllMonitors);
values.add_property(L"fancyzones_multi_monitor_mode", expected.spanZonesAcrossMonitors);
values.add_property(L"fancyzones_makeDraggedWindowTransparent", expected.makeDraggedWindowTransparent);
values.add_property(L"fancyzones_zoneColor", expected.zoneColor);
values.add_property(L"fancyzones_zoneBorderColor", expected.zoneBorderColor);
values.add_property(L"fancyzones_zoneHighlightColor", expected.zoneHighlightColor);
values.add_property(L"fancyzones_highlight_opacity", expected.zoneHighlightOpacity);
values.add_property(L"fancyzones_editor_hotkey", expected.editorHotkey.get_json());
values.add_property(L"fancyzones_windowSwitching", expected.windowSwitching);
values.add_property(L"fancyzones_nextTab_hotkey", expected.nextTabHotkey.get_json());
values.add_property(L"fancyzones_prevTab_hotkey", expected.prevTabHotkey.get_json());
values.add_property(L"fancyzones_excluded_apps", expected.excludedApps);
TEST_METHOD_CLEANUP(Cleanup) json::to_file(FancyZonesSettings::GetSettingsFileName(), values.get_raw_json());
{
std::filesystem::remove_all(PTSettingsHelper::get_module_save_folder_location(m_moduleName)); FancyZonesSettings::instance().LoadSettings();
} auto actual = FancyZonesSettings::settings();
compareSettings(expected, actual);
}
TEST_METHOD (GetConfig) TEST_METHOD (ParseInvalid)
{ {
int expectedSize = 0; PowerToysSettings::PowerToyValues values(NonLocalizable::ModuleKey, NonLocalizable::ModuleKey);
m_settings->GetConfig(nullptr, &expectedSize); values.add_property(L"non_fancyzones_value", false);
Assert::AreNotEqual(0, expectedSize);
int actualBufferSize = expectedSize; json::to_file(FancyZonesSettings::GetSettingsFileName(), values.get_raw_json());
PWSTR actualBuffer = new wchar_t[actualBufferSize];
Assert::IsTrue(m_settings->GetConfig(actualBuffer, &actualBufferSize)); FancyZonesSettings::instance().LoadSettings();
Assert::AreEqual(expectedSize, actualBufferSize); auto actual = FancyZonesSettings::settings();
} compareSettings(m_defaultSettings, actual);
}
TEST_METHOD (GetConfigSmallBuffer) TEST_METHOD (ParseEmpty)
{ {
int size = 0; FancyZonesSettings::instance().LoadSettings();
m_settings->GetConfig(nullptr, &size); auto actual = FancyZonesSettings::settings();
Assert::AreNotEqual(0, size); compareSettings(m_defaultSettings, actual);
}
int actualBufferSize = size - 1;
PWSTR actualBuffer = new wchar_t[actualBufferSize];
Assert::IsFalse(m_settings->GetConfig(actualBuffer, &actualBufferSize));
Assert::AreEqual(size, actualBufferSize);
}
TEST_METHOD (GetConfigNullBuffer)
{
int expectedSize = 0;
m_settings->GetConfig(nullptr, &expectedSize);
Assert::AreNotEqual(0, expectedSize);
int actualBufferSize = 0;
Assert::IsFalse(m_settings->GetConfig(nullptr, &actualBufferSize));
Assert::AreEqual(expectedSize, actualBufferSize);
}
TEST_METHOD (SetConfig)
{
//cleanup file before call set config
const auto settingsFile = PTSettingsHelper::get_module_save_folder_location(m_moduleName) + L"\\settings.json";
std::filesystem::remove(settingsFile);
const Settings expected{
.shiftDrag = true,
.mouseSwitch = true,
.displayChange_moveWindows = true,
.zoneSetChange_flashZones = false,
.zoneSetChange_moveWindows = true,
.overrideSnapHotkeys = false,
.moveWindowAcrossMonitors = false,
.appLastZone_moveWindows = true,
.openWindowOnActiveMonitor = false,
.restoreSize = false,
.use_cursorpos_editor_startupscreen = true,
.showZonesOnAllMonitors = false,
.spanZonesAcrossMonitors = false,
.makeDraggedWindowTransparent = true,
.zoneColor = L"#FAFAFA",
.zoneBorderColor = L"CCDDEE",
.zoneHighlightColor = L"#00AABB",
.zoneHighlightOpacity = 45,
.editorHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_OEM_3),
.nextTabHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_NEXT),
.prevTabHotkey = PowerToysSettings::HotkeyObject::from_settings(false, false, false, false, VK_PRIOR),
.excludedApps = L"app\r\napp2",
.excludedAppsArray = { L"APP", L"APP2" },
};
auto config = serializedPowerToySettings(expected);
m_settings->SetConfig(config.c_str());
auto actual = m_settings->GetSettings();
compareSettings(expected, *actual);
Assert::IsTrue(std::filesystem::exists(settingsFile));
}
}; };
} }

View File

@ -10,7 +10,6 @@
#include <FancyZonesLib/FancyZonesData/AppZoneHistory.h> #include <FancyZonesLib/FancyZonesData/AppZoneHistory.h>
#include <FancyZonesLib/FancyZonesDataTypes.h> #include <FancyZonesLib/FancyZonesDataTypes.h>
#include <FancyZonesLib/JsonHelpers.h> #include <FancyZonesLib/JsonHelpers.h>
#include <FancyZonesLib/ZoneColors.h>
#include "Util.h" #include "Util.h"
#include <common/utils/process_path.h> #include <common/utils/process_path.h>
@ -31,9 +30,6 @@ namespace FancyZonesUnitTests
HMONITOR m_monitor{}; HMONITOR m_monitor{};
MONITORINFOEX m_monitorInfo{}; MONITORINFOEX m_monitorInfo{};
GUID m_virtualDesktopGuid{}; GUID m_virtualDesktopGuid{};
ZoneColors m_zoneColors{};
OverlappingZonesAlgorithm m_overlappingAlgorithm = OverlappingZonesAlgorithm::Positional;
bool m_showZoneText = true;
void testWorkArea(winrt::com_ptr<IWorkArea> workArea) void testWorkArea(winrt::com_ptr<IWorkArea> workArea)
{ {
@ -65,13 +61,6 @@ namespace FancyZonesUnitTests
Assert::IsTrue(guid.has_value()); Assert::IsTrue(guid.has_value());
m_virtualDesktopGuid = *guid; m_virtualDesktopGuid = *guid;
m_zoneColors = ZoneColors{
.primaryColor = FancyZonesUtils::HexToRGB(L"#4287f5"),
.borderColor = FancyZonesUtils::HexToRGB(L"#FFFFFF"),
.highlightColor = FancyZonesUtils::HexToRGB(L"#42eff5"),
.highlightOpacity = 50,
};
AppZoneHistory::instance().LoadData(); AppZoneHistory::instance().LoadData();
AppliedLayouts::instance().LoadData(); AppliedLayouts::instance().LoadData();
} }
@ -84,7 +73,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (CreateWorkArea) TEST_METHOD (CreateWorkArea)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {});
testWorkArea(workArea); testWorkArea(workArea);
auto* zoneSet{ workArea->ZoneSet() }; auto* zoneSet{ workArea->ZoneSet() };
@ -95,7 +84,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (CreateWorkAreaNoHinst) TEST_METHOD (CreateWorkAreaNoHinst)
{ {
auto workArea = MakeWorkArea({}, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea({}, m_monitor, m_uniqueId, {});
testWorkArea(workArea); testWorkArea(workArea);
auto* zoneSet{ workArea->ZoneSet() }; auto* zoneSet{ workArea->ZoneSet() };
@ -106,7 +95,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (CreateWorkAreaNoHinstFlashZones) TEST_METHOD (CreateWorkAreaNoHinstFlashZones)
{ {
auto workArea = MakeWorkArea({}, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea({}, m_monitor, m_uniqueId, {});
testWorkArea(workArea); testWorkArea(workArea);
auto* zoneSet{ workArea->ZoneSet() }; auto* zoneSet{ workArea->ZoneSet() };
@ -117,7 +106,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (CreateWorkAreaNoMonitor) TEST_METHOD (CreateWorkAreaNoMonitor)
{ {
auto workArea = MakeWorkArea(m_hInst, {}, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea(m_hInst, {}, m_uniqueId, {});
testWorkArea(workArea); testWorkArea(workArea);
} }
@ -136,7 +125,7 @@ namespace FancyZonesUnitTests
uniqueIdData.height = monitorRect.height(); uniqueIdData.height = monitorRect.height();
} }
auto workArea = MakeWorkArea(m_hInst, m_monitor, uniqueIdData, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea(m_hInst, m_monitor, uniqueIdData, {});
const std::wstring expectedWorkArea = std::to_wstring(m_monitorInfo.rcMonitor.right) + L"_" + std::to_wstring(m_monitorInfo.rcMonitor.bottom); const std::wstring expectedWorkArea = std::to_wstring(m_monitorInfo.rcMonitor.right) + L"_" + std::to_wstring(m_monitorInfo.rcMonitor.bottom);
const FancyZonesDataTypes::DeviceIdData expectedUniqueId{ L"FallbackDevice", m_monitorInfo.rcMonitor.right - m_monitorInfo.rcMonitor.left, m_monitorInfo.rcMonitor.bottom - m_monitorInfo.rcMonitor.top, m_virtualDesktopGuid }; const FancyZonesDataTypes::DeviceIdData expectedUniqueId{ L"FallbackDevice", m_monitorInfo.rcMonitor.right - m_monitorInfo.rcMonitor.left, m_monitorInfo.rcMonitor.bottom - m_monitorInfo.rcMonitor.top, m_virtualDesktopGuid };
@ -165,7 +154,7 @@ namespace FancyZonesUnitTests
uniqueId.height = monitorRect.height(); uniqueId.height = monitorRect.height();
} }
auto workArea = MakeWorkArea(m_hInst, m_monitor, uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea(m_hInst, m_monitor, uniqueId, {});
const std::wstring expectedWorkArea = std::to_wstring(m_monitorInfo.rcMonitor.right) + L"_" + std::to_wstring(m_monitorInfo.rcMonitor.bottom); const std::wstring expectedWorkArea = std::to_wstring(m_monitorInfo.rcMonitor.right) + L"_" + std::to_wstring(m_monitorInfo.rcMonitor.bottom);
Assert::IsNotNull(workArea.get()); Assert::IsNotNull(workArea.get());
@ -185,10 +174,10 @@ namespace FancyZonesUnitTests
const int zoneCount = 5; const int zoneCount = 5;
const auto customSetGuid = Helpers::CreateGuidString(); const auto customSetGuid = Helpers::CreateGuidString();
auto parentWorkArea = MakeWorkArea(m_hInst, m_monitor, m_parentUniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto parentWorkArea = MakeWorkArea(m_hInst, m_monitor, m_parentUniqueId, {});
// newWorkArea = false - workArea won't be cloned from parent // newWorkArea = false - workArea won't be cloned from parent
auto actualWorkArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto actualWorkArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {});
Assert::IsNotNull(actualWorkArea->ZoneSet()); Assert::IsNotNull(actualWorkArea->ZoneSet());
@ -209,9 +198,6 @@ namespace FancyZonesUnitTests
HINSTANCE m_hInst{}; HINSTANCE m_hInst{};
HMONITOR m_monitor{}; HMONITOR m_monitor{};
MONITORINFO m_monitorInfo{}; MONITORINFO m_monitorInfo{};
ZoneColors m_zoneColors{};
OverlappingZonesAlgorithm m_overlappingAlgorithm = OverlappingZonesAlgorithm::Positional;
bool m_showZoneText = true;
TEST_METHOD_INITIALIZE(Init) TEST_METHOD_INITIALIZE(Init)
{ {
@ -225,13 +211,6 @@ namespace FancyZonesUnitTests
m_uniqueId.width = m_monitorInfo.rcMonitor.right - m_monitorInfo.rcMonitor.left; m_uniqueId.width = m_monitorInfo.rcMonitor.right - m_monitorInfo.rcMonitor.left;
m_uniqueId.height = m_monitorInfo.rcMonitor.bottom - m_monitorInfo.rcMonitor.top; m_uniqueId.height = m_monitorInfo.rcMonitor.bottom - m_monitorInfo.rcMonitor.top;
CLSIDFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}", &m_uniqueId.virtualDesktopId); CLSIDFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}", &m_uniqueId.virtualDesktopId);
m_zoneColors = ZoneColors{
.primaryColor = FancyZonesUtils::HexToRGB(L"#4287f5"),
.borderColor = FancyZonesUtils::HexToRGB(L"#FFFFFF"),
.highlightColor = FancyZonesUtils::HexToRGB(L"#42eff5"),
.highlightOpacity = 50,
};
AppZoneHistory::instance().LoadData(); AppZoneHistory::instance().LoadData();
AppliedLayouts::instance().LoadData(); AppliedLayouts::instance().LoadData();
@ -246,7 +225,7 @@ namespace FancyZonesUnitTests
public: public:
TEST_METHOD (MoveSizeEnter) TEST_METHOD (MoveSizeEnter)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {});
const auto expected = S_OK; const auto expected = S_OK;
const auto actual = workArea->MoveSizeEnter(Mocks::Window()); const auto actual = workArea->MoveSizeEnter(Mocks::Window());
@ -256,7 +235,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (MoveSizeEnterTwice) TEST_METHOD (MoveSizeEnterTwice)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {});
const auto expected = S_OK; const auto expected = S_OK;
@ -268,7 +247,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (MoveSizeUpdate) TEST_METHOD (MoveSizeUpdate)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {});
const auto expected = S_OK; const auto expected = S_OK;
const auto actual = workArea->MoveSizeUpdate(POINT{ 0, 0 }, true, false); const auto actual = workArea->MoveSizeUpdate(POINT{ 0, 0 }, true, false);
@ -278,7 +257,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (MoveSizeUpdatePointNegativeCoordinates) TEST_METHOD (MoveSizeUpdatePointNegativeCoordinates)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {});
const auto expected = S_OK; const auto expected = S_OK;
const auto actual = workArea->MoveSizeUpdate(POINT{ -10, -10 }, true, false); const auto actual = workArea->MoveSizeUpdate(POINT{ -10, -10 }, true, false);
@ -288,7 +267,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (MoveSizeUpdatePointBigCoordinates) TEST_METHOD (MoveSizeUpdatePointBigCoordinates)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {});
const auto expected = S_OK; const auto expected = S_OK;
const auto actual = workArea->MoveSizeUpdate(POINT{ m_monitorInfo.rcMonitor.right + 1, m_monitorInfo.rcMonitor.bottom + 1 }, true, false); const auto actual = workArea->MoveSizeUpdate(POINT{ m_monitorInfo.rcMonitor.right + 1, m_monitorInfo.rcMonitor.bottom + 1 }, true, false);
@ -298,7 +277,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (MoveSizeEnd) TEST_METHOD (MoveSizeEnd)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {});
const auto window = Mocks::Window(); const auto window = Mocks::Window();
workArea->MoveSizeEnter(window); workArea->MoveSizeEnter(window);
@ -315,7 +294,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (MoveSizeEndWindowNotAdded) TEST_METHOD (MoveSizeEndWindowNotAdded)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {});
const auto window = Mocks::Window(); const auto window = Mocks::Window();
workArea->MoveSizeEnter(window); workArea->MoveSizeEnter(window);
@ -331,7 +310,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (MoveSizeEndDifferentWindows) TEST_METHOD (MoveSizeEndDifferentWindows)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {});
const auto window = Mocks::Window(); const auto window = Mocks::Window();
workArea->MoveSizeEnter(window); workArea->MoveSizeEnter(window);
@ -344,7 +323,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (MoveSizeEndWindowNotSet) TEST_METHOD (MoveSizeEndWindowNotSet)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {});
const auto expected = E_INVALIDARG; const auto expected = E_INVALIDARG;
const auto actual = workArea->MoveSizeEnd(Mocks::Window(), POINT{ 0, 0 }); const auto actual = workArea->MoveSizeEnd(Mocks::Window(), POINT{ 0, 0 });
@ -354,7 +333,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (MoveSizeEndInvalidPoint) TEST_METHOD (MoveSizeEndInvalidPoint)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {});
const auto window = Mocks::Window(); const auto window = Mocks::Window();
workArea->MoveSizeEnter(window); workArea->MoveSizeEnter(window);
@ -371,7 +350,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (MoveWindowIntoZoneByIndex) TEST_METHOD (MoveWindowIntoZoneByIndex)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {});
Assert::IsNotNull(workArea->ZoneSet()); Assert::IsNotNull(workArea->ZoneSet());
workArea->MoveWindowIntoZoneByIndex(Mocks::Window(), 0); workArea->MoveWindowIntoZoneByIndex(Mocks::Window(), 0);
@ -381,7 +360,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (MoveWindowIntoZoneByDirectionAndIndex) TEST_METHOD (MoveWindowIntoZoneByDirectionAndIndex)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {});
Assert::IsNotNull(workArea->ZoneSet()); Assert::IsNotNull(workArea->ZoneSet());
const auto window = Mocks::WindowCreate(m_hInst); const auto window = Mocks::WindowCreate(m_hInst);
@ -396,7 +375,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (MoveWindowIntoZoneByDirectionManyTimes) TEST_METHOD (MoveWindowIntoZoneByDirectionManyTimes)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {});
Assert::IsNotNull(workArea->ZoneSet()); Assert::IsNotNull(workArea->ZoneSet());
const auto window = Mocks::WindowCreate(m_hInst); const auto window = Mocks::WindowCreate(m_hInst);
@ -413,7 +392,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (SaveWindowProcessToZoneIndexNullptrWindow) TEST_METHOD (SaveWindowProcessToZoneIndexNullptrWindow)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {});
Assert::IsNotNull(workArea->ZoneSet()); Assert::IsNotNull(workArea->ZoneSet());
workArea->SaveWindowProcessToZoneIndex(nullptr); workArea->SaveWindowProcessToZoneIndex(nullptr);
@ -424,7 +403,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (SaveWindowProcessToZoneIndexNoWindowAdded) TEST_METHOD (SaveWindowProcessToZoneIndexNoWindowAdded)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {});
Assert::IsNotNull(workArea->ZoneSet()); Assert::IsNotNull(workArea->ZoneSet());
auto window = Mocks::WindowCreate(m_hInst); auto window = Mocks::WindowCreate(m_hInst);
@ -439,7 +418,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (SaveWindowProcessToZoneIndexNoWindowAddedWithFilledAppZoneHistory) TEST_METHOD (SaveWindowProcessToZoneIndexNoWindowAddedWithFilledAppZoneHistory)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {});
Assert::IsNotNull(workArea->ZoneSet()); Assert::IsNotNull(workArea->ZoneSet());
const auto window = Mocks::WindowCreate(m_hInst); const auto window = Mocks::WindowCreate(m_hInst);
@ -467,7 +446,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (SaveWindowProcessToZoneIndexWindowAdded) TEST_METHOD (SaveWindowProcessToZoneIndexWindowAdded)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {});
Assert::IsNotNull(workArea->ZoneSet()); Assert::IsNotNull(workArea->ZoneSet());
auto window = Mocks::WindowCreate(m_hInst); auto window = Mocks::WindowCreate(m_hInst);
@ -497,7 +476,7 @@ namespace FancyZonesUnitTests
TEST_METHOD (WhenWindowIsNotResizablePlacingItIntoTheZoneShouldNotResizeIt) TEST_METHOD (WhenWindowIsNotResizablePlacingItIntoTheZoneShouldNotResizeIt)
{ {
auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {}, m_zoneColors, m_overlappingAlgorithm, m_showZoneText); auto workArea = MakeWorkArea(m_hInst, m_monitor, m_uniqueId, {});
Assert::IsNotNull(workArea->ZoneSet()); Assert::IsNotNull(workArea->ZoneSet());
auto window = Mocks::WindowCreate(m_hInst); auto window = Mocks::WindowCreate(m_hInst);

View File

@ -43,6 +43,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
FancyzonesNextTabHotkey = new KeyboardKeysProperty(DefaultNextTabHotkeyValue); FancyzonesNextTabHotkey = new KeyboardKeysProperty(DefaultNextTabHotkeyValue);
FancyzonesPrevTabHotkey = new KeyboardKeysProperty(DefaultPrevTabHotkeyValue); FancyzonesPrevTabHotkey = new KeyboardKeysProperty(DefaultPrevTabHotkeyValue);
FancyzonesMakeDraggedWindowTransparent = new BoolProperty(); FancyzonesMakeDraggedWindowTransparent = new BoolProperty();
FancyzonesAllowPopupWindowSnap = new BoolProperty();
FancyzonesAllowChildWindowSnap = new BoolProperty();
FancyzonesExcludedApps = new StringProperty(); FancyzonesExcludedApps = new StringProperty();
FancyzonesInActiveColor = new StringProperty(ConfigDefaults.DefaultFancyZonesInActiveColor); FancyzonesInActiveColor = new StringProperty(ConfigDefaults.DefaultFancyZonesInActiveColor);
FancyzonesBorderColor = new StringProperty(ConfigDefaults.DefaultFancyzonesBorderColor); FancyzonesBorderColor = new StringProperty(ConfigDefaults.DefaultFancyzonesBorderColor);
@ -102,6 +104,12 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("fancyzones_makeDraggedWindowTransparent")] [JsonPropertyName("fancyzones_makeDraggedWindowTransparent")]
public BoolProperty FancyzonesMakeDraggedWindowTransparent { get; set; } public BoolProperty FancyzonesMakeDraggedWindowTransparent { get; set; }
[JsonPropertyName("fancyzones_allowPopupWindowSnap")]
public BoolProperty FancyzonesAllowPopupWindowSnap { get; set; }
[JsonPropertyName("fancyzones_allowChildWindowSnap")]
public BoolProperty FancyzonesAllowChildWindowSnap { get; set; }
[JsonPropertyName("fancyzones_zoneHighlightColor")] [JsonPropertyName("fancyzones_zoneHighlightColor")]
public StringProperty FancyzonesZoneHighlightColor { get; set; } public StringProperty FancyzonesZoneHighlightColor { get; set; }

View File

@ -84,6 +84,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
_showOnAllMonitors = Settings.Properties.FancyzonesShowOnAllMonitors.Value; _showOnAllMonitors = Settings.Properties.FancyzonesShowOnAllMonitors.Value;
_spanZonesAcrossMonitors = Settings.Properties.FancyzonesSpanZonesAcrossMonitors.Value; _spanZonesAcrossMonitors = Settings.Properties.FancyzonesSpanZonesAcrossMonitors.Value;
_makeDraggedWindowTransparent = Settings.Properties.FancyzonesMakeDraggedWindowTransparent.Value; _makeDraggedWindowTransparent = Settings.Properties.FancyzonesMakeDraggedWindowTransparent.Value;
_allowPopupWindowSnap = Settings.Properties.FancyzonesAllowPopupWindowSnap.Value;
_allowChildWindowSnap = Settings.Properties.FancyzonesAllowChildWindowSnap.Value;
_highlightOpacity = Settings.Properties.FancyzonesHighlightOpacity.Value; _highlightOpacity = Settings.Properties.FancyzonesHighlightOpacity.Value;
_excludedApps = Settings.Properties.FancyzonesExcludedApps.Value; _excludedApps = Settings.Properties.FancyzonesExcludedApps.Value;
_systemTheme = Settings.Properties.FancyzonesSystemTheme.Value; _systemTheme = Settings.Properties.FancyzonesSystemTheme.Value;
@ -131,6 +133,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
private bool _makeDraggedWindowTransparent; private bool _makeDraggedWindowTransparent;
private bool _systemTheme; private bool _systemTheme;
private bool _showZoneNumber; private bool _showZoneNumber;
private bool _allowPopupWindowSnap;
private bool _allowChildWindowSnap;
private int _highlightOpacity; private int _highlightOpacity;
private string _excludedApps; private string _excludedApps;
@ -562,6 +566,42 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
} }
} }
public bool AllowPopupWindowSnap
{
get
{
return _allowPopupWindowSnap;
}
set
{
if (value != _allowPopupWindowSnap)
{
_allowPopupWindowSnap = value;
Settings.Properties.FancyzonesAllowPopupWindowSnap.Value = value;
NotifyPropertyChanged();
}
}
}
public bool AllowChildWindowSnap
{
get
{
return _allowChildWindowSnap;
}
set
{
if (value != _allowChildWindowSnap)
{
_allowChildWindowSnap = value;
Settings.Properties.FancyzonesAllowChildWindowSnap.Value = value;
NotifyPropertyChanged();
}
}
}
// For the following setters we use OrdinalIgnoreCase string comparison since // For the following setters we use OrdinalIgnoreCase string comparison since
// we expect value to be a hex code. // we expect value to be a hex code.
public string ZoneHighlightColor public string ZoneHighlightColor

View File

@ -2050,4 +2050,13 @@ From there, simply click on one of the supported files in the File Explorer and
<value>Attribution</value> <value>Attribution</value>
<comment>giving credit to the projects this utility was based on</comment> <comment>giving credit to the projects this utility was based on</comment>
</data> </data>
<data name="FancyZones_AllowPopupWindowSnap.Description" xml:space="preserve">
<value>This setting can affect all popup windows including notifications</value>
</data>
<data name="FancyZones_AllowPopupWindowSnap.Header" xml:space="preserve">
<value>Allow popup windows snapping</value>
</data>
<data name="FancyZones_AllowChildWindowSnap.Content" xml:space="preserve">
<value>Allow child windows snapping</value>
</data>
</root> </root>

View File

@ -183,6 +183,12 @@
<CheckBox x:Uid="FancyZones_RestoreSize" IsChecked="{x:Bind Mode=TwoWay, Path=ViewModel.RestoreSize}" Margin="{StaticResource ExpanderSettingMargin}"/> <CheckBox x:Uid="FancyZones_RestoreSize" IsChecked="{x:Bind Mode=TwoWay, Path=ViewModel.RestoreSize}" Margin="{StaticResource ExpanderSettingMargin}"/>
<Rectangle Style="{StaticResource ExpanderSeparatorStyle}" /> <Rectangle Style="{StaticResource ExpanderSeparatorStyle}" />
<CheckBox x:Uid="FancyZones_MakeDraggedWindowTransparentCheckBoxControl" IsChecked="{x:Bind Mode=TwoWay, Path=ViewModel.MakeDraggedWindowsTransparent}" Margin="{StaticResource ExpanderSettingMargin}"/> <CheckBox x:Uid="FancyZones_MakeDraggedWindowTransparentCheckBoxControl" IsChecked="{x:Bind Mode=TwoWay, Path=ViewModel.MakeDraggedWindowsTransparent}" Margin="{StaticResource ExpanderSettingMargin}"/>
<Rectangle Style="{StaticResource ExpanderSeparatorStyle}" />
<controls:CheckBoxWithDescriptionControl IsChecked="{x:Bind Mode=TwoWay, Path=ViewModel.AllowPopupWindowSnap}"
Margin="56, -2, 40, 14"
x:Uid="FancyZones_AllowPopupWindowSnap"/>
<Rectangle Style="{StaticResource ExpanderSeparatorStyle}" />
<CheckBox x:Uid="FancyZones_AllowChildWindowSnap" IsChecked="{x:Bind Mode=TwoWay, Path=ViewModel.AllowChildWindowSnap}" Margin="{StaticResource ExpanderSettingMargin}"/>
</StackPanel> </StackPanel>
</controls:SettingExpander.Content> </controls:SettingExpander.Content>