diff --git a/src/common/common.cpp b/src/common/common.cpp index 68465fa0a4..34bf86a2af 100644 --- a/src/common/common.cpp +++ b/src/common/common.cpp @@ -53,14 +53,31 @@ static bool is_system_window(HWND hwnd, const char* class_name) { return false; } -WindowAndProcPath get_filtered_base_window_and_path(HWND window) { - WindowAndProcPath result; - auto root = GetAncestor(window, GA_ROOT); - if (!IsWindowVisible(root)) { +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(root, GWL_STYLE); - auto exStyle = GetWindowLong(root, GWL_EXSTYLE); + 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 && @@ -76,23 +93,66 @@ WindowAndProcPath get_filtered_base_window_and_path(HWND window) { return result; } std::array class_name; - GetClassNameA(root, class_name.data(), static_cast(class_name.size())); - if (is_system_window(root, class_name.data())) { + 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(root); + 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.hwnd = root; 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; } -HWND get_filtered_active_window() { - return get_filtered_base_window_and_path(GetForegroundWindow()).hwnd; +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 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) { diff --git a/src/common/common.h b/src/common/common.h index a6d228361c..389da78343 100644 --- a/src/common/common.h +++ b/src/common/common.h @@ -13,15 +13,21 @@ std::optional get_window_pos(HWND hwnd); // Gets mouse postion. std::optional 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 -struct WindowAndProcPath { - HWND hwnd = nullptr; - std::wstring process_path; +// 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 }; -WindowAndProcPath get_filtered_base_window_and_path(HWND window); -// Gets active window, filtering out all "non standard" windows like the taskbar, etc. -HWND get_filtered_active_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(); // Calculate sizes int width(const RECT& rect); diff --git a/src/modules/fancyzones/lib/FancyZones.cpp b/src/modules/fancyzones/lib/FancyZones.cpp index bd85213883..489bf2e242 100644 --- a/src/modules/fancyzones/lib/FancyZones.cpp +++ b/src/modules/fancyzones/lib/FancyZones.cpp @@ -592,53 +592,20 @@ LRESULT CALLBACK FancyZones::s_WndProc(HWND window, UINT message, WPARAM wparam, 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 { - auto style = GetWindowLongPtr(window, GWL_STYLE); - auto exStyle = GetWindowLongPtr(window, GWL_EXSTYLE); - // 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) + auto filtered = get_fancyzones_filtered_window(window); + if (!filtered.zonable) { return false; } // 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) { 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; } diff --git a/src/modules/shortcut_guide/overlay_window.cpp b/src/modules/shortcut_guide/overlay_window.cpp index e986a47d8b..752e4982af 100644 --- a/src/modules/shortcut_guide/overlay_window.cpp +++ b/src/modules/shortcut_guide/overlay_window.cpp @@ -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); tasklist_buttons.clear(); this->active_window = active_window; + this->active_window_snappable = snappable; auto old_bck = colors.start_color_menu; auto colors_updated = colors.update(); 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_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); 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); diff --git a/src/modules/shortcut_guide/overlay_window.h b/src/modules/shortcut_guide/overlay_window.h index 357352b06b..f6ac4e0a62 100644 --- a/src/modules/shortcut_guide/overlay_window.h +++ b/src/modules/shortcut_guide/overlay_window.h @@ -47,7 +47,7 @@ class D2DOverlayWindow : public D2DWindow { public: D2DOverlayWindow(); - void show(HWND active_window); + void show(HWND active_window, bool snappable); void animate(int vk_code); ~D2DOverlayWindow(); void apply_overlay_opacity(float opacity); @@ -84,6 +84,7 @@ private: HTHUMBNAIL thumbnail; HWND active_window = nullptr; + bool active_window_snappable = false; D2DOverlaySVG landscape, portrait; D2DOverlaySVG* use_overlay = nullptr; D2DSVG no_active; diff --git a/src/modules/shortcut_guide/shortcut_guide.cpp b/src/modules/shortcut_guide/shortcut_guide.cpp index c9ac087659..7936aca899 100644 --- a/src/modules/shortcut_guide/shortcut_guide.cpp +++ b/src/modules/shortcut_guide/shortcut_guide.cpp @@ -158,8 +158,8 @@ intptr_t OverlayWindow::signal_event(const wchar_t* name, intptr_t data) void OverlayWindow::on_held() { - auto active_window = get_filtered_active_window(); - winkey_popup->show(active_window); + auto filter = get_shortcutguide_filtered_window(); + winkey_popup->show(filter.hwnd, filter.snappable); } void OverlayWindow::on_held_press(DWORD vkCode)