ShortcutGuide, FancyZones: split window filtering (#1225)

Splits the code use to filter windows for FancyZones and the
"active window" for the ShortcutGuide. The FancyZones logic is preserved
and merged into a single function. We keep it in common.h, as it might
be also used in other PowerToys, like maximized to new desktop. We do
however change the return type to be more descriptive. It also returns
a separate flag for if the window has a visible owner. This can be used
to implement the approved apps list.

For the ShortcutGuide, the logic is relaxed to include more windows. One
example are Explorer properties windows. Those are (and should) filtered
by the FancyZones, but should appear in the window preview in the SCG.

The new return type also includes information if the window will react
to the default Windows Snap. This is not ideal though. Currently, SCG
can only disable the entire "Windows Controls" group. OTOH windows like
"Save As..." dialogs can be snapped to corners etc., but cannot be
minimized nor maximized. Until SCG can separately disable those buttons
we will display the buttons in the enabled state only if the window
supports all settings. In the future, we should integrate FancyZones
snap override here too.
This commit is contained in:
Bartosz Sosnowski
2020-02-07 15:53:57 +01:00
committed by GitHub
parent f963d28ba8
commit 0fdc1d0a1f
6 changed files with 96 additions and 61 deletions

View File

@@ -53,14 +53,31 @@ static bool is_system_window(HWND hwnd, const char* class_name) {
return false; return false;
} }
WindowAndProcPath get_filtered_base_window_and_path(HWND window) { static bool no_visible_owner(HWND window) noexcept
WindowAndProcPath result; {
auto root = GetAncestor(window, GA_ROOT); auto owner = GetWindow(window, GW_OWNER);
if (!IsWindowVisible(root)) { 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; return result;
} }
auto style = GetWindowLong(root, GWL_STYLE); auto style = GetWindowLong(window, GWL_STYLE);
auto exStyle = GetWindowLong(root, GWL_EXSTYLE); auto exStyle = GetWindowLong(window, GWL_EXSTYLE);
// WS_POPUP need to have a border or minimize/maximize buttons, // WS_POPUP need to have a border or minimize/maximize buttons,
// otherwise the window is "not interesting" // otherwise the window is "not interesting"
if ((style & WS_POPUP) == WS_POPUP && if ((style & WS_POPUP) == WS_POPUP &&
@@ -76,23 +93,66 @@ WindowAndProcPath get_filtered_base_window_and_path(HWND window) {
return result; return result;
} }
std::array<char, 256> class_name; std::array<char, 256> class_name;
GetClassNameA(root, class_name.data(), static_cast<int>(class_name.size())); GetClassNameA(window, class_name.data(), static_cast<int>(class_name.size()));
if (is_system_window(root, class_name.data())) { if (is_system_window(window, class_name.data())) {
return result; return result;
} }
auto process_path = get_process_path(root); auto process_path = get_process_path(window);
// Check for Cortana: // Check for Cortana:
if (strcmp(class_name.data(), "Windows.UI.Core.CoreWindow") == 0 && if (strcmp(class_name.data(), "Windows.UI.Core.CoreWindow") == 0 &&
process_path.ends_with(L"SearchUI.exe")) { process_path.ends_with(L"SearchUI.exe")) {
return result; return result;
} }
result.hwnd = root;
result.process_path = std::move(process_path); 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; return result;
} }
HWND get_filtered_active_window() { ShortcutGuideFilter get_shortcutguide_filtered_window() {
return get_filtered_base_window_and_path(GetForegroundWindow()).hwnd; 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 cortanda_hwnd = nullptr;
if (cortanda_hwnd == nullptr) {
if (strcmp(class_name.data(), "Windows.UI.Core.CoreWindow") == 0 &&
get_process_path(active_window).ends_with(L"SearchUI.exe")) {
cortanda_hwnd = active_window;
return result;
}
} else if (cortanda_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 conrers, 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) { int width(const RECT& rect) {

View File

@@ -13,15 +13,21 @@ std::optional<RECT> get_window_pos(HWND hwnd);
// Gets mouse postion. // Gets mouse postion.
std::optional<POINT> get_mouse_pos(); std::optional<POINT> get_mouse_pos();
// Gets window ancestor (usualy the window we want to do stuff with), filtering out all "non standard" windows like the taskbar, etc. and provide the app process path // Test if window can be zoned by FancyZones
struct WindowAndProcPath { struct FancyZonesFilter {
HWND hwnd = nullptr; bool zonable = false; // If the window is zonable by FancyZones by default - true when both standard_window and no_visible_owner are also true
std::wstring process_path; 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
}; };
WindowAndProcPath get_filtered_base_window_and_path(HWND window); FancyZonesFilter get_fancyzones_filtered_window(HWND window);
// Gets active window, filtering out all "non standard" windows like the taskbar, etc.
HWND get_filtered_active_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();
// Calculate sizes // Calculate sizes
int width(const RECT& rect); int width(const RECT& rect);

View File

@@ -592,53 +592,20 @@ LRESULT CALLBACK FancyZones::s_WndProc(HWND window, UINT message, WPARAM wparam,
DefWindowProc(window, message, wparam, lparam); DefWindowProc(window, message, wparam, lparam);
} }
static bool HasVisibleOwner(HWND window) noexcept
{
auto owner = GetWindow(window, GW_OWNER);
if (owner == nullptr)
{
return false; // There is no owner at all
}
if (!IsWindowVisible(owner))
{
return false; // Owner is invisible
}
RECT rect;
if (!GetWindowRect(owner, &rect))
{
return true; // Could not get the rect, return true (and filter out the window) just in case
}
// Return false (and allow the window to be zonable) if the owner window size is zero
return rect.top != rect.bottom || rect.left != rect.right;
}
bool FancyZones::IsInterestingWindow(HWND window) noexcept bool FancyZones::IsInterestingWindow(HWND window) noexcept
{ {
auto style = GetWindowLongPtr(window, GWL_STYLE); auto filtered = get_fancyzones_filtered_window(window);
auto exStyle = GetWindowLongPtr(window, GWL_EXSTYLE); if (!filtered.zonable)
// Ignore:
if (GetAncestor(window, GA_ROOT) != window || // windows that are not top-level
HasVisibleOwner(window) || // windows that have an visible owner - like Save As dialogs
(style & WS_CHILD) != 0 || // windows that are child elements of other windows - like buttons
(style & WS_DISABLED) != 0 || // windows that are disabled
(exStyle & WS_EX_TOOLWINDOW) != 0 || // toolbar windows
!IsWindowVisible(window)) // invisible windows
{
return false;
}
// Filter some windows like the Start menu or Cortana
auto windowAndPath = get_filtered_base_window_and_path(window);
if (windowAndPath.hwnd == nullptr)
{ {
return false; return false;
} }
// Filter out user specified apps // Filter out user specified apps
CharUpperBuffW(windowAndPath.process_path.data(), (DWORD)windowAndPath.process_path.length()); CharUpperBuffW(filtered.process_path.data(), (DWORD)filtered.process_path.length());
if (m_settings) if (m_settings)
{ {
for (const auto& excluded : m_settings->GetSettings().excludedAppsArray) for (const auto& excluded : m_settings->GetSettings().excludedAppsArray)
{ {
if (windowAndPath.process_path.find(excluded) != std::wstring::npos) if (filtered.process_path.find(excluded) != std::wstring::npos)
{ {
return false; return false;
} }

View File

@@ -210,11 +210,12 @@ D2DOverlayWindow::D2DOverlayWindow() :
}); });
} }
void D2DOverlayWindow::show(HWND active_window) void D2DOverlayWindow::show(HWND active_window, bool snappable)
{ {
std::unique_lock lock(mutex); std::unique_lock lock(mutex);
tasklist_buttons.clear(); tasklist_buttons.clear();
this->active_window = active_window; this->active_window = active_window;
this->active_window_snappable = snappable;
auto old_bck = colors.start_color_menu; auto old_bck = colors.start_color_menu;
auto colors_updated = colors.update(); auto colors_updated = colors.update();
auto new_light_mode = (theme_setting == Light) || (theme_setting == System && colors.light_mode); auto new_light_mode = (theme_setting == Light) || (theme_setting == System && colors.light_mode);
@@ -861,7 +862,7 @@ void D2DOverlayWindow::render(ID2D1DeviceContext5* d2d_dc)
down = GET_RESOURCE_STRING(IDS_NO_ACTION); down = GET_RESOURCE_STRING(IDS_NO_ACTION);
down_disabled = true; down_disabled = true;
} }
auto text_color = D2D1::ColorF(light_mode ? 0x222222 : 0xDDDDDD, minature_shown || window_state == MINIMIZED ? 1.0f : 0.3f); auto text_color = D2D1::ColorF(light_mode ? 0x222222 : 0xDDDDDD, active_window_snappable && (minature_shown || window_state == MINIMIZED) ? 1.0f : 0.3f);
use_overlay->find_element(L"KeyUpGroup")->SetAttributeValue(L"fill-opacity", up_disabled ? 0.3f : 1.0f); use_overlay->find_element(L"KeyUpGroup")->SetAttributeValue(L"fill-opacity", up_disabled ? 0.3f : 1.0f);
text.set_aligment_center().write(d2d_dc, text_color, use_overlay->get_maximize_label(), up); text.set_aligment_center().write(d2d_dc, text_color, use_overlay->get_maximize_label(), up);
use_overlay->find_element(L"KeyDownGroup")->SetAttributeValue(L"fill-opacity", down_disabled ? 0.3f : 1.0f); use_overlay->find_element(L"KeyDownGroup")->SetAttributeValue(L"fill-opacity", down_disabled ? 0.3f : 1.0f);

View File

@@ -47,7 +47,7 @@ class D2DOverlayWindow : public D2DWindow
{ {
public: public:
D2DOverlayWindow(); D2DOverlayWindow();
void show(HWND active_window); void show(HWND active_window, bool snappable);
void animate(int vk_code); void animate(int vk_code);
~D2DOverlayWindow(); ~D2DOverlayWindow();
void apply_overlay_opacity(float opacity); void apply_overlay_opacity(float opacity);
@@ -84,6 +84,7 @@ private:
HTHUMBNAIL thumbnail; HTHUMBNAIL thumbnail;
HWND active_window = nullptr; HWND active_window = nullptr;
bool active_window_snappable = false;
D2DOverlaySVG landscape, portrait; D2DOverlaySVG landscape, portrait;
D2DOverlaySVG* use_overlay = nullptr; D2DOverlaySVG* use_overlay = nullptr;
D2DSVG no_active; D2DSVG no_active;

View File

@@ -158,8 +158,8 @@ intptr_t OverlayWindow::signal_event(const wchar_t* name, intptr_t data)
void OverlayWindow::on_held() void OverlayWindow::on_held()
{ {
auto active_window = get_filtered_active_window(); auto filter = get_shortcutguide_filtered_window();
winkey_popup->show(active_window); winkey_popup->show(filter.hwnd, filter.snappable);
} }
void OverlayWindow::on_held_press(DWORD vkCode) void OverlayWindow::on_held_press(DWORD vkCode)