Refactor common and fancyzones/utils (#6073)

* Move module-specific functions to module

* Refactor FZ utils

* Remove 'zonable' from FancyZonesWindowInfo

* Address PR comments

* Address PR comments

* Fix stuff after rebasing
This commit is contained in:
stefansjfw 2020-08-24 19:38:15 +02:00 committed by GitHub
parent 2817bf4d62
commit 9999a2b126
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 705 additions and 690 deletions

View file

@ -56,12 +56,10 @@ std::optional<POINT> get_mouse_pos()
}
}
// Test if a window is part of the shell or the task bar.
// We compare the HWND against HWND of the desktop and shell windows,
// we also filter out some window class names know to belong to
// the taskbar.
static bool is_system_window(HWND hwnd, const char* class_name)
bool is_system_window(HWND hwnd, const char* class_name)
{
// We compare the HWND against HWND of the desktop and shell windows,
// we also filter out some window class names know to belong to the taskbar.
static auto system_classes = { "SysListView32", "WorkerW", "Shell_TrayWnd", "Shell_SecondaryTrayWnd", "Progman" };
static auto system_hwnds = { GetDesktopWindow(), GetShellWindow() };
for (auto system_hwnd : system_hwnds)
@ -81,125 +79,6 @@ static bool is_system_window(HWND hwnd, const char* class_name)
return false;
}
static bool no_visible_owner(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
}
// Return false (and allow the window to be zonable) if the owner window size is zero
// It is enough that the window is zero-sized in one dimension only.
return rect.top == rect.bottom || rect.left == rect.right;
}
FancyZonesFilter get_fancyzones_filtered_window(HWND window)
{
FancyZonesFilter result;
if (GetAncestor(window, GA_ROOT) != window || !IsWindowVisible(window))
{
return result;
}
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 result;
}
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 result;
}
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 result;
}
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 result;
}
result.process_path = std::move(process_path);
result.standard_window = true;
result.no_visible_owner = no_visible_owner(window);
result.zonable = result.standard_window && result.no_visible_owner;
return result;
}
ShortcutGuideFilter get_shortcutguide_filtered_window()
{
ShortcutGuideFilter result;
auto active_window = GetForegroundWindow();
active_window = GetAncestor(active_window, GA_ROOT);
if (!IsWindowVisible(active_window))
{
return result;
}
auto style = GetWindowLong(active_window, GWL_STYLE);
auto exStyle = GetWindowLong(active_window, GWL_EXSTYLE);
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 result;
}
std::array<char, 256> class_name;
GetClassNameA(active_window, class_name.data(), static_cast<int>(class_name.size()));
if (is_system_window(active_window, class_name.data()))
{
return result;
}
static HWND cortana_hwnd = nullptr;
if (cortana_hwnd == nullptr)
{
if (strcmp(class_name.data(), "Windows.UI.Core.CoreWindow") == 0 &&
get_process_path(active_window).ends_with(L"SearchUI.exe"))
{
cortana_hwnd = active_window;
return result;
}
}
else if (cortana_hwnd == active_window)
{
return result;
}
result.hwnd = active_window;
// In reality, Windows Snap works if even one of those styles is set
// for a window, it is just limited. If there is no WS_MAXIMIZEBOX using
// WinKey + Up just won't maximize the window. Similary, without
// WS_MINIMIZEBOX the window will not get minimized. A "Save As..." dialog
// is a example of such window - it can be snapped to both sides and to
// all screen corners, but will not get maximized nor minimized.
// For now, since ShortcutGuide can only disable entire "Windows Controls"
// group, we require that the window supports all the options.
result.snappable = ((style & WS_MAXIMIZEBOX) == WS_MAXIMIZEBOX) &&
((style & WS_MINIMIZEBOX) == WS_MINIMIZEBOX) &&
((style & WS_THICKFRAME) == WS_THICKFRAME);
return result;
}
int width(const RECT& rect)
{
return rect.right - rect.left;

View file

@ -13,24 +13,8 @@ std::optional<RECT> get_button_pos(HWND hwnd);
std::optional<RECT> get_window_pos(HWND hwnd);
// Gets mouse position.
std::optional<POINT> get_mouse_pos();
// Test if window can be zoned by FancyZones
struct FancyZonesFilter
{
bool zonable = false; // If the window is zonable by FancyZones by default - true when both standard_window and no_visible_owner are also true
bool standard_window = false; // True if from the styles the window looks like a standard window
bool no_visible_owner = false; // True if the window is a top-level window that does not have a visible owner
std::wstring process_path; // Path to the executable owning the window
};
FancyZonesFilter get_fancyzones_filtered_window(HWND window);
// Gets active foreground window, filtering out all "non standard" windows like the taskbar, etc.
struct ShortcutGuideFilter
{
HWND hwnd = nullptr; // Handle to the top-level foreground window or nullptr if there is no such window
bool snappable = false; // True, if the window can react to Windows Snap keys
};
ShortcutGuideFilter get_shortcutguide_filtered_window();
// Check if window is part of the shell or the taskbar.
bool is_system_window(HWND hwnd, const char* class_name);
// Calculate sizes
int width(const RECT& rect);

View file

@ -350,7 +350,7 @@ bool FancyZones::ShouldProcessNewWindow(HWND window) noexcept
// that belong to excluded applications list.
if (IsSplashScreen(window) ||
(reinterpret_cast<size_t>(::GetProp(window, ZonedWindowProperties::PropertyMultipleZoneID)) != 0) ||
!IsInterestingWindow(window, m_settings->GetSettings()->excludedAppsArray))
!FancyZonesUtils::IsInterestingWindow(window, m_settings->GetSettings()->excludedAppsArray))
{
return false;
}
@ -482,7 +482,7 @@ void OpenWindowOnActiveMonitor(HWND window, HMONITOR monitor) noexcept
if (GetMonitorInfo(monitor, &destMi))
{
RECT newPosition = FitOnScreen(placement.rcNormalPosition, originMi.rcWork, destMi.rcWork);
SizeWindowToRect(window, newPosition);
FancyZonesUtils::SizeWindowToRect(window, newPosition);
}
}
}
@ -642,7 +642,7 @@ void FancyZones::ToggleEditor() noexcept
std::vector<std::pair<HMONITOR, RECT>> allMonitors;
m_dpiUnawareThread.submit(OnThreadExecutor::task_t{ [&] {
allMonitors = GetAllMonitorRects<&MONITORINFOEX::rcWork>();
allMonitors = FancyZonesUtils::GetAllMonitorRects<&MONITORINFOEX::rcWork>();
} }).wait();
for (auto& [monitor, workArea] : allMonitors)
@ -1031,7 +1031,7 @@ void FancyZones::UpdateWindowsPositions() noexcept
void FancyZones::CycleActiveZoneSet(DWORD vkCode) noexcept
{
auto window = GetForegroundWindow();
if (IsInterestingWindow(window, m_settings->GetSettings()->excludedAppsArray))
if (FancyZonesUtils::IsInterestingWindow(window, m_settings->GetSettings()->excludedAppsArray))
{
const HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL);
if (monitor)
@ -1100,8 +1100,8 @@ bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexce
bool moved = m_windowMoveHandler.MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */, m_workAreaHandler.GetWorkArea(m_currentDesktopId, current));
if (!moved)
{
RestoreWindowOrigin(window);
RestoreWindowSize(window);
FancyZonesUtils::RestoreWindowOrigin(window);
FancyZonesUtils::RestoreWindowSize(window);
}
return true;
}
@ -1127,7 +1127,7 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept
current = MonitorFromWindow(window, MONITOR_DEFAULTTONULL);
}
auto allMonitors = GetAllMonitorRects<&MONITORINFOEX::rcWork>();
auto allMonitors = FancyZonesUtils::GetAllMonitorRects<&MONITORINFOEX::rcWork>();
if (current && allMonitors.size() > 1 && m_settings->GetSettings()->moveWindowAcrossMonitors)
{
@ -1181,7 +1181,7 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept
return false;
}
size_t chosenIdx = ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
size_t chosenIdx = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
if (chosenIdx < zoneRects.size())
{
@ -1222,9 +1222,9 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept
return false;
}
RECT combinedRect = GetAllMonitorsCombinedRect<&MONITORINFOEX::rcWork>();
windowRect = PrepareRectForCycling(windowRect, combinedRect, vkCode);
chosenIdx = ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
RECT combinedRect = FancyZonesUtils::GetAllMonitorsCombinedRect<&MONITORINFOEX::rcWork>();
windowRect = FancyZonesUtils::PrepareRectForCycling(windowRect, combinedRect, vkCode);
chosenIdx = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
if (chosenIdx < zoneRects.size())
{
// Moving to another monitor succeeded
@ -1248,7 +1248,7 @@ bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept
bool FancyZones::OnSnapHotkey(DWORD vkCode) noexcept
{
auto window = GetForegroundWindow();
if (IsInterestingWindow(window, m_settings->GetSettings()->excludedAppsArray))
if (FancyZonesUtils::IsInterestingWindow(window, m_settings->GetSettings()->excludedAppsArray))
{
if (m_settings->GetSettings()->moveWindowsBasedOnPosition)
{
@ -1335,7 +1335,7 @@ std::vector<HMONITOR> FancyZones::GetMonitorsSorted() noexcept
std::shared_lock readLock(m_lock);
auto monitorInfo = GetRawMonitorData();
OrderMonitors(monitorInfo);
FancyZonesUtils::OrderMonitors(monitorInfo);
std::vector<HMONITOR> output;
std::transform(std::begin(monitorInfo), std::end(monitorInfo), std::back_inserter(output), [](const auto& info) { return info.first; });
return output;

View file

@ -90,7 +90,7 @@ namespace
data.deviceId = json.GetNamedString(NonLocalizable::DeviceIdStr);
data.zoneSetUuid = json.GetNamedString(NonLocalizable::ZoneSetUuidStr);
if (!IsValidGuid(data.zoneSetUuid) || !IsValidDeviceId(data.deviceId))
if (!FancyZonesUtils::IsValidGuid(data.zoneSetUuid) || !FancyZonesUtils::IsValidDeviceId(data.deviceId))
{
return std::nullopt;
}
@ -250,7 +250,7 @@ namespace JSONHelpers
CustomZoneSetJSON result;
result.uuid = customZoneSet.GetNamedString(NonLocalizable::UuidStr);
if (!IsValidGuid(result.uuid))
if (!FancyZonesUtils::IsValidGuid(result.uuid))
{
return std::nullopt;
}
@ -314,7 +314,7 @@ namespace JSONHelpers
zoneSetData.uuid = zoneSet.GetNamedString(NonLocalizable::UuidStr);
zoneSetData.type = FancyZonesDataTypes::TypeFromString(std::wstring{ zoneSet.GetNamedString(NonLocalizable::TypeStr) });
if (!IsValidGuid(zoneSetData.uuid))
if (!FancyZonesUtils::IsValidGuid(zoneSetData.uuid))
{
return std::nullopt;
}
@ -415,7 +415,7 @@ namespace JSONHelpers
DeviceInfoJSON result;
result.deviceId = device.GetNamedString(NonLocalizable::DeviceIdStr);
if (!IsValidDeviceId(result.deviceId))
if (!FancyZonesUtils::IsValidDeviceId(result.deviceId))
{
return std::nullopt;
}

View file

@ -172,7 +172,7 @@ bool WindowMoveHandler::MoveWindowIntoZoneByDirectionAndPosition(HWND window, DW
void WindowMoveHandlerPrivate::MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen, const std::unordered_map<HMONITOR, winrt::com_ptr<IZoneWindow>>& zoneWindowMap) noexcept
{
if (!IsInterestingWindow(window, m_settings->GetSettings()->excludedAppsArray) || WindowMoveHandlerUtils::IsCursorTypeIndicatingSizeEvent())
if (!FancyZonesUtils::IsInterestingWindow(window, m_settings->GetSettings()->excludedAppsArray) || WindowMoveHandlerUtils::IsCursorTypeIndicatingSizeEvent())
{
return;
}
@ -297,7 +297,7 @@ void WindowMoveHandlerPrivate::MoveSizeUpdate(HMONITOR monitor, POINT const& ptS
void WindowMoveHandlerPrivate::MoveSizeEnd(HWND window, POINT const& ptScreen, const std::unordered_map<HMONITOR, winrt::com_ptr<IZoneWindow>>& zoneWindowMap) noexcept
{
if (window != m_windowMoveSize && !IsInterestingWindow(window, m_settings->GetSettings()->excludedAppsArray))
if (window != m_windowMoveSize && !FancyZonesUtils::IsInterestingWindow(window, m_settings->GetSettings()->excludedAppsArray))
{
return;
}
@ -320,9 +320,9 @@ void WindowMoveHandlerPrivate::MoveSizeEnd(HWND window, POINT const& ptScreen, c
{
::RemoveProp(window, ZonedWindowProperties::PropertyRestoreSizeID);
}
else if (!IsWindowMaximized(window))
else if (!FancyZonesUtils::IsWindowMaximized(window))
{
RestoreWindowSize(window);
FancyZonesUtils::RestoreWindowSize(window);
}
}

View file

@ -10,6 +10,8 @@
#include <utility>
using namespace FancyZonesUtils;
namespace
{
constexpr int C_MULTIPLIER = 10000;
@ -399,7 +401,7 @@ ZoneSet::MoveWindowIntoZoneByDirectionAndPosition(HWND window, HWND windowZone,
windowRect.left -= windowZoneRect.left;
windowRect.right -= windowZoneRect.left;
size_t result = ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
size_t result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
if (result < zoneRects.size())
{
MoveWindowIntoZoneByIndex(window, windowZone, freeZoneIndices[result]);
@ -408,8 +410,8 @@ ZoneSet::MoveWindowIntoZoneByDirectionAndPosition(HWND window, HWND windowZone,
else if (cycle)
{
// Try again from the position off the screen in the opposite direction to vkCode
windowRect = PrepareRectForCycling(windowRect, windowZoneRect, vkCode);
result = ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
windowRect = FancyZonesUtils::PrepareRectForCycling(windowRect, windowZoneRect, vkCode);
result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
if (result < zoneRects.size())
{

View file

@ -22,6 +22,8 @@ namespace NonLocalizable
const wchar_t ToolWindowClassName[] = L"SuperFancyZones_ZoneWindow";
}
using namespace FancyZonesUtils;
namespace ZoneWindowUtils
{
std::wstring GenerateUniqueId(HMONITOR monitor, PCWSTR deviceId, PCWSTR virtualDesktopId)

View file

@ -15,460 +15,531 @@ namespace NonLocalizable
const wchar_t PowerToysAppFZEditor[] = L"FANCYZONESEDITOR.EXE";
}
typedef BOOL(WINAPI* GetDpiForMonitorInternalFunc)(HMONITOR, UINT, UINT*, UINT*);
UINT GetDpiForMonitor(HMONITOR monitor) noexcept
namespace
{
UINT dpi{};
if (wil::unique_hmodule user32{ LoadLibrary(L"user32.dll") })
bool HasNoVisibleOwner(HWND window) noexcept
{
if (auto func = reinterpret_cast<GetDpiForMonitorInternalFunc>(GetProcAddress(user32.get(), "GetDpiForMonitorInternal")))
auto owner = GetWindow(window, GW_OWNER);
if (owner == nullptr)
{
func(monitor, 0, &dpi, &dpi);
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;
}
if (dpi == 0)
FancyZonesUtils::FancyZonesWindowInfo GetFancyZonesWindowInfo(HWND window)
{
if (wil::unique_hdc hdc{ GetDC(nullptr) })
FancyZonesUtils::FancyZonesWindowInfo result;
if (GetAncestor(window, GA_ROOT) != window || !IsWindowVisible(window))
{
dpi = GetDeviceCaps(hdc.get(), LOGPIXELSX);
return result;
}
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 result;
}
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 result;
}
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 result;
}
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 result;
}
result.processPath = std::move(process_path);
result.standardWindow = true;
result.noVisibleOwner = HasNoVisibleOwner(window);
return result;
}
return (dpi == 0) ? DPIAware::DEFAULT_DPI : dpi;
}
void OrderMonitors(std::vector<std::pair<HMONITOR, RECT>>& monitorInfo)
namespace FancyZonesUtils
{
const size_t nMonitors = monitorInfo.size();
// blocking[i][j] - whether monitor i blocks monitor j in the ordering, i.e. monitor i should go before monitor j
std::vector<std::vector<bool>> blocking(nMonitors, std::vector<bool>(nMonitors, false));
// blockingCount[j] - the number of monitors which block monitor j
std::vector<size_t> blockingCount(nMonitors, 0);
for (size_t i = 0; i < nMonitors; i++)
typedef BOOL(WINAPI* GetDpiForMonitorInternalFunc)(HMONITOR, UINT, UINT*, UINT*);
UINT GetDpiForMonitor(HMONITOR monitor) noexcept
{
RECT rectI = monitorInfo[i].second;
for (size_t j = 0; j < nMonitors; j++)
UINT dpi{};
if (wil::unique_hmodule user32{ LoadLibrary(L"user32.dll") })
{
RECT rectJ = monitorInfo[j].second;
blocking[i][j] = rectI.top < rectJ.bottom && rectI.left < rectJ.right && i != j;
if (blocking[i][j])
if (auto func = reinterpret_cast<GetDpiForMonitorInternalFunc>(GetProcAddress(user32.get(), "GetDpiForMonitorInternal")))
{
blockingCount[j]++;
func(monitor, 0, &dpi, &dpi);
}
}
if (dpi == 0)
{
if (wil::unique_hdc hdc{ GetDC(nullptr) })
{
dpi = GetDeviceCaps(hdc.get(), LOGPIXELSX);
}
}
return (dpi == 0) ? DPIAware::DEFAULT_DPI : dpi;
}
// used[i] - whether the sorting algorithm has used monitor i so far
std::vector<bool> used(nMonitors, false);
// the sorted sequence of monitors
std::vector<std::pair<HMONITOR, RECT>> sortedMonitorInfo;
for (size_t iteration = 0; iteration < nMonitors; iteration++)
void OrderMonitors(std::vector<std::pair<HMONITOR, RECT>>& monitorInfo)
{
// Indices of candidates to become the next monitor in the sequence
std::vector<size_t> candidates;
const size_t nMonitors = monitorInfo.size();
// blocking[i][j] - whether monitor i blocks monitor j in the ordering, i.e. monitor i should go before monitor j
std::vector<std::vector<bool>> blocking(nMonitors, std::vector<bool>(nMonitors, false));
// blockingCount[j] - the number of monitors which block monitor j
std::vector<size_t> blockingCount(nMonitors, 0);
// First, find indices of all unblocked monitors
for (size_t i = 0; i < nMonitors; i++)
{
if (blockingCount[i] == 0 && !used[i])
RECT rectI = monitorInfo[i].second;
for (size_t j = 0; j < nMonitors; j++)
{
candidates.push_back(i);
}
}
// In the unlikely event that there are no unblocked monitors, declare all unused monitors as candidates.
if (candidates.empty())
{
for (size_t i = 0; i < nMonitors; i++)
{
if (!used[i])
RECT rectJ = monitorInfo[j].second;
blocking[i][j] = rectI.top < rectJ.bottom && rectI.left < rectJ.right && i != j;
if (blocking[i][j])
{
candidates.push_back(i);
blockingCount[j]++;
}
}
}
// Pick the lexicographically smallest monitor as the next one
size_t smallest = candidates[0];
for (size_t j = 1; j < candidates.size(); j++)
{
size_t current = candidates[j];
// used[i] - whether the sorting algorithm has used monitor i so far
std::vector<bool> used(nMonitors, false);
// Compare (top, left) lexicographically
if (std::tie(monitorInfo[current].second.top, monitorInfo[current].second.left) <
std::tie(monitorInfo[smallest].second.top, monitorInfo[smallest].second.left))
// the sorted sequence of monitors
std::vector<std::pair<HMONITOR, RECT>> sortedMonitorInfo;
for (size_t iteration = 0; iteration < nMonitors; iteration++)
{
// Indices of candidates to become the next monitor in the sequence
std::vector<size_t> candidates;
// First, find indices of all unblocked monitors
for (size_t i = 0; i < nMonitors; i++)
{
smallest = current;
if (blockingCount[i] == 0 && !used[i])
{
candidates.push_back(i);
}
}
// In the unlikely event that there are no unblocked monitors, declare all unused monitors as candidates.
if (candidates.empty())
{
for (size_t i = 0; i < nMonitors; i++)
{
if (!used[i])
{
candidates.push_back(i);
}
}
}
// Pick the lexicographically smallest monitor as the next one
size_t smallest = candidates[0];
for (size_t j = 1; j < candidates.size(); j++)
{
size_t current = candidates[j];
// Compare (top, left) lexicographically
if (std::tie(monitorInfo[current].second.top, monitorInfo[current].second.left) <
std::tie(monitorInfo[smallest].second.top, monitorInfo[smallest].second.left))
{
smallest = current;
}
}
used[smallest] = true;
sortedMonitorInfo.push_back(monitorInfo[smallest]);
for (size_t i = 0; i < nMonitors; i++)
{
if (blocking[smallest][i])
{
blockingCount[i]--;
}
}
}
used[smallest] = true;
sortedMonitorInfo.push_back(monitorInfo[smallest]);
for (size_t i = 0; i < nMonitors; i++)
{
if (blocking[smallest][i])
{
blockingCount[i]--;
}
}
monitorInfo = std::move(sortedMonitorInfo);
}
monitorInfo = std::move(sortedMonitorInfo);
}
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)
void SizeWindowToRect(HWND window, RECT rect) noexcept
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
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;
}
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);
}
// 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;
}
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);
}
bool IsInterestingWindow(HWND window, const std::vector<std::wstring>& excludedApps) noexcept
{
auto filtered = get_fancyzones_filtered_window(window);
if (!filtered.zonable)
{
return false;
}
// Filter out user specified apps
CharUpperBuffW(filtered.process_path.data(), (DWORD)filtered.process_path.length());
if (find_app_name_in_path(filtered.process_path, excludedApps))
{
return false;
}
if (find_app_name_in_path(filtered.process_path, { NonLocalizable::PowerToysAppPowerLauncher }))
{
return false;
}
if (find_app_name_in_path(filtered.process_path, { NonLocalizable::PowerToysAppFZEditor }))
{
return false;
}
return true;
}
bool IsWindowMaximized(HWND window) noexcept
{
WINDOWPLACEMENT placement{};
if (GetWindowPlacement(window, &placement) &&
placement.showCmd == SW_SHOWMAXIMIZED)
bool IsInterestingWindow(HWND window, const std::vector<std::wstring>& excludedApps) noexcept
{
auto windowInfo = GetFancyZonesWindowInfo(window);
auto zonable = windowInfo.standardWindow && windowInfo.noVisibleOwner;
if (!zonable)
{
return false;
}
// Filter out user specified apps
CharUpperBuffW(windowInfo.processPath.data(), (DWORD)windowInfo.processPath.length());
if (find_app_name_in_path(windowInfo.processPath, excludedApps))
{
return false;
}
if (find_app_name_in_path(windowInfo.processPath, { NonLocalizable::PowerToysAppPowerLauncher }))
{
return false;
}
if (find_app_name_in_path(windowInfo.processPath, { NonLocalizable::PowerToysAppFZEditor }))
{
return false;
}
return true;
}
return false;
}
void SaveWindowSizeAndOrigin(HWND window) noexcept
{
HANDLE handle = GetPropW(window, ZonedWindowProperties::PropertyRestoreSizeID);
if (handle)
bool IsWindowMaximized(HWND window) noexcept
{
// Size already set, skip
return;
WINDOWPLACEMENT placement{};
if (GetWindowPlacement(window, &placement) &&
placement.showCmd == SW_SHOWMAXIMIZED)
{
return true;
}
return false;
}
RECT rect;
if (GetWindowRect(window, &rect))
void SaveWindowSizeAndOrigin(HWND window) noexcept
{
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]);
HANDLE handle = GetPropW(window, ZonedWindowProperties::PropertyRestoreSizeID);
if (handle)
{
// Size already set, skip
return;
}
RECT rect;
if (GetWindowRect(window, &rect))
{
rect.right = rect.left + windowSize[0];
rect.bottom = rect.top + windowSize[1];
SizeWindowToRect(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);
}
::RemoveProp(window, ZonedWindowProperties::PropertyRestoreSizeID);
}
}
void RestoreWindowOrigin(HWND window) noexcept
{
auto windowOriginData = GetPropW(window, ZonedWindowProperties::PropertyRestoreOriginID);
if (windowOriginData)
void RestoreWindowSize(HWND window) noexcept
{
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))
auto windowSizeData = GetPropW(window, ZonedWindowProperties::PropertyRestoreSizeID);
if (windowSizeData)
{
int xOffset = windowOrigin[0] - rect.left;
int yOffset = windowOrigin[1] - rect.top;
std::array<int, 2> windowSize;
memcpy(windowSize.data(), &windowSizeData, sizeof windowSize);
rect.left += xOffset;
rect.right += xOffset;
rect.top += yOffset;
rect.bottom += yOffset;
SizeWindowToRect(window, rect);
// {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);
}
::RemoveProp(window, ZonedWindowProperties::PropertyRestoreOriginID);
}
}
bool IsValidGuid(const std::wstring& str)
{
GUID id;
return SUCCEEDED(CLSIDFromString(str.c_str(), &id));
}
void RestoreWindowOrigin(HWND window) noexcept
{
auto windowOriginData = GetPropW(window, ZonedWindowProperties::PropertyRestoreOriginID);
if (windowOriginData)
{
std::array<int, 2> windowOrigin;
memcpy(windowOrigin.data(), &windowOriginData, sizeof windowOrigin);
bool IsValidDeviceId(const std::wstring& str)
{
std::wstring monitorName;
std::wstring temp;
std::vector<std::wstring> parts;
std::wstringstream wss(str);
// {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;
return SUCCEEDED(CLSIDFromString(str.c_str(), &id));
}
bool IsValidDeviceId(const std::wstring& str)
{
std::wstring monitorName;
std::wstring temp;
std::vector<std::wstring> parts;
std::wstringstream wss(str);
/*
Important fix for device info that contains a '_' in the name:
1. first search for '#'
2. Then split the remaining string by '_'
*/
*/
// Step 1: parse the name until the #, then to the '_'
if (str.find(L'#') != std::string::npos)
{
std::getline(wss, temp, L'#');
// Step 1: parse the name until the #, then to the '_'
if (str.find(L'#') != std::string::npos)
{
std::getline(wss, temp, L'#');
monitorName = temp;
monitorName = temp;
if (!std::getline(wss, temp, L'_'))
if (!std::getline(wss, temp, L'_'))
{
return false;
}
monitorName += L"#" + temp;
parts.push_back(monitorName);
}
// Step 2: parse the rest of the id
while (std::getline(wss, temp, L'_'))
{
parts.push_back(temp);
}
if (parts.size() != 4)
{
return false;
}
monitorName += L"#" + temp;
parts.push_back(monitorName);
}
// Step 2: parse the rest of the id
while (std::getline(wss, temp, L'_'))
{
parts.push_back(temp);
}
if (parts.size() != 4)
{
return false;
}
/*
/*
Refer to ZoneWindowUtils::GenerateUniqueId parts contain:
1. monitor id [string]
2. width of device [int]
3. height of device [int]
4. virtual desktop id (GUID) [string]
*/
try
{
//check if resolution contain only digits
for (const auto& c : parts[1])
{
std::stoi(std::wstring(&c));
}
for (const auto& c : parts[2])
{
std::stoi(std::wstring(&c));
}
}
catch (const std::exception&)
{
return false;
}
if (!IsValidGuid(parts[3]) || parts[0].empty())
{
return false;
}
return true;
}
size_t ChooseNextZoneByPosition(DWORD vkCode, RECT windowRect, const std::vector<RECT>& zoneRects) noexcept
{
using complex = std::complex<double>;
const size_t invalidResult = zoneRects.size();
const double inf = 1e100;
const double eccentricity = 2.0;
auto rectCenter = [](RECT rect) {
return complex {
0.5 * rect.left + 0.5 * rect.right,
0.5 * rect.top + 0.5 * rect.bottom
};
};
auto distance = [&](complex arrowDirection, complex zoneDirection) {
double result = inf;
try
{
double scalarProduct = (arrowDirection * conj(zoneDirection)).real();
if (scalarProduct <= 0.0)
//check if resolution contain only digits
for (const auto& c : parts[1])
{
return inf;
std::stoi(std::wstring(&c));
}
// no need to divide by abs(arrowDirection) because it's = 1
double cosAngle = scalarProduct / abs(zoneDirection);
double tanAngle = abs(tan(acos(cosAngle)));
if (tanAngle > 10)
for (const auto& c : parts[2])
{
// The angle is too wide
return inf;
}
// find the intersection with the ellipse with given eccentricity and major axis along arrowDirection
double intersectY = 2 * eccentricity / (1.0 + eccentricity * eccentricity * tanAngle * tanAngle);
double distanceEstimate = scalarProduct / intersectY;
if (std::isfinite(distanceEstimate))
{
result = distanceEstimate;
std::stoi(std::wstring(&c));
}
}
catch (...)
catch (const std::exception&)
{
return false;
}
return result;
};
std::vector<std::pair<size_t, complex>> candidateCenters;
for (size_t i = 0; i < zoneRects.size(); i++)
{
auto center = rectCenter(zoneRects[i]);
// Offset the zone slightly, to differentiate in case there are overlapping zones
center += 0.001 * (i + 1);
candidateCenters.emplace_back(i, center);
}
complex directionVector, windowCenter = rectCenter(windowRect);
switch (vkCode)
{
case VK_UP:
directionVector = { 0.0, -1.0 };
break;
case VK_DOWN:
directionVector = { 0.0, 1.0 };
break;
case VK_LEFT:
directionVector = { -1.0, 0.0 };
break;
case VK_RIGHT:
directionVector = { 1.0, 0.0 };
break;
default:
return invalidResult;
}
size_t closestIdx = invalidResult;
double smallestDistance = inf;
for (auto [zoneIdx, zoneCenter] : candidateCenters)
{
double dist = distance(directionVector, zoneCenter - windowCenter);
if (dist < smallestDistance)
if (!IsValidGuid(parts[3]) || parts[0].empty())
{
smallestDistance = dist;
closestIdx = zoneIdx;
return false;
}
return true;
}
return closestIdx;
}
RECT PrepareRectForCycling(RECT windowRect, RECT zoneWindowRect, DWORD vkCode) noexcept
{
LONG deltaX = 0, deltaY = 0;
switch (vkCode)
size_t ChooseNextZoneByPosition(DWORD vkCode, RECT windowRect, const std::vector<RECT>& zoneRects) noexcept
{
case VK_UP:
deltaY = zoneWindowRect.bottom - zoneWindowRect.top;
break;
case VK_DOWN:
deltaY = zoneWindowRect.top - zoneWindowRect.bottom;
break;
case VK_LEFT:
deltaX = zoneWindowRect.right - zoneWindowRect.left;
break;
case VK_RIGHT:
deltaX = zoneWindowRect.left - zoneWindowRect.right;
using complex = std::complex<double>;
const size_t invalidResult = zoneRects.size();
const double inf = 1e100;
const double eccentricity = 2.0;
auto rectCenter = [](RECT rect) {
return complex {
0.5 * rect.left + 0.5 * rect.right,
0.5 * rect.top + 0.5 * rect.bottom
};
};
auto distance = [&](complex arrowDirection, complex zoneDirection) {
double result = inf;
try
{
double scalarProduct = (arrowDirection * conj(zoneDirection)).real();
if (scalarProduct <= 0.0)
{
return inf;
}
// no need to divide by abs(arrowDirection) because it's = 1
double cosAngle = scalarProduct / abs(zoneDirection);
double tanAngle = abs(tan(acos(cosAngle)));
if (tanAngle > 10)
{
// The angle is too wide
return inf;
}
// find the intersection with the ellipse with given eccentricity and major axis along arrowDirection
double intersectY = 2 * eccentricity / (1.0 + eccentricity * eccentricity * tanAngle * tanAngle);
double distanceEstimate = scalarProduct / intersectY;
if (std::isfinite(distanceEstimate))
{
result = distanceEstimate;
}
}
catch (...)
{
}
return result;
};
std::vector<std::pair<size_t, complex>> candidateCenters;
for (size_t i = 0; i < zoneRects.size(); i++)
{
auto center = rectCenter(zoneRects[i]);
// Offset the zone slightly, to differentiate in case there are overlapping zones
center += 0.001 * (i + 1);
candidateCenters.emplace_back(i, center);
}
complex directionVector, windowCenter = rectCenter(windowRect);
switch (vkCode)
{
case VK_UP:
directionVector = { 0.0, -1.0 };
break;
case VK_DOWN:
directionVector = { 0.0, 1.0 };
break;
case VK_LEFT:
directionVector = { -1.0, 0.0 };
break;
case VK_RIGHT:
directionVector = { 1.0, 0.0 };
break;
default:
return invalidResult;
}
size_t closestIdx = invalidResult;
double smallestDistance = inf;
for (auto [zoneIdx, zoneCenter] : candidateCenters)
{
double dist = distance(directionVector, zoneCenter - windowCenter);
if (dist < smallestDistance)
{
smallestDistance = dist;
closestIdx = zoneIdx;
}
}
return closestIdx;
}
windowRect.left += deltaX;
windowRect.right += deltaX;
windowRect.top += deltaY;
windowRect.bottom += deltaY;
RECT PrepareRectForCycling(RECT windowRect, RECT zoneWindowRect, DWORD vkCode) noexcept
{
LONG deltaX = 0, deltaY = 0;
switch (vkCode)
{
case VK_UP:
deltaY = zoneWindowRect.bottom - zoneWindowRect.top;
break;
case VK_DOWN:
deltaY = zoneWindowRect.top - zoneWindowRect.bottom;
break;
case VK_LEFT:
deltaX = zoneWindowRect.right - zoneWindowRect.left;
break;
case VK_RIGHT:
deltaX = zoneWindowRect.left - zoneWindowRect.right;
}
return windowRect;
windowRect.left += deltaX;
windowRect.right += deltaX;
windowRect.top += deltaY;
windowRect.bottom += deltaY;
return windowRect;
}
}

View file

@ -2,180 +2,194 @@
#include "gdiplus.h"
struct Rect
namespace FancyZonesUtils
{
Rect() {}
Rect(RECT rect) :
m_rect(rect)
// Window properties relevant to FancyZones
struct FancyZonesWindowInfo
{
}
Rect(RECT rect, UINT dpi) :
m_rect(rect)
{
m_rect.right = m_rect.left + MulDiv(m_rect.right - m_rect.left, dpi, 96);
m_rect.bottom = m_rect.top + MulDiv(m_rect.bottom - m_rect.top, dpi, 96);
}
int x() const { return m_rect.left; }
int y() const { return m_rect.top; }
int width() const { return m_rect.right - m_rect.left; }
int height() const { return m_rect.bottom - m_rect.top; }
int left() const { return m_rect.left; }
int top() const { return m_rect.top; }
int right() const { return m_rect.right; }
int bottom() const { return m_rect.bottom; }
int aspectRatio() const { return MulDiv(m_rect.bottom - m_rect.top, 100, m_rect.right - m_rect.left); }
private:
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));
quad->rgbReserved = alpha;
quad->rgbRed = GetRValue(color) * alpha / 255;
quad->rgbGreen = GetGValue(color) * alpha / 255;
quad->rgbBlue = GetBValue(color) * alpha / 255;
}
inline void FillRectARGB(wil::unique_hdc& hdc, RECT const* prcFill, BYTE alpha, COLORREF color, bool blendAlpha)
{
BITMAPINFO bi;
ZeroMemory(&bi, sizeof(bi));
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bi.bmiHeader.biWidth = 1;
bi.bmiHeader.biHeight = 1;
bi.bmiHeader.biPlanes = 1;
bi.bmiHeader.biBitCount = 32;
bi.bmiHeader.biCompression = BI_RGB;
RECT fillRect;
CopyRect(&fillRect, prcFill);
RGBQUAD bitmapBits;
InitRGB(&bitmapBits, alpha, color);
StretchDIBits(
hdc.get(),
fillRect.left,
fillRect.top,
fillRect.right - fillRect.left,
fillRect.bottom - fillRect.top,
0,
0,
1,
1,
&bitmapBits,
&bi,
DIB_RGB_COLORS,
SRCCOPY);
}
inline void ParseDeviceId(PCWSTR deviceId, PWSTR parsedId, size_t size)
{
// We're interested in the unique part between the first and last #'s
// Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
// Example output: DELA026#5&10a58c63&0&UID16777488
const std::wstring defaultDeviceId = L"FallbackDevice";
if (!deviceId)
{
StringCchCopy(parsedId, size, defaultDeviceId.c_str());
return;
}
wchar_t buffer[256];
StringCchCopy(buffer, 256, deviceId);
PWSTR pszStart = wcschr(buffer, L'#');
PWSTR pszEnd = wcsrchr(buffer, L'#');
if (pszStart && pszEnd && (pszStart != pszEnd))
{
pszStart++; // skip past the first #
*pszEnd = '\0';
StringCchCopy(parsedId, size, pszStart);
}
else
{
StringCchCopy(parsedId, size, defaultDeviceId.c_str());
}
}
inline BYTE OpacitySettingToAlpha(int opacity)
{
return static_cast<BYTE>(opacity * 2.55);
}
template<RECT MONITORINFO::*member>
std::vector<std::pair<HMONITOR, RECT>> GetAllMonitorRects()
{
using result_t = std::vector<std::pair<HMONITOR, RECT>>;
result_t result;
auto enumMonitors = [](HMONITOR monitor, HDC hdc, LPRECT pRect, LPARAM param) -> BOOL
{
MONITORINFOEX mi;
mi.cbSize = sizeof(mi);
result_t& result = *reinterpret_cast<result_t*>(param);
if (GetMonitorInfo(monitor, &mi))
{
result.push_back({ monitor, mi.*member });
}
return TRUE;
// True if from the styles the window looks like a standard window
bool standardWindow = false;
// True if the window is a top-level window that does not have a visible owner
bool noVisibleOwner = false;
// Path to the executable owning the window
std::wstring processPath;
};
EnumDisplayMonitors(NULL, NULL, enumMonitors, reinterpret_cast<LPARAM>(&result));
return result;
}
template<RECT MONITORINFO::*member>
RECT GetAllMonitorsCombinedRect()
{
auto allMonitors = GetAllMonitorRects<member>();
bool empty = true;
RECT result{ 0, 0, 0, 0 };
for (auto& [monitor, rect] : allMonitors)
struct Rect
{
if (empty)
Rect() {}
Rect(RECT rect) :
m_rect(rect)
{
empty = false;
result = rect;
}
Rect(RECT rect, UINT dpi) :
m_rect(rect)
{
m_rect.right = m_rect.left + MulDiv(m_rect.right - m_rect.left, dpi, 96);
m_rect.bottom = m_rect.top + MulDiv(m_rect.bottom - m_rect.top, dpi, 96);
}
int x() const { return m_rect.left; }
int y() const { return m_rect.top; }
int width() const { return m_rect.right - m_rect.left; }
int height() const { return m_rect.bottom - m_rect.top; }
int left() const { return m_rect.left; }
int top() const { return m_rect.top; }
int right() const { return m_rect.right; }
int bottom() const { return m_rect.bottom; }
int aspectRatio() const { return MulDiv(m_rect.bottom - m_rect.top, 100, m_rect.right - m_rect.left); }
private:
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));
quad->rgbReserved = alpha;
quad->rgbRed = GetRValue(color) * alpha / 255;
quad->rgbGreen = GetGValue(color) * alpha / 255;
quad->rgbBlue = GetBValue(color) * alpha / 255;
}
inline void FillRectARGB(wil::unique_hdc& hdc, RECT const* prcFill, BYTE alpha, COLORREF color, bool blendAlpha)
{
BITMAPINFO bi;
ZeroMemory(&bi, sizeof(bi));
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bi.bmiHeader.biWidth = 1;
bi.bmiHeader.biHeight = 1;
bi.bmiHeader.biPlanes = 1;
bi.bmiHeader.biBitCount = 32;
bi.bmiHeader.biCompression = BI_RGB;
RECT fillRect;
CopyRect(&fillRect, prcFill);
RGBQUAD bitmapBits;
InitRGB(&bitmapBits, alpha, color);
StretchDIBits(
hdc.get(),
fillRect.left,
fillRect.top,
fillRect.right - fillRect.left,
fillRect.bottom - fillRect.top,
0,
0,
1,
1,
&bitmapBits,
&bi,
DIB_RGB_COLORS,
SRCCOPY);
}
inline void ParseDeviceId(PCWSTR deviceId, PWSTR parsedId, size_t size)
{
// We're interested in the unique part between the first and last #'s
// Example input: \\?\DISPLAY#DELA026#5&10a58c63&0&UID16777488#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
// Example output: DELA026#5&10a58c63&0&UID16777488
const std::wstring defaultDeviceId = L"FallbackDevice";
if (!deviceId)
{
StringCchCopy(parsedId, size, defaultDeviceId.c_str());
return;
}
wchar_t buffer[256];
StringCchCopy(buffer, 256, deviceId);
PWSTR pszStart = wcschr(buffer, L'#');
PWSTR pszEnd = wcsrchr(buffer, L'#');
if (pszStart && pszEnd && (pszStart != pszEnd))
{
pszStart++; // skip past the first #
*pszEnd = '\0';
StringCchCopy(parsedId, size, pszStart);
}
else
{
result.left = min(result.left, rect.left);
result.top = min(result.top, rect.top);
result.right = max(result.right, rect.right);
result.bottom = max(result.bottom, rect.bottom);
StringCchCopy(parsedId, size, defaultDeviceId.c_str());
}
}
return result;
inline BYTE OpacitySettingToAlpha(int opacity)
{
return static_cast<BYTE>(opacity * 2.55);
}
template<RECT MONITORINFO::*member>
std::vector<std::pair<HMONITOR, RECT>> GetAllMonitorRects()
{
using result_t = std::vector<std::pair<HMONITOR, RECT>>;
result_t result;
auto enumMonitors = [](HMONITOR monitor, HDC hdc, LPRECT pRect, LPARAM param) -> BOOL {
MONITORINFOEX mi;
mi.cbSize = sizeof(mi);
result_t& result = *reinterpret_cast<result_t*>(param);
if (GetMonitorInfo(monitor, &mi))
{
result.push_back({ monitor, mi.*member });
}
return TRUE;
};
EnumDisplayMonitors(NULL, NULL, enumMonitors, reinterpret_cast<LPARAM>(&result));
return result;
}
template<RECT MONITORINFO::*member>
RECT GetAllMonitorsCombinedRect()
{
auto allMonitors = GetAllMonitorRects<member>();
bool empty = true;
RECT result{ 0, 0, 0, 0 };
for (auto& [monitor, rect] : allMonitors)
{
if (empty)
{
empty = false;
result = rect;
}
else
{
result.left = min(result.left, rect.left);
result.top = min(result.top, rect.top);
result.right = max(result.right, rect.right);
result.bottom = max(result.bottom, rect.bottom);
}
}
return result;
}
UINT GetDpiForMonitor(HMONITOR monitor) noexcept;
void OrderMonitors(std::vector<std::pair<HMONITOR, RECT>>& monitorInfo);
void SizeWindowToRect(HWND window, RECT rect) noexcept;
bool IsInterestingWindow(HWND window, const std::vector<std::wstring>& excludedApps) noexcept;
bool IsWindowMaximized(HWND window) noexcept;
void SaveWindowSizeAndOrigin(HWND window) noexcept;
void RestoreWindowSize(HWND window) noexcept;
void RestoreWindowOrigin(HWND window) noexcept;
bool IsValidGuid(const std::wstring& str);
bool IsValidDeviceId(const std::wstring& str);
RECT PrepareRectForCycling(RECT windowRect, RECT zoneWindowRect, DWORD vkCode) noexcept;
size_t ChooseNextZoneByPosition(DWORD vkCode, RECT windowRect, const std::vector<RECT>& zoneRects) noexcept;
}
UINT GetDpiForMonitor(HMONITOR monitor) noexcept;
void OrderMonitors(std::vector<std::pair<HMONITOR, RECT>>& monitorInfo);
void SizeWindowToRect(HWND window, RECT rect) noexcept;
bool IsInterestingWindow(HWND window, const std::vector<std::wstring>& excludedApps) noexcept;
bool IsWindowMaximized(HWND window) noexcept;
void SaveWindowSizeAndOrigin(HWND window) noexcept;
void RestoreWindowSize(HWND window) noexcept;
void RestoreWindowOrigin(HWND window) noexcept;
bool IsValidGuid(const std::wstring& str);
bool IsValidDeviceId(const std::wstring& str);
size_t ChooseNextZoneByPosition(DWORD vkCode, RECT windowRect, const std::vector<RECT>& zoneRects) noexcept;
RECT PrepareRectForCycling(RECT windowRect, RECT zoneWindowRect, DWORD vkCode) noexcept;

View file

@ -14,6 +14,7 @@
using namespace JSONHelpers;
using namespace FancyZonesDataTypes;
using namespace FancyZonesUtils;
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace FancyZonesUnitTests

View file

@ -6,6 +6,8 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace FancyZonesUnitTests
{
using namespace FancyZonesUtils;
void TestMonitorSetPermutations(const std::vector<std::pair<HMONITOR, RECT>>& monitorInfo)
{
auto monitorInfoPermutation = monitorInfo;

View file

@ -29,6 +29,66 @@ namespace
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
// Window properties relevant to ShortcutGuide
struct ShortcutGuideWindowInfo
{
HWND hwnd = nullptr; // Handle to the top-level foreground window or nullptr if there is no such window
bool snappable = false; // True, if the window can react to Windows Snap keys
};
ShortcutGuideWindowInfo GetShortcutGuideWindowInfo()
{
ShortcutGuideWindowInfo result;
auto active_window = GetForegroundWindow();
active_window = GetAncestor(active_window, GA_ROOT);
if (!IsWindowVisible(active_window))
{
return result;
}
auto style = GetWindowLong(active_window, GWL_STYLE);
auto exStyle = GetWindowLong(active_window, GWL_EXSTYLE);
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 result;
}
std::array<char, 256> class_name;
GetClassNameA(active_window, class_name.data(), static_cast<int>(class_name.size()));
if (is_system_window(active_window, class_name.data()))
{
return result;
}
static HWND cortana_hwnd = nullptr;
if (cortana_hwnd == nullptr)
{
if (strcmp(class_name.data(), "Windows.UI.Core.CoreWindow") == 0 &&
get_process_path(active_window).ends_with(L"SearchUI.exe"))
{
cortana_hwnd = active_window;
return result;
}
}
else if (cortana_hwnd == active_window)
{
return result;
}
result.hwnd = active_window;
// In reality, Windows Snap works if even one of those styles is set
// for a window, it is just limited. If there is no WS_MAXIMIZEBOX using
// WinKey + Up just won't maximize the window. Similary, without
// WS_MINIMIZEBOX the window will not get minimized. A "Save As..." dialog
// is a example of such window - it can be snapped to both sides and to
// all screen corners, but will not get maximized nor minimized.
// For now, since ShortcutGuide can only disable entire "Windows Controls"
// group, we require that the window supports all the options.
result.snappable = ((style & WS_MAXIMIZEBOX) == WS_MAXIMIZEBOX) &&
((style & WS_MINIMIZEBOX) == WS_MINIMIZEBOX) &&
((style & WS_THICKFRAME) == WS_THICKFRAME);
return result;
}
}
OverlayWindow::OverlayWindow()
@ -232,8 +292,8 @@ intptr_t OverlayWindow::signal_event(LowlevelKeyboardEvent* event)
void OverlayWindow::on_held()
{
auto filter = get_shortcutguide_filtered_window();
winkey_popup->show(filter.hwnd, filter.snappable);
auto windowInfo = GetShortcutGuideWindowInfo();
winkey_popup->show(windowInfo.hwnd, windowInfo.snappable);
}
void OverlayWindow::on_held_press(DWORD vkCode)