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