From 9999a2b12695e80266b89cd9268ffe970498c14b Mon Sep 17 00:00:00 2001 From: stefansjfw <57057282+stefansjfw@users.noreply.github.com> Date: Mon, 24 Aug 2020 19:38:15 +0200 Subject: [PATCH] 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 --- src/common/common.cpp | 127 +-- src/common/common.h | 20 +- src/modules/fancyzones/lib/FancyZones.cpp | 26 +- src/modules/fancyzones/lib/JsonHelpers.cpp | 8 +- .../fancyzones/lib/WindowMoveHandler.cpp | 8 +- src/modules/fancyzones/lib/ZoneSet.cpp | 8 +- src/modules/fancyzones/lib/ZoneWindow.cpp | 2 + src/modules/fancyzones/lib/util.cpp | 787 ++++++++++-------- src/modules/fancyzones/lib/util.h | 342 ++++---- .../tests/UnitTests/JsonHelpers.Tests.cpp | 1 + .../fancyzones/tests/UnitTests/Util.Spec.cpp | 2 + src/modules/shortcut_guide/shortcut_guide.cpp | 64 +- 12 files changed, 705 insertions(+), 690 deletions(-) diff --git a/src/common/common.cpp b/src/common/common.cpp index e83e9eb69..5a781b756 100644 --- a/src/common/common.cpp +++ b/src/common/common.cpp @@ -56,12 +56,10 @@ std::optional 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 class_name; - GetClassNameA(window, class_name.data(), static_cast(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 class_name; - GetClassNameA(active_window, class_name.data(), static_cast(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; diff --git a/src/common/common.h b/src/common/common.h index 94cbeaede..5044b1339 100644 --- a/src/common/common.h +++ b/src/common/common.h @@ -13,24 +13,8 @@ std::optional get_button_pos(HWND hwnd); std::optional get_window_pos(HWND hwnd); // Gets mouse position. std::optional 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); diff --git a/src/modules/fancyzones/lib/FancyZones.cpp b/src/modules/fancyzones/lib/FancyZones.cpp index 55da98e00..0ad1cce51 100644 --- a/src/modules/fancyzones/lib/FancyZones.cpp +++ b/src/modules/fancyzones/lib/FancyZones.cpp @@ -350,7 +350,7 @@ bool FancyZones::ShouldProcessNewWindow(HWND window) noexcept // that belong to excluded applications list. if (IsSplashScreen(window) || (reinterpret_cast(::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> 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 FancyZones::GetMonitorsSorted() noexcept std::shared_lock readLock(m_lock); auto monitorInfo = GetRawMonitorData(); - OrderMonitors(monitorInfo); + FancyZonesUtils::OrderMonitors(monitorInfo); std::vector output; std::transform(std::begin(monitorInfo), std::end(monitorInfo), std::back_inserter(output), [](const auto& info) { return info.first; }); return output; diff --git a/src/modules/fancyzones/lib/JsonHelpers.cpp b/src/modules/fancyzones/lib/JsonHelpers.cpp index 7ace8db5a..4f8af2062 100644 --- a/src/modules/fancyzones/lib/JsonHelpers.cpp +++ b/src/modules/fancyzones/lib/JsonHelpers.cpp @@ -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; } diff --git a/src/modules/fancyzones/lib/WindowMoveHandler.cpp b/src/modules/fancyzones/lib/WindowMoveHandler.cpp index 822270615..f39b8af2b 100644 --- a/src/modules/fancyzones/lib/WindowMoveHandler.cpp +++ b/src/modules/fancyzones/lib/WindowMoveHandler.cpp @@ -172,7 +172,7 @@ bool WindowMoveHandler::MoveWindowIntoZoneByDirectionAndPosition(HWND window, DW void WindowMoveHandlerPrivate::MoveSizeStart(HWND window, HMONITOR monitor, POINT const& ptScreen, const std::unordered_map>& 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>& 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); } } diff --git a/src/modules/fancyzones/lib/ZoneSet.cpp b/src/modules/fancyzones/lib/ZoneSet.cpp index 823e27b29..6be2b1a7e 100644 --- a/src/modules/fancyzones/lib/ZoneSet.cpp +++ b/src/modules/fancyzones/lib/ZoneSet.cpp @@ -10,6 +10,8 @@ #include +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()) { diff --git a/src/modules/fancyzones/lib/ZoneWindow.cpp b/src/modules/fancyzones/lib/ZoneWindow.cpp index 6cf19b40c..24c4271eb 100644 --- a/src/modules/fancyzones/lib/ZoneWindow.cpp +++ b/src/modules/fancyzones/lib/ZoneWindow.cpp @@ -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) diff --git a/src/modules/fancyzones/lib/util.cpp b/src/modules/fancyzones/lib/util.cpp index a30150563..cdb612a65 100644 --- a/src/modules/fancyzones/lib/util.cpp +++ b/src/modules/fancyzones/lib/util.cpp @@ -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(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 class_name; + GetClassNameA(window, class_name.data(), static_cast(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>& 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> blocking(nMonitors, std::vector(nMonitors, false)); - - // blockingCount[j] - the number of monitors which block monitor j - std::vector 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(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 used(nMonitors, false); - - // the sorted sequence of monitors - std::vector> sortedMonitorInfo; - - for (size_t iteration = 0; iteration < nMonitors; iteration++) + void OrderMonitors(std::vector>& monitorInfo) { - // Indices of candidates to become the next monitor in the sequence - std::vector 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> blocking(nMonitors, std::vector(nMonitors, false)); + + // blockingCount[j] - the number of monitors which block monitor j + std::vector 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 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> sortedMonitorInfo; + + for (size_t iteration = 0; iteration < nMonitors; iteration++) + { + // Indices of candidates to become the next monitor in the sequence + std::vector 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& 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& 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 windowSizeData = { width, height }; - std::array 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 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 windowSizeData = { width, height }; + std::array 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 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 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 windowOrigin; + memcpy(windowOrigin.data(), &windowOriginData, sizeof windowOrigin); -bool IsValidDeviceId(const std::wstring& str) -{ - std::wstring monitorName; - std::wstring temp; - std::vector 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 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& zoneRects) noexcept -{ - using complex = std::complex; - 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> 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& 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; + 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> 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; + } } diff --git a/src/modules/fancyzones/lib/util.h b/src/modules/fancyzones/lib/util.h index 79b6fd99e..4870ba191 100644 --- a/src/modules/fancyzones/lib/util.h +++ b/src/modules/fancyzones/lib/util.h @@ -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(opacity * 2.55); -} - -template -std::vector> GetAllMonitorRects() -{ - using result_t = std::vector>; - 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(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(&result)); - return result; -} - -template -RECT GetAllMonitorsCombinedRect() -{ - auto allMonitors = GetAllMonitorRects(); - 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(opacity * 2.55); + } + + template + std::vector> GetAllMonitorRects() + { + using result_t = std::vector>; + 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(param); + if (GetMonitorInfo(monitor, &mi)) + { + result.push_back({ monitor, mi.*member }); + } + + return TRUE; + }; + + EnumDisplayMonitors(NULL, NULL, enumMonitors, reinterpret_cast(&result)); + return result; + } + + template + RECT GetAllMonitorsCombinedRect() + { + auto allMonitors = GetAllMonitorRects(); + 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>& monitorInfo); + void SizeWindowToRect(HWND window, RECT rect) noexcept; + + bool IsInterestingWindow(HWND window, const std::vector& 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& zoneRects) noexcept; } - -UINT GetDpiForMonitor(HMONITOR monitor) noexcept; -void OrderMonitors(std::vector>& monitorInfo); -void SizeWindowToRect(HWND window, RECT rect) noexcept; - -bool IsInterestingWindow(HWND window, const std::vector& 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& zoneRects) noexcept; -RECT PrepareRectForCycling(RECT windowRect, RECT zoneWindowRect, DWORD vkCode) noexcept; diff --git a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp index 82fb407c2..6551f5434 100644 --- a/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp +++ b/src/modules/fancyzones/tests/UnitTests/JsonHelpers.Tests.cpp @@ -14,6 +14,7 @@ using namespace JSONHelpers; using namespace FancyZonesDataTypes; +using namespace FancyZonesUtils; using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace FancyZonesUnitTests diff --git a/src/modules/fancyzones/tests/UnitTests/Util.Spec.cpp b/src/modules/fancyzones/tests/UnitTests/Util.Spec.cpp index 772bf6e75..488ea17a3 100644 --- a/src/modules/fancyzones/tests/UnitTests/Util.Spec.cpp +++ b/src/modules/fancyzones/tests/UnitTests/Util.Spec.cpp @@ -6,6 +6,8 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace FancyZonesUnitTests { + using namespace FancyZonesUtils; + void TestMonitorSetPermutations(const std::vector>& monitorInfo) { auto monitorInfoPermutation = monitorInfo; diff --git a/src/modules/shortcut_guide/shortcut_guide.cpp b/src/modules/shortcut_guide/shortcut_guide.cpp index 32a47ef37..788763b33 100644 --- a/src/modules/shortcut_guide/shortcut_guide.cpp +++ b/src/modules/shortcut_guide/shortcut_guide.cpp @@ -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 class_name; + GetClassNameA(active_window, class_name.data(), static_cast(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)