[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
SYSMENU
systemd
SYSTEMAPPS
SYSTEMTIME
systemverilog
Tadele

View File

@ -3,7 +3,7 @@
#include <string>
// 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)
{
@ -17,3 +17,16 @@ bool find_app_name_in_path(const std::wstring& where, const std::vector<std::wst
}
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)
{
DPIAware::EnableDPIAwarenessForThisProcess();
m_settings = MakeFancyZonesSettings(reinterpret_cast<HINSTANCE>(&__ImageBase), appName.c_str(), appKey.c_str());
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();

View File

@ -19,7 +19,6 @@ private:
winrt::com_ptr<IFancyZones> m_app;
HWINEVENTHOOK m_objectLocationWinEventHook = nullptr;
std::vector<HWINEVENTHOOK> m_staticWinEventHooks;
winrt::com_ptr<IFancyZonesSettings> m_settings;
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/MonitorUtils.h>
#include <FancyZonesLib/Settings.h>
#include <FancyZonesLib/SettingsObserver.h>
#include <FancyZonesLib/ZoneSet.h>
#include <FancyZonesLib/WorkArea.h>
#include <FancyZonesLib/WindowMoveHandler.h>
#include <FancyZonesLib/WindowUtils.h>
#include <FancyZonesLib/util.h>
#include "on_thread_executor.h"
@ -33,7 +35,6 @@
#include "util.h"
#include <FancyZonesLib/SecondaryMouseButtonsHook.h>
#include <winrt/Windows.UI.ViewManagement.h>
enum class DisplayChangeType
{
@ -50,18 +51,15 @@ namespace NonLocalizable
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:
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_settings(settings),
m_windowMoveHandler(settings, [this]() {
m_windowMoveHandler([this]() {
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]() {
PostMessage(m_window, WM_PRIV_VD_INIT, 0, 0);
},
@ -71,6 +69,8 @@ public:
{
this->disableModuleCallback = std::move(disableModuleCallback);
FancyZonesSettings::instance().LoadSettings();
FancyZonesDataInstance().ReplaceZoneSettingsFileFromOlderVersions();
LayoutTemplates::instance().LoadData();
CustomLayouts::instance().LoadData();
@ -87,23 +87,17 @@ public:
void MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen) noexcept
{
if (m_settings->GetSettings()->spanZonesAcrossMonitors)
if (FancyZonesSettings::settings().spanZonesAcrossMonitors)
{
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));
}
void MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen) noexcept
{
if (m_settings->GetSettings()->spanZonesAcrossMonitors)
if (FancyZonesSettings::settings().spanZonesAcrossMonitors)
{
monitor = NULL;
}
@ -174,8 +168,7 @@ private:
void RegisterVirtualDesktopUpdates() 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;
void MoveWindowIntoZone(HWND window, winrt::com_ptr<IWorkArea> workArea, const ZoneIndexSet& zoneIndexSet) noexcept;
bool MoveToAppLastZone(HWND window, HMONITOR active, HMONITOR primary) noexcept;
@ -190,8 +183,7 @@ private:
std::vector<HMONITOR> GetMonitorsSorted() noexcept;
HMONITOR WorkAreaKeyFromWindow(HWND window) noexcept;
bool GetSystemTheme() const noexcept;
ZoneColors GetZoneColors() const noexcept;
virtual void SettingsUpdate(SettingId type) override;
const HINSTANCE m_hinstance{};
@ -200,9 +192,6 @@ private:
MonitorWorkAreaHandler m_workAreaHandler;
VirtualDesktop m_virtualDesktop;
FileWatcher m_settingsFileWatcher;
winrt::com_ptr<IFancyZonesSettings> m_settings{};
GUID m_previousDesktopId{}; // UUID of previously active virtual desktop.
GUID m_currentDesktopId{}; // UUID of the current virtual desktop.
wil::unique_handle m_terminateEditorEvent; // Handle of FancyZonesEditor.exe we launch and wait on
@ -231,8 +220,6 @@ private:
};
std::function<void()> FancyZones::disableModuleCallback = {};
COLORREF currentAccentColor;
COLORREF currentBackgroundColor;
// IFancyZones
IFACEMETHODIMP_(void)
@ -253,11 +240,22 @@ FancyZones::Run() noexcept
return;
}
RegisterHotKey(m_window, static_cast<int>(HotkeyId::Editor), m_settings->GetSettings()->editorHotkey.get_modifiers(), m_settings->GetSettings()->editorHotkey.get_code());
if (m_settings->GetSettings()->windowSwitching)
if (!RegisterHotKey(m_window, static_cast<int>(HotkeyId::Editor), FancyZonesSettings::settings().editorHotkey.get_modifiers(), FancyZonesSettings::settings().editorHotkey.get_code()))
{
RegisterHotKey(m_window, static_cast<int>(HotkeyId::NextTab), m_settings->GetSettings()->nextTabHotkey.get_modifiers(), m_settings->GetSettings()->nextTabHotkey.get_code());
RegisterHotKey(m_window, static_cast<int>(HotkeyId::PrevTab), m_settings->GetSettings()->prevTabHotkey.get_modifiers(), m_settings->GetSettings()->prevTabHotkey.get_code());
Logger::error(L"Failed to register hotkey: {}", get_last_error_or_default(GetLastError()));
}
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();
@ -389,8 +387,8 @@ bool FancyZones::MoveToAppLastZone(HWND window, HMONITOR active, HMONITOR primar
void FancyZones::WindowCreated(HWND window) noexcept
{
const bool moveToAppLastZone = m_settings->GetSettings()->appLastZone_moveWindows;
const bool openOnActiveMonitor = m_settings->GetSettings()->openWindowOnActiveMonitor;
const bool moveToAppLastZone = FancyZonesSettings::settings().appLastZone_moveWindows;
const bool openOnActiveMonitor = FancyZonesSettings::settings().openWindowOnActiveMonitor;
if (!moveToAppLastZone && !openOnActiveMonitor)
{
// 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
// that belong to excluded applications list.
const bool isSplashScreen = FancyZonesUtils::IsSplashScreen(window);
const bool isSplashScreen = FancyZonesWindowUtils::IsSplashScreen(window);
if (isSplashScreen)
{
return;
@ -425,7 +423,7 @@ void FancyZones::WindowCreated(HWND window) noexcept
return;
}
const bool isCandidateForLastKnownZone = FancyZonesUtils::IsCandidateForZoning(window, m_settings->GetSettings()->excludedAppsArray);
const bool isCandidateForLastKnownZone = FancyZonesWindowUtils::IsCandidateForZoning(window);
if (!isCandidateForLastKnownZone)
{
return;
@ -477,7 +475,7 @@ FancyZones::OnKeyDown(PKBDLLHOOKSTRUCT info) noexcept
}
}
if (m_settings->GetSettings()->quickLayoutSwitch)
if (FancyZonesSettings::settings().quickLayoutSwitch)
{
int digitPressed = -1;
if ('0' <= info->vkCode && info->vkCode <= '9')
@ -526,7 +524,7 @@ void FancyZones::ToggleEditor() noexcept
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)
{
POINT currentCursorPos{};
@ -569,7 +567,7 @@ void FancyZones::ToggleEditor() noexcept
std::wstring params;
const std::wstring divider = L"/";
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 */
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)
{
OnSettingsChanged();
FancyZonesSettings::instance().LoadSettings();
}
else
{
@ -845,7 +843,7 @@ void FancyZones::OnDisplayChange(DisplayChangeType changeType) noexcept
if ((changeType == DisplayChangeType::WorkArea) || (changeType == DisplayChangeType::DisplayChange))
{
if (m_settings->GetSettings()->displayChange_moveWindows)
if (FancyZonesSettings::settings().displayChange_moveWindows)
{
UpdateWindowsPositions();
}
@ -895,7 +893,7 @@ void FancyZones::AddWorkArea(HMONITOR monitor, const std::wstring& deviceId) noe
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)
{
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
{
if (m_settings->GetSettings()->spanZonesAcrossMonitors)
if (FancyZonesSettings::settings().spanZonesAcrossMonitors)
{
AddWorkArea(nullptr, {});
}
@ -984,7 +982,7 @@ bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexce
HMONITOR current = WorkAreaKeyFromWindow(window);
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.
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);
// 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);
if (!moved)
{
FancyZonesUtils::RestoreWindowOrigin(window);
FancyZonesUtils::RestoreWindowSize(window);
FancyZonesWindowUtils::RestoreWindowOrigin(window);
FancyZonesWindowUtils::RestoreWindowSize(window);
}
else
{
@ -1053,7 +1051,7 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept
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.
// 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
auto window = GetForegroundWindow();
if (m_settings->GetSettings()->moveWindowsBasedOnPosition)
if (FancyZonesSettings::settings().moveWindowsBasedOnPosition)
{
return OnSnapHotkeyBasedOnPosition(window, vkCode);
}
@ -1231,6 +1229,11 @@ void FancyZones::RegisterVirtualDesktopUpdates() noexcept
void FancyZones::UpdateHotkey(int hotkeyId, const PowerToysSettings::HotkeyObject& hotkeyObject, bool enable) noexcept
{
if (!m_window)
{
return;
}
UnregisterHotKey(m_window, hotkeyId);
if (!enable)
@ -1248,28 +1251,34 @@ void FancyZones::UpdateHotkey(int hotkeyId, const PowerToysSettings::HotkeyObjec
}
}
void FancyZones::OnSettingsChanged() noexcept
void FancyZones::SettingsUpdate(SettingId id)
{
_TRACER_;
m_settings->ReloadSettings();
// Update the hotkeys
UpdateHotkey(static_cast<int>(HotkeyId::Editor), m_settings->GetSettings()->editorHotkey, true);
auto windowSwitching = m_settings->GetSettings()->windowSwitching;
UpdateHotkey(static_cast<int>(HotkeyId::NextTab), m_settings->GetSettings()->nextTabHotkey, windowSwitching);
UpdateHotkey(static_cast<int>(HotkeyId::PrevTab), m_settings->GetSettings()->prevTabHotkey, windowSwitching);
// Needed if we toggled spanZonesAcrossMonitors
m_workAreaHandler.Clear();
// update zone colors
m_workAreaHandler.UpdateZoneColors(GetZoneColors());
// update overlapping algorithm
m_workAreaHandler.UpdateOverlappingAlgorithm(m_settings->GetSettings()->overlappingZonesAlgorithm);
PostMessageW(m_window, WM_PRIV_VD_INIT, NULL, NULL);
switch (id)
{
case SettingId::EditorHotkey:
{
UpdateHotkey(static_cast<int>(HotkeyId::Editor), FancyZonesSettings::settings().editorHotkey, true);
}
break;
case SettingId::PrevTabHotkey:
{
UpdateHotkey(static_cast<int>(HotkeyId::PrevTab), FancyZonesSettings::settings().prevTabHotkey, FancyZonesSettings::settings().windowSwitching);
}
break;
case SettingId::NextTabHotkey:
{
UpdateHotkey(static_cast<int>(HotkeyId::NextTab), FancyZonesSettings::settings().nextTabHotkey, FancyZonesSettings::settings().windowSwitching);
}
break;
case SettingId::SpanZonesAcrossMonitors:
{
m_workAreaHandler.Clear();
PostMessageW(m_window, WM_PRIV_VD_INIT, NULL, NULL);
}
break;
default:
break;
}
}
void FancyZones::OnEditorExitEvent() noexcept
@ -1287,14 +1296,14 @@ void FancyZones::UpdateZoneSets() noexcept
workArea->UpdateActiveZoneSet();
}
auto moveWindows = m_settings->GetSettings()->zoneSetChange_moveWindows;
auto moveWindows = FancyZonesSettings::settings().zoneSetChange_moveWindows;
UpdateWindowsPositions(!moveWindows);
}
bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept
{
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);
@ -1303,7 +1312,7 @@ bool FancyZones::ShouldProcessSnapHotkey(DWORD vkCode) noexcept
{
if (vkCode == VK_UP || vkCode == VK_DOWN)
{
return m_settings->GetSettings()->moveWindowsBasedOnPosition;
return FancyZonesSettings::settings().moveWindowsBasedOnPosition;
}
else
{
@ -1346,7 +1355,7 @@ void FancyZones::ApplyQuickLayout(int key) 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))
{
@ -1385,7 +1394,7 @@ std::vector<std::pair<HMONITOR, RECT>> FancyZones::GetRawMonitorData() noexcept
HMONITOR FancyZones::WorkAreaKeyFromWindow(HWND window) noexcept
{
if (m_settings->GetSettings()->spanZonesAcrossMonitors)
if (FancyZonesSettings::settings().spanZonesAcrossMonitors)
{
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;
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);
return winrt::make_self<FancyZones>(hinstance, disableCallback);
}

View File

@ -52,4 +52,4 @@ interface __declspec(uuid("{2CB37E8F-87E6-4AEC-B4B2-E0FDC873343F}")) IFancyZones
(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" />
<ClInclude Include="SecondaryMouseButtonsHook.h" />
<ClInclude Include="Settings.h" />
<ClInclude Include="SettingsConstants.h" />
<ClInclude Include="SettingsObserver.h" />
<ClInclude Include="trace.h" />
<ClInclude Include="util.h" />
<ClInclude Include="VirtualDesktop.h" />
<ClInclude Include="WindowMoveHandler.h" />
<ClInclude Include="FancyZonesWindowProperties.h" />
<ClInclude Include="WindowUtils.h" />
<ClInclude Include="Zone.h" />
<ClInclude Include="ZoneColors.h" />
<ClInclude Include="Colors.h" />
<ClInclude Include="ZoneIndexSetBitmask.h" />
<ClInclude Include="ZoneSet.h" />
<ClInclude Include="WorkArea.h" />
<ClInclude Include="ZonesOverlay.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="Colors.cpp" />
<ClCompile Include="FancyZonesData\AppZoneHistory.cpp">
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">../pch.h</PrecompiledHeaderFile>
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">../pch.h</PrecompiledHeaderFile>
@ -117,6 +121,7 @@
<ClCompile Include="util.cpp" />
<ClCompile Include="VirtualDesktop.cpp" />
<ClCompile Include="WindowMoveHandler.cpp" />
<ClCompile Include="WindowUtils.cpp" />
<ClCompile Include="Zone.cpp" />
<ClCompile Include="ZoneSet.cpp" />
<ClCompile Include="WorkArea.cpp" />

View File

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

View File

@ -1,7 +1,7 @@
#include "pch.h"
#include "MonitorUtils.h"
#include <FancyZonesLib/util.h>
#include <FancyZonesLib/WindowUtils.h>
namespace MonitorUtils
{
@ -68,7 +68,7 @@ namespace MonitorUtils
if (GetMonitorInfo(monitor, &destMi))
{
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();
}
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.
*/
void Clear();
/**
* Update zone colors after settings changed
*/
void UpdateZoneColors(const ZoneColors& colors);
/**
* Update overlapping algorithm after settings changed
*/
void UpdateOverlappingAlgorithm(OverlappingZonesAlgorithm overlappingAlgorithm);
private:
// Work area is uniquely defined by monitor and virtual desktop id.

View File

@ -1,10 +1,15 @@
#include "pch.h"
#include <common/SettingsAPI/settings_objects.h>
#include <common/utils/resources.h>
#include "Settings.h"
#include "FancyZonesLib/Settings.h"
#include "FancyZonesLib/FancyZones.h"
#include "trace.h"
#include <common/logger/call_tracer.h>
#include <common/logger/logger.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
namespace NonLocalizable
@ -27,6 +32,8 @@ namespace NonLocalizable
const wchar_t ShowOnAllMonitorsID[] = L"fancyzones_show_on_all_monitors";
const wchar_t SpanZonesAcrossMonitorsID[] = L"fancyzones_span_zones_across_monitors";
const wchar_t MakeDraggedWindowTransparentID[] = L"fancyzones_makeDraggedWindowTransparent";
const wchar_t AllowPopupWindowSnapID[] = L"fancyzones_allowPopupWindowSnap";
const wchar_t AllowChildWindowSnapID[] = L"fancyzones_allowChildWindowSnap";
const wchar_t SystemThemeID[] = L"fancyzones_systemTheme";
const wchar_t ZoneColorID[] = L"fancyzones_zoneColor";
@ -40,214 +47,208 @@ namespace NonLocalizable
const wchar_t ExcludedAppsID[] = L"fancyzones_excluded_apps";
const wchar_t ZoneHighlightOpacityID[] = L"fancyzones_highlight_opacity";
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:
FancyZonesSettings(HINSTANCE hinstance, PCWSTR name, PCWSTR key) :
m_hinstance(hinstance),
m_moduleName(name),
m_moduleKey(key)
const std::wstring& settingsFileName = GetSettingsFileName();
m_settingsFileWatcher = std::make_unique<FileWatcher>(settingsFileName, [&]() {
PostMessageW(HWND_BROADCAST, WM_PRIV_SETTINGS_CHANGED, NULL, NULL);
});
}
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)
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
void FancyZonesSettings::SetBoolFlag(const PowerToysSettings::PowerToyValues& values, const wchar_t* id, SettingId notificationId, bool& out)
{
PowerToysSettings::Settings settings(m_hinstance, m_moduleName);
// Pass a string literal or a resource id to Settings::set_description().
settings.set_description(IDS_SETTING_DESCRIPTION);
settings.set_icon_key(NonLocalizable::IconKeyID);
settings.set_overview_link(NonLocalizable::OverviewURL);
settings.set_video_link(NonLocalizable::VideoURL);
// Add a custom action property. When using this settings type, the "PowertoyModuleIface::call_custom_action()"
// method should be overridden as well.
settings.add_custom_action(
NonLocalizable::ToggleEditorActionID, // action name.
IDS_SETTING_LAUNCH_EDITOR_LABEL,
IDS_SETTING_LAUNCH_EDITOR_BUTTON,
IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION);
settings.add_hotkey(NonLocalizable::EditorHotkeyID, IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL, m_settings.editorHotkey);
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)
if (const auto val = values.get_bool_value(id))
{
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)
FancyZonesSettings::SetConfig(PCWSTR serializedPowerToysSettingsJson) noexcept
void FancyZonesSettings::LoadSettings()
{
LoadSettings(serializedPowerToysSettingsJson, false /*fromFile*/);
SaveSettings();
}
_TRACER_;
IFACEMETHODIMP_(void)
FancyZonesSettings::ReloadSettings() noexcept
{
LoadSettings(m_moduleKey.c_str(), true /*fromFile*/);
}
void FancyZonesSettings::LoadSettings(PCWSTR config, bool fromFile) noexcept
{
try
{
PowerToysSettings::PowerToyValues values = fromFile ?
PowerToysSettings::PowerToyValues::load_from_settings_file(m_moduleKey) :
PowerToysSettings::PowerToyValues::from_json_string(config, m_moduleKey);
for (auto const& setting : m_configBools)
auto jsonOpt = json::from_file(GetSettingsFileName());
if (!jsonOpt)
{
if (const auto val = values.get_bool_value(setting.name))
{
*setting.value = *val;
}
Logger::warn(L"Failed to read from settings file");
return;
}
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))
{
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))
{
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))
{
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))
{
m_settings.zoneNumberColor = std::move(*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'))
if (m_settings.zoneNumberColor != *val)
{
view.remove_prefix(1);
}
while (!view.empty())
{
auto pos = (std::min)(view.find_first_of(L"\r\n"), view.length());
m_settings.excludedAppsArray.emplace_back(view.substr(0, pos));
view.remove_prefix(pos);
while (view.starts_with('\n') || view.starts_with('\r'))
{
view.remove_prefix(1);
}
m_settings.zoneNumberColor = std::move(*val);
NotifyObservers(SettingId::ZoneNumberColor);
}
}
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))
{
// Avoid undefined behavior
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.
Logger::error(L"Failed to read settings. {}", e.message());
MessageBox(NULL,
GET_RESOURCE_STRING(IDS_FANCYZONES_SETTINGS_LOAD_ERROR).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);
for (auto const& setting : m_configBools)
if (observer->WantsToBeNotified(id))
{
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
#include <unordered_set>
#include <common/SettingsAPI/settings_helpers.h>
#include <common/SettingsAPI/settings_objects.h>
#include <FancyZonesLib/ModuleConstants.h>
#include <FancyZonesLib/SettingsConstants.h>
class SettingsObserver;
class FileWatcher;
enum struct OverlappingZonesAlgorithm : int
{
Smallest = 0,
@ -34,6 +43,8 @@ struct Settings
bool makeDraggedWindowTransparent = true;
bool systemTheme = true;
bool showZoneNumber = true;
bool allowSnapPopupWindows = false;
bool allowSnapChildWindows = false;
std::wstring zoneColor = L"#AACDFF";
std::wstring zoneBorderColor = L"#FFFFFF";
std::wstring zoneHighlightColor = L"#008CFF";
@ -48,12 +59,38 @@ struct Settings
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;
IFACEMETHOD_(void, SetConfig)(PCWSTR serializedPowerToysSettings) = 0;
IFACEMETHOD_(void, ReloadSettings)() = 0;
IFACEMETHOD_(const Settings*, GetSettings)() const = 0;
};
public:
static FancyZonesSettings& instance();
static inline const Settings& settings()
{
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 "Settings.h"
#include "WorkArea.h"
#include "util.h"
#include <FancyZonesLib/WindowUtils.h>
// Non-Localizable strings
namespace NonLocalizable
@ -49,8 +49,7 @@ namespace WindowMoveHandlerUtils
}
}
WindowMoveHandler::WindowMoveHandler(const winrt::com_ptr<IFancyZonesSettings>& settings, const std::function<void()>& keyUpdateCallback) :
m_settings(settings),
WindowMoveHandler::WindowMoveHandler(const std::function<void()>& keyUpdateCallback) :
m_mouseState(false),
m_mouseHook(std::bind(&WindowMoveHandler::OnMouseDown, this)),
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
{
if (!FancyZonesUtils::IsCandidateForZoning(window, m_settings->GetSettings()->excludedAppsArray) || WindowMoveHandlerUtils::IsCursorTypeIndicatingSizeEvent())
if (!FancyZonesWindowUtils::IsCandidateForZoning(window) || WindowMoveHandlerUtils::IsCursorTypeIndicatingSizeEvent())
{
return;
}
m_draggedWindowInfo.hasNoVisibleOwner = FancyZonesUtils::HasNoVisibleOwner(window);
m_draggedWindowInfo.isStandardWindow = FancyZonesUtils::IsStandardWindow(window);
m_draggedWindowInfo.hasNoVisibleOwner = !FancyZonesWindowUtils::HasVisibleOwner(window);
m_draggedWindowInfo.isStandardWindow = FancyZonesWindowUtils::IsStandardWindow(window) && (!FancyZonesWindowUtils::IsPopupWindow(window) || FancyZonesSettings::settings().allowSnapPopupWindows);
m_inDragging = true;
auto iter = workAreaMap.find(monitor);
@ -78,7 +77,7 @@ void WindowMoveHandler::MoveSizeStart(HWND window, HMONITOR monitor, POINT const
m_draggedWindow = window;
if (m_settings->GetSettings()->mouseSwitch)
if (FancyZonesSettings::settings().mouseSwitch)
{
m_mouseHook.enable();
}
@ -97,7 +96,7 @@ void WindowMoveHandler::MoveSizeStart(HWND window, HMONITOR monitor, POINT const
m_draggedWindowWorkArea = iter->second;
SetWindowTransparency(m_draggedWindow);
m_draggedWindowWorkArea->MoveSizeEnter(m_draggedWindow);
if (m_settings->GetSettings()->showZonesOnAllMonitors)
if (FancyZonesSettings::settings().showZonesOnAllMonitors)
{
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.
m_draggedWindowWorkArea->ClearSelectedZones();
if (!m_settings->GetSettings()->showZonesOnAllMonitors)
if (!FancyZonesSettings::settings().showZonesOnAllMonitors)
{
m_draggedWindowWorkArea->HideZonesOverlay();
}
@ -219,12 +218,12 @@ void WindowMoveHandler::MoveSizeEnd(HWND window, POINT const& ptScreen, const st
auto workArea = std::move(m_draggedWindowWorkArea);
ResetWindowTransparency();
bool hasNoVisibleOwner = FancyZonesUtils::HasNoVisibleOwner(window);
bool isStandardWindow = FancyZonesUtils::IsStandardWindow(window);
bool hasNoVisibleOwner = !FancyZonesWindowUtils::HasVisibleOwner(window);
bool isStandardWindow = FancyZonesWindowUtils::IsStandardWindow(window);
if ((isStandardWindow == false && 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
// 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
{
if (m_settings->GetSettings()->restoreSize)
if (FancyZonesSettings::settings().restoreSize)
{
if (WindowMoveHandlerUtils::IsCursorTypeIndicatingSizeEvent())
{
::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 NonLocalizable;
using namespace FancyZonesUtils;
static bool warning_shown = false;
if (!is_process_elevated() && IsProcessOfWindowElevated(window))
if (!is_process_elevated() && FancyZonesWindowUtils::IsProcessOfWindowElevated(window))
{
m_dragEnabled = false;
if (!warning_shown && !is_toast_disabled(CantDragElevatedDontShowAgainRegistryPath, CantDragElevatedDisableIntervalInDays))
@ -335,7 +333,7 @@ void WindowMoveHandler::WarnIfElevationIsRequired(HWND window) noexcept
void WindowMoveHandler::UpdateDragState() noexcept
{
if (m_settings->GetSettings()->shiftDrag)
if (FancyZonesSettings::settings().shiftDrag)
{
m_dragEnabled = (m_shiftKeyState.state() ^ m_mouseState);
}
@ -347,7 +345,7 @@ void WindowMoveHandler::UpdateDragState() noexcept
void WindowMoveHandler::SetWindowTransparency(HWND window) noexcept
{
if (m_settings->GetSettings()->makeDraggedWindowTransparent)
if (FancyZonesSettings::settings().makeDraggedWindowTransparent)
{
m_windowTransparencyProperties.draggedWindowExstyle = GetWindowLong(window, GWL_EXSTYLE);
@ -364,7 +362,7 @@ void WindowMoveHandler::SetWindowTransparency(HWND window) 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);
SetWindowLong(m_windowTransparencyProperties.draggedWindow, GWL_EXSTYLE, m_windowTransparencyProperties.draggedWindowExstyle);

View File

@ -12,7 +12,7 @@ interface IWorkArea;
class WindowMoveHandler
{
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 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 ResetWindowTransparency() noexcept;
winrt::com_ptr<IFancyZonesSettings> m_settings{};
bool m_inDragging{}; // Whether or not a move/size operation is currently active
HWND m_draggedWindow{}; // The window that is being moved/sized
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/AppZoneHistory.h"
#include "FancyZonesDataTypes.h"
#include "SettingsObserver.h"
#include "ZonesOverlay.h"
#include "trace.h"
#include "util.h"
#include "on_thread_executor.h"
#include "Settings.h"
#include <FancyZonesLib/WindowUtils.h>
#include <ShellScalingApi.h>
#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);
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
ShowWindow(window, SW_SHOWNORMAL);
@ -110,7 +111,7 @@ public:
WorkArea(HINSTANCE hinstance);
~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 MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled, bool selectManyZones) noexcept;
@ -145,10 +146,6 @@ public:
ClearSelectedZones() noexcept;
IFACEMETHODIMP_(void)
FlashZones() noexcept;
IFACEMETHODIMP_(void)
SetZoneColors(const ZoneColors& colors) noexcept;
IFACEMETHODIMP_(void)
SetOverlappingZonesAlgorithm(OverlappingZonesAlgorithm overlappingAlgorithm) noexcept;
protected:
static LRESULT CALLBACK s_WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept;
@ -171,9 +168,6 @@ private:
WPARAM m_keyLast{};
size_t m_keyCycle{};
std::unique_ptr<ZonesOverlay> m_zonesOverlay;
ZoneColors m_zoneColors;
OverlappingZonesAlgorithm m_overlappingAlgorithm;
bool m_showZoneText;
};
WorkArea::WorkArea(HINSTANCE hinstance)
@ -192,12 +186,8 @@ WorkArea::~WorkArea()
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;
m_monitor = monitor;
if (monitor)
@ -278,7 +268,7 @@ IFACEMETHODIMP WorkArea::MoveSizeUpdate(POINT const& ptScreen, bool dragEnabled,
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;
@ -297,7 +287,7 @@ IFACEMETHODIMP WorkArea::MoveSizeEnd(HWND window, POINT const& ptScreen) noexcep
MapWindowPoints(nullptr, m_window, &ptClient, 1);
m_zoneSet->MoveWindowIntoZoneByIndexSet(window, m_window, m_highlightZone);
if (FancyZonesUtils::HasNoVisibleOwner(window))
if (!FancyZonesWindowUtils::HasVisibleOwner(window))
{
SaveWindowProcessToZoneIndex(window);
}
@ -331,7 +321,7 @@ WorkArea::MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool
{
if (m_zoneSet->MoveWindowIntoZoneByDirectionAndIndex(window, m_window, vkCode, cycle))
{
if (FancyZonesUtils::HasNoVisibleOwner(window))
if (!FancyZonesWindowUtils::HasVisibleOwner(window))
{
SaveWindowProcessToZoneIndex(window);
}
@ -408,7 +398,7 @@ WorkArea::ShowZonesOverlay() noexcept
if (m_window)
{
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();
}
}
@ -428,11 +418,11 @@ WorkArea::HideZonesOverlay() noexcept
IFACEMETHODIMP_(void)
WorkArea::UpdateActiveZoneSet() noexcept
{
CalculateZoneSet(m_overlappingAlgorithm);
CalculateZoneSet(FancyZonesSettings::settings().overlappingZonesAlgorithm);
if (m_window)
{
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())
{
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)
{
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();
}
}
IFACEMETHODIMP_(void)
WorkArea::SetZoneColors(const ZoneColors& colors) noexcept
{
m_zoneColors = colors;
}
IFACEMETHODIMP_(void)
WorkArea::SetOverlappingZonesAlgorithm(OverlappingZonesAlgorithm overlappingAlgorithm) noexcept
{
m_overlappingAlgorithm = overlappingAlgorithm;
}
#pragma region private
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
@ -631,10 +608,10 @@ LRESULT CALLBACK WorkArea::s_WndProc(HWND window, UINT message, WPARAM wparam, L
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);
if (self->Init(hinstance, monitor, uniqueId, parentUniqueId, zoneColors, overlappingAlgorithm, showZoneText))
if (self->Init(hinstance, monitor, uniqueId, parentUniqueId))
{
return self;
}

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
#include "pch.h"
#include "util.h"
#include "Settings.h"
#include <common/display/dpi_aware.h>
#include <common/utils/process_path.h>
@ -9,35 +8,10 @@
#include <array>
#include <complex>
#include <wil/Resource.h>
#include <fancyzones/FancyZonesLib/FancyZonesDataTypes.h>
#include <fancyzones/FancyZonesLib/FancyZonesWindowProperties.h>
#include <common/display/dpi_aware.h>
// Non-Localizable strings
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;
}
}
#include <FancyZonesLib/FancyZonesWindowProperties.h>
namespace FancyZonesUtils
{
@ -200,337 +174,6 @@ namespace FancyZonesUtils
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)
{
GUID id;
@ -716,44 +359,4 @@ namespace FancyZonesUtils
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
#include "gdiplus.h"
#include <common/utils/string_utils.h>
namespace FancyZonesDataTypes
{
struct DeviceIdData;
}
namespace FancyZonesUtils
{
struct Rect
@ -40,16 +34,6 @@ namespace FancyZonesUtils
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)
{
ZeroMemory(quad, sizeof(*quad));
@ -189,23 +173,6 @@ namespace FancyZonesUtils
UINT GetDpiForMonitor(HMONITOR monitor) noexcept;
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);
std::optional<GUID> GuidFromString(const std::wstring& str) noexcept;
std::optional<std::wstring> GuidToString(const GUID& guid) noexcept;
@ -215,10 +182,5 @@ namespace FancyZonesUtils
std::wstring TrimDeviceId(const std::wstring& deviceId);
RECT PrepareRectForCycling(RECT windowRect, RECT workAreaRect, DWORD vkCode) 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);
size_t ChooseNextZoneByPosition(DWORD vkCode, RECT windowRect, const std::vector<RECT>& zoneRects) noexcept;
}

View File

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

View File

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

View File

@ -4,6 +4,7 @@
#include <FancyZonesLib/Settings.h>
#include <FancyZonesLib/FancyZones.h>
#include <FancyZonesLib/ModuleConstants.h>
#include <common/SettingsAPI/settings_helpers.h>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
@ -58,503 +59,106 @@ namespace FancyZonesUnitTests
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;
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);
ptSettings.set_description(IDS_SETTING_DESCRIPTION);
ptSettings.set_icon_key(L"pt-fancy-zones");
ptSettings.set_overview_link(L"https://aka.ms/PowerToysOverview_FancyZones");
ptSettings.set_video_link(L"https://youtu.be/rTtGzZYAXgY");
// reset to defaults
PowerToysSettings::PowerToyValues values(NonLocalizable::ModuleKey, NonLocalizable::ModuleKey);
values.add_property(L"fancyzones_shiftDrag", m_defaultSettings.shiftDrag);
values.add_property(L"fancyzones_mouseSwitch", m_defaultSettings.mouseSwitch);
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(
L"ToggledFZEditor", // action name.
IDS_SETTING_LAUNCH_EDITOR_LABEL,
IDS_SETTING_LAUNCH_EDITOR_BUTTON,
IDS_SETTING_LAUNCH_EDITOR_DESCRIPTION);
ptSettings.add_hotkey(L"fancyzones_editor_hotkey", IDS_SETTING_LAUNCH_EDITOR_HOTKEY_LABEL, settings.editorHotkey);
ptSettings.add_bool_toggle(L"fancyzones_windowSwitching", IDS_SETTING_WINDOW_SWITCHING_TOGGLE_LABEL, settings.windowSwitching);
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();
json::to_file(FancyZonesSettings::GetSettingsFileName(), values.get_raw_json());
FancyZonesSettings::instance().LoadSettings();
}
TEST_METHOD_CLEANUP(Cleanup)
{
std::filesystem::remove(FancyZonesSettings::GetSettingsFileName());
}
TEST_METHOD_INITIALIZE(Init)
{
HINSTANCE hInst = (HINSTANCE)GetModuleHandleW(nullptr);
TEST_METHOD (Parse)
{
//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);
Assert::IsTrue(m_settings != nullptr);
}
PowerToysSettings::PowerToyValues values(NonLocalizable::ModuleKey, NonLocalizable::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);
TEST_METHOD_CLEANUP(Cleanup)
{
std::filesystem::remove_all(PTSettingsHelper::get_module_save_folder_location(m_moduleName));
}
json::to_file(FancyZonesSettings::GetSettingsFileName(), values.get_raw_json());
FancyZonesSettings::instance().LoadSettings();
auto actual = FancyZonesSettings::settings();
compareSettings(expected, actual);
}
TEST_METHOD (GetConfig)
{
int expectedSize = 0;
m_settings->GetConfig(nullptr, &expectedSize);
Assert::AreNotEqual(0, expectedSize);
TEST_METHOD (ParseInvalid)
{
PowerToysSettings::PowerToyValues values(NonLocalizable::ModuleKey, NonLocalizable::ModuleKey);
values.add_property(L"non_fancyzones_value", false);
int actualBufferSize = expectedSize;
PWSTR actualBuffer = new wchar_t[actualBufferSize];
json::to_file(FancyZonesSettings::GetSettingsFileName(), values.get_raw_json());
Assert::IsTrue(m_settings->GetConfig(actualBuffer, &actualBufferSize));
Assert::AreEqual(expectedSize, actualBufferSize);
}
FancyZonesSettings::instance().LoadSettings();
auto actual = FancyZonesSettings::settings();
compareSettings(m_defaultSettings, actual);
}
TEST_METHOD (GetConfigSmallBuffer)
{
int size = 0;
m_settings->GetConfig(nullptr, &size);
Assert::AreNotEqual(0, size);
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));
}
TEST_METHOD (ParseEmpty)
{
FancyZonesSettings::instance().LoadSettings();
auto actual = FancyZonesSettings::settings();
compareSettings(m_defaultSettings, actual);
}
};
}

View File

@ -10,7 +10,6 @@
#include <FancyZonesLib/FancyZonesData/AppZoneHistory.h>
#include <FancyZonesLib/FancyZonesDataTypes.h>
#include <FancyZonesLib/JsonHelpers.h>
#include <FancyZonesLib/ZoneColors.h>
#include "Util.h"
#include <common/utils/process_path.h>
@ -31,9 +30,6 @@ namespace FancyZonesUnitTests
HMONITOR m_monitor{};
MONITORINFOEX m_monitorInfo{};
GUID m_virtualDesktopGuid{};
ZoneColors m_zoneColors{};
OverlappingZonesAlgorithm m_overlappingAlgorithm = OverlappingZonesAlgorithm::Positional;
bool m_showZoneText = true;
void testWorkArea(winrt::com_ptr<IWorkArea> workArea)
{
@ -65,13 +61,6 @@ namespace FancyZonesUnitTests
Assert::IsTrue(guid.has_value());
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();
AppliedLayouts::instance().LoadData();
}
@ -84,7 +73,7 @@ namespace FancyZonesUnitTests
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);
auto* zoneSet{ workArea->ZoneSet() };
@ -95,7 +84,7 @@ namespace FancyZonesUnitTests
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);
auto* zoneSet{ workArea->ZoneSet() };
@ -106,7 +95,7 @@ namespace FancyZonesUnitTests
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);
auto* zoneSet{ workArea->ZoneSet() };
@ -117,7 +106,7 @@ namespace FancyZonesUnitTests
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);
}
@ -136,7 +125,7 @@ namespace FancyZonesUnitTests
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 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();
}
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);
Assert::IsNotNull(workArea.get());
@ -185,10 +174,10 @@ namespace FancyZonesUnitTests
const int zoneCount = 5;
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
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());
@ -209,9 +198,6 @@ namespace FancyZonesUnitTests
HINSTANCE m_hInst{};
HMONITOR m_monitor{};
MONITORINFO m_monitorInfo{};
ZoneColors m_zoneColors{};
OverlappingZonesAlgorithm m_overlappingAlgorithm = OverlappingZonesAlgorithm::Positional;
bool m_showZoneText = true;
TEST_METHOD_INITIALIZE(Init)
{
@ -225,13 +211,6 @@ namespace FancyZonesUnitTests
m_uniqueId.width = m_monitorInfo.rcMonitor.right - m_monitorInfo.rcMonitor.left;
m_uniqueId.height = m_monitorInfo.rcMonitor.bottom - m_monitorInfo.rcMonitor.top;
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();
AppliedLayouts::instance().LoadData();
@ -246,7 +225,7 @@ namespace FancyZonesUnitTests
public:
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 actual = workArea->MoveSizeEnter(Mocks::Window());
@ -256,7 +235,7 @@ namespace FancyZonesUnitTests
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;
@ -268,7 +247,7 @@ namespace FancyZonesUnitTests
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 actual = workArea->MoveSizeUpdate(POINT{ 0, 0 }, true, false);
@ -278,7 +257,7 @@ namespace FancyZonesUnitTests
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 actual = workArea->MoveSizeUpdate(POINT{ -10, -10 }, true, false);
@ -288,7 +267,7 @@ namespace FancyZonesUnitTests
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 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)
{
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();
workArea->MoveSizeEnter(window);
@ -315,7 +294,7 @@ namespace FancyZonesUnitTests
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();
workArea->MoveSizeEnter(window);
@ -331,7 +310,7 @@ namespace FancyZonesUnitTests
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();
workArea->MoveSizeEnter(window);
@ -344,7 +323,7 @@ namespace FancyZonesUnitTests
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 actual = workArea->MoveSizeEnd(Mocks::Window(), POINT{ 0, 0 });
@ -354,7 +333,7 @@ namespace FancyZonesUnitTests
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();
workArea->MoveSizeEnter(window);
@ -371,7 +350,7 @@ namespace FancyZonesUnitTests
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());
workArea->MoveWindowIntoZoneByIndex(Mocks::Window(), 0);
@ -381,7 +360,7 @@ namespace FancyZonesUnitTests
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());
const auto window = Mocks::WindowCreate(m_hInst);
@ -396,7 +375,7 @@ namespace FancyZonesUnitTests
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());
const auto window = Mocks::WindowCreate(m_hInst);
@ -413,7 +392,7 @@ namespace FancyZonesUnitTests
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());
workArea->SaveWindowProcessToZoneIndex(nullptr);
@ -424,7 +403,7 @@ namespace FancyZonesUnitTests
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());
auto window = Mocks::WindowCreate(m_hInst);
@ -439,7 +418,7 @@ namespace FancyZonesUnitTests
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());
const auto window = Mocks::WindowCreate(m_hInst);
@ -467,7 +446,7 @@ namespace FancyZonesUnitTests
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());
auto window = Mocks::WindowCreate(m_hInst);
@ -497,7 +476,7 @@ namespace FancyZonesUnitTests
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());
auto window = Mocks::WindowCreate(m_hInst);

View File

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

View File

@ -84,6 +84,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
_showOnAllMonitors = Settings.Properties.FancyzonesShowOnAllMonitors.Value;
_spanZonesAcrossMonitors = Settings.Properties.FancyzonesSpanZonesAcrossMonitors.Value;
_makeDraggedWindowTransparent = Settings.Properties.FancyzonesMakeDraggedWindowTransparent.Value;
_allowPopupWindowSnap = Settings.Properties.FancyzonesAllowPopupWindowSnap.Value;
_allowChildWindowSnap = Settings.Properties.FancyzonesAllowChildWindowSnap.Value;
_highlightOpacity = Settings.Properties.FancyzonesHighlightOpacity.Value;
_excludedApps = Settings.Properties.FancyzonesExcludedApps.Value;
_systemTheme = Settings.Properties.FancyzonesSystemTheme.Value;
@ -131,6 +133,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
private bool _makeDraggedWindowTransparent;
private bool _systemTheme;
private bool _showZoneNumber;
private bool _allowPopupWindowSnap;
private bool _allowChildWindowSnap;
private int _highlightOpacity;
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
// we expect value to be a hex code.
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>
<comment>giving credit to the projects this utility was based on</comment>
</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>

View File

@ -183,6 +183,12 @@
<CheckBox x:Uid="FancyZones_RestoreSize" IsChecked="{x:Bind Mode=TwoWay, Path=ViewModel.RestoreSize}" Margin="{StaticResource ExpanderSettingMargin}"/>
<Rectangle Style="{StaticResource ExpanderSeparatorStyle}" />
<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>
</controls:SettingExpander.Content>