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,9 +15,78 @@ namespace NonLocalizable
const wchar_t PowerToysAppFZEditor[] = L"FANCYZONESEDITOR.EXE";
}
typedef BOOL(WINAPI* GetDpiForMonitorInternalFunc)(HMONITOR, UINT, UINT*, UINT*);
UINT GetDpiForMonitor(HMONITOR monitor) noexcept
namespace
{
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;
}
FancyZonesUtils::FancyZonesWindowInfo GetFancyZonesWindowInfo(HWND window)
{
FancyZonesUtils::FancyZonesWindowInfo 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.processPath = std::move(process_path);
result.standardWindow = true;
result.noVisibleOwner = HasNoVisibleOwner(window);
return result;
}
}
namespace FancyZonesUtils
{
typedef BOOL(WINAPI* GetDpiForMonitorInternalFunc)(HMONITOR, UINT, UINT*, UINT*);
UINT GetDpiForMonitor(HMONITOR monitor) noexcept
{
UINT dpi{};
if (wil::unique_hmodule user32{ LoadLibrary(L"user32.dll") })
{
@ -36,10 +105,10 @@ UINT GetDpiForMonitor(HMONITOR monitor) noexcept
}
return (dpi == 0) ? DPIAware::DEFAULT_DPI : dpi;
}
}
void OrderMonitors(std::vector<std::pair<HMONITOR, RECT>>& monitorInfo)
{
void OrderMonitors(std::vector<std::pair<HMONITOR, RECT>>& monitorInfo)
{
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));
@ -119,10 +188,10 @@ void OrderMonitors(std::vector<std::pair<HMONITOR, RECT>>& monitorInfo)
}
monitorInfo = std::move(sortedMonitorInfo);
}
}
void SizeWindowToRect(HWND window, RECT rect) noexcept
{
void SizeWindowToRect(HWND window, RECT rect) noexcept
{
WINDOWPLACEMENT placement{};
::GetWindowPlacement(window, &placement);
@ -154,34 +223,35 @@ void SizeWindowToRect(HWND window, RECT rect) noexcept
// 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)
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(filtered.process_path.data(), (DWORD)filtered.process_path.length());
if (find_app_name_in_path(filtered.process_path, excludedApps))
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(filtered.process_path, { NonLocalizable::PowerToysAppPowerLauncher }))
if (find_app_name_in_path(windowInfo.processPath, { NonLocalizable::PowerToysAppPowerLauncher }))
{
return false;
}
if (find_app_name_in_path(filtered.process_path, { NonLocalizable::PowerToysAppFZEditor }))
if (find_app_name_in_path(windowInfo.processPath, { NonLocalizable::PowerToysAppFZEditor }))
{
return false;
}
return true;
}
}
bool IsWindowMaximized(HWND window) noexcept
{
bool IsWindowMaximized(HWND window) noexcept
{
WINDOWPLACEMENT placement{};
if (GetWindowPlacement(window, &placement) &&
placement.showCmd == SW_SHOWMAXIMIZED)
@ -189,10 +259,10 @@ bool IsWindowMaximized(HWND window) noexcept
return true;
}
return false;
}
}
void SaveWindowSizeAndOrigin(HWND window) noexcept
{
void SaveWindowSizeAndOrigin(HWND window) noexcept
{
HANDLE handle = GetPropW(window, ZonedWindowProperties::PropertyRestoreSizeID);
if (handle)
{
@ -219,10 +289,10 @@ void SaveWindowSizeAndOrigin(HWND window) noexcept
memcpy(&rawData, windowOriginData.data(), sizeof rawData);
SetPropW(window, ZonedWindowProperties::PropertyRestoreOriginID, rawData);
}
}
}
void RestoreWindowSize(HWND window) noexcept
{
void RestoreWindowSize(HWND window) noexcept
{
auto windowSizeData = GetPropW(window, ZonedWindowProperties::PropertyRestoreSizeID);
if (windowSizeData)
{
@ -242,10 +312,10 @@ void RestoreWindowSize(HWND window) noexcept
::RemoveProp(window, ZonedWindowProperties::PropertyRestoreSizeID);
}
}
}
void RestoreWindowOrigin(HWND window) noexcept
{
void RestoreWindowOrigin(HWND window) noexcept
{
auto windowOriginData = GetPropW(window, ZonedWindowProperties::PropertyRestoreOriginID);
if (windowOriginData)
{
@ -270,16 +340,16 @@ void RestoreWindowOrigin(HWND window) noexcept
::RemoveProp(window, ZonedWindowProperties::PropertyRestoreOriginID);
}
}
}
bool IsValidGuid(const std::wstring& str)
{
bool IsValidGuid(const std::wstring& str)
{
GUID id;
return SUCCEEDED(CLSIDFromString(str.c_str(), &id));
}
}
bool IsValidDeviceId(const std::wstring& str)
{
bool IsValidDeviceId(const std::wstring& str)
{
std::wstring monitorName;
std::wstring temp;
std::vector<std::wstring> parts;
@ -348,10 +418,10 @@ bool IsValidDeviceId(const std::wstring& str)
}
return true;
}
}
size_t ChooseNextZoneByPosition(DWORD vkCode, RECT windowRect, const std::vector<RECT>& zoneRects) noexcept
{
size_t ChooseNextZoneByPosition(DWORD vkCode, RECT windowRect, const std::vector<RECT>& zoneRects) noexcept
{
using complex = std::complex<double>;
const size_t invalidResult = zoneRects.size();
const double inf = 1e100;
@ -445,10 +515,10 @@ size_t ChooseNextZoneByPosition(DWORD vkCode, RECT windowRect, const std::vector
}
return closestIdx;
}
}
RECT PrepareRectForCycling(RECT windowRect, RECT zoneWindowRect, DWORD vkCode) noexcept
{
RECT PrepareRectForCycling(RECT windowRect, RECT zoneWindowRect, DWORD vkCode) noexcept
{
LONG deltaX = 0, deltaY = 0;
switch (vkCode)
{
@ -471,4 +541,5 @@ RECT PrepareRectForCycling(RECT windowRect, RECT zoneWindowRect, DWORD vkCode) n
windowRect.bottom += deltaY;
return windowRect;
}
}

View file

@ -2,8 +2,21 @@
#include "gdiplus.h"
struct Rect
namespace FancyZonesUtils
{
// Window properties relevant to FancyZones
struct FancyZonesWindowInfo
{
// 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;
};
struct Rect
{
Rect() {}
Rect(RECT rect) :
@ -28,31 +41,31 @@ struct Rect
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:
private:
RECT m_rect{};
};
};
inline void MakeWindowTransparent(HWND window)
{
inline void MakeWindowTransparent(HWND window)
{
int const pos = -GetSystemMetrics(SM_CXVIRTUALSCREEN) - 8;
if (wil::unique_hrgn hrgn{ CreateRectRgn(pos, 0, (pos + 1), 1) })
{
DWM_BLURBEHIND bh = { DWM_BB_ENABLE | DWM_BB_BLURREGION, TRUE, hrgn.get(), FALSE };
DwmEnableBlurBehindWindow(window, &bh);
}
}
}
inline void InitRGB(_Out_ RGBQUAD* quad, BYTE alpha, COLORREF color)
{
inline void InitRGB(_Out_ RGBQUAD* quad, BYTE alpha, COLORREF color)
{
ZeroMemory(quad, sizeof(*quad));
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)
{
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);
@ -81,10 +94,10 @@ inline void FillRectARGB(wil::unique_hdc& hdc, RECT const* prcFill, BYTE alpha,
&bi,
DIB_RGB_COLORS,
SRCCOPY);
}
}
inline void ParseDeviceId(PCWSTR deviceId, PWSTR parsedId, size_t size)
{
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
@ -109,21 +122,20 @@ inline void ParseDeviceId(PCWSTR deviceId, PWSTR parsedId, size_t size)
{
StringCchCopy(parsedId, size, defaultDeviceId.c_str());
}
}
}
inline BYTE OpacitySettingToAlpha(int opacity)
{
inline BYTE OpacitySettingToAlpha(int opacity)
{
return static_cast<BYTE>(opacity * 2.55);
}
}
template<RECT MONITORINFO::*member>
std::vector<std::pair<HMONITOR, RECT>> GetAllMonitorRects()
{
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
{
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);
@ -137,11 +149,11 @@ std::vector<std::pair<HMONITOR, RECT>> GetAllMonitorRects()
EnumDisplayMonitors(NULL, NULL, enumMonitors, reinterpret_cast<LPARAM>(&result));
return result;
}
}
template<RECT MONITORINFO::*member>
RECT GetAllMonitorsCombinedRect()
{
template<RECT MONITORINFO::*member>
RECT GetAllMonitorsCombinedRect()
{
auto allMonitors = GetAllMonitorRects<member>();
bool empty = true;
RECT result{ 0, 0, 0, 0 };
@ -163,19 +175,21 @@ RECT GetAllMonitorsCombinedRect()
}
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)