From 50a0e7da64e460b804a7097fa5bb5f3cd97b5aaf Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sat, 10 Apr 2021 14:56:33 +0400 Subject: [PATCH] Add IsOverlapped method --- ui/platform/linux/ui_utility_linux.cpp | 267 ++++++++++++++++++++++++- ui/platform/mac/ui_utility_mac.mm | 7 + ui/platform/ui_platform_utility.h | 4 + ui/platform/win/ui_utility_win.cpp | 54 +++++ ui/ui_utility.cpp | 32 +++ ui/ui_utility.h | 4 + 6 files changed, 367 insertions(+), 1 deletion(-) diff --git a/ui/platform/linux/ui_utility_linux.cpp b/ui/platform/linux/ui_utility_linux.cpp index b61e47b..e925ef6 100644 --- a/ui/platform/linux/ui_utility_linux.cpp +++ b/ui/platform/linux/ui_utility_linux.cpp @@ -33,6 +33,259 @@ namespace { constexpr auto kXCBFrameExtentsAtomName = "_GTK_FRAME_EXTENTS"_cs; #ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION +std::optional XCBWindowMapped(xcb_window_t window) { + const auto connection = base::Platform::XCB::GetConnectionFromQt(); + if (!connection) { + return std::nullopt; + } + + const auto cookie = xcb_get_window_attributes(connection, window); + const auto reply = xcb_get_window_attributes_reply( + connection, + cookie, + nullptr); + + if (!reply) { + return std::nullopt; + } + + const auto guard = gsl::finally([&] { free(reply); }); + return reply->map_state == XCB_MAP_STATE_VIEWABLE; +} + +std::optional XCBWindowHidden(xcb_window_t window) { + const auto connection = base::Platform::XCB::GetConnectionFromQt(); + if (!connection) { + return std::nullopt; + } + + const auto stateAtom = base::Platform::XCB::GetAtom( + connection, + "_NET_WM_STATE"); + + const auto stateHiddenAtom = base::Platform::XCB::GetAtom( + connection, + "_NET_WM_STATE_HIDDEN"); + + if (!stateAtom.has_value() || !stateHiddenAtom.has_value()) { + return std::nullopt; + } + + const auto cookie = xcb_get_property( + connection, + false, + window, + *stateAtom, + XCB_ATOM_ATOM, + 0, + 1024); + + const auto reply = xcb_get_property_reply( + connection, + cookie, + nullptr); + + if (!reply) { + return std::nullopt; + } + + const auto guard = gsl::finally([&] { free(reply); }); + if (reply->type != XCB_ATOM_ATOM || reply->format != 32) { + return std::nullopt; + } + + const auto atomsStart = reinterpret_cast( + xcb_get_property_value(reply)); + + const auto states = std::vector( + atomsStart, + atomsStart + reply->length); + + return ranges::contains(states, *stateHiddenAtom); +} + +QRect XCBWindowGeometry(xcb_window_t window) { + const auto connection = base::Platform::XCB::GetConnectionFromQt(); + if (!connection) { + return {}; + } + + const auto cookie = xcb_get_geometry(connection, window); + const auto reply = xcb_get_geometry_reply( + connection, + cookie, + nullptr); + + if (!reply) { + return {}; + } + + const auto guard = gsl::finally([&] { free(reply); }); + return QRect(reply->x, reply->y, reply->width, reply->height); +} + +std::optional XCBCurrentWorkspace() { + const auto connection = base::Platform::XCB::GetConnectionFromQt(); + if (!connection) { + return std::nullopt; + } + + const auto root = base::Platform::XCB::GetRootWindowFromQt(); + if (!root.has_value()) { + return std::nullopt; + } + + const auto currentDesktopAtom = base::Platform::XCB::GetAtom( + connection, + "_NET_CURRENT_DESKTOP"); + + if (!currentDesktopAtom.has_value()) { + return std::nullopt; + } + + const auto cookie = xcb_get_property( + connection, + false, + *root, + *currentDesktopAtom, + XCB_ATOM_CARDINAL, + 0, + 1024); + + const auto reply = xcb_get_property_reply( + connection, + cookie, + nullptr); + + if (!reply) { + return std::nullopt; + } + + const auto guard = gsl::finally([&] { free(reply); }); + return (reply->type == XCB_ATOM_CARDINAL) + ? std::make_optional( + *reinterpret_cast(xcb_get_property_value(reply))) + : std::nullopt; +} + +std::optional XCBWindowWorkspace(xcb_window_t window) { + const auto connection = base::Platform::XCB::GetConnectionFromQt(); + if (!connection) { + return std::nullopt; + } + + const auto desktopAtom = base::Platform::XCB::GetAtom( + connection, + "_NET_WM_DESKTOP"); + + if (!desktopAtom.has_value()) { + return std::nullopt; + } + + const auto cookie = xcb_get_property( + connection, + false, + window, + *desktopAtom, + XCB_ATOM_CARDINAL, + 0, + 1024); + + const auto reply = xcb_get_property_reply( + connection, + cookie, + nullptr); + + if (!reply) { + return std::nullopt; + } + + const auto guard = gsl::finally([&] { free(reply); }); + return (reply->type == XCB_ATOM_CARDINAL) + ? std::make_optional( + *reinterpret_cast(xcb_get_property_value(reply))) + : std::nullopt; +} + +std::optional XCBIsOverlapped( + not_null widget, + const QRect &rect) { + const auto window = widget->window()->winId(); + Expects(window != XCB_WINDOW_NONE); + + const auto connection = base::Platform::XCB::GetConnectionFromQt(); + if (!connection) { + return std::nullopt; + } + + const auto root = base::Platform::XCB::GetRootWindowFromQt(); + if (!root.has_value()) { + return std::nullopt; + } + + const auto windowWorkspace = XCBWindowWorkspace( + window); + + const auto currentWorkspace = XCBCurrentWorkspace(); + + if (windowWorkspace.has_value() + && currentWorkspace.has_value() + && *windowWorkspace != *currentWorkspace + && *windowWorkspace != 0xFFFFFFFF) { + return true; + } + + const auto cookie = xcb_query_tree(connection, *root); + const auto reply = xcb_query_tree_reply(connection, cookie, nullptr); + if (!reply) { + return std::nullopt; + } + + const auto guard = gsl::finally([&] { free(reply); }); + + const auto tree = xcb_query_tree_children(reply); + auto aboveTheWindow = false; + + for (auto i = 0, l = xcb_query_tree_children_length(reply); i < l; ++i) { + if (window == tree[i]) { + aboveTheWindow = true; + continue; + } + + if (!aboveTheWindow) { + continue; + } + + const auto geometry = XCBWindowGeometry(tree[i]); + if (!rect.intersects(geometry)) { + continue; + } + + const auto workspace = XCBWindowWorkspace(tree[i]); + if (workspace.has_value() + && windowWorkspace.has_value() + && *workspace != *windowWorkspace + && *workspace != 0xFFFFFFFF) { + continue; + } + + const auto mapped = XCBWindowMapped(tree[i]); + if (mapped.has_value() && !*mapped) { + continue; + } + + const auto hidden = XCBWindowHidden(tree[i]); + if (hidden.has_value() + && *hidden) { + continue; + } + + return true; + } + + return false; +} + bool SetXCBFrameExtents(QWindow *window, const QMargins &extents) { const auto connection = base::Platform::XCB::GetConnectionFromQt(); if (!connection) { @@ -189,12 +442,24 @@ void ClearTransientParent(not_null widget) { if (::Platform::IsX11()) { xcb_delete_property( base::Platform::XCB::GetConnectionFromQt(), - widget->windowHandle()->winId(), + widget->winId(), XCB_ATOM_WM_TRANSIENT_FOR); } #endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION } +std::optional IsOverlapped( + not_null widget, + const QRect &rect) { +#ifndef DESKTOP_APP_DISABLE_X11_INTEGRATION + if (::Platform::IsX11()) { + return XCBIsOverlapped(widget, rect); + } +#endif // !DESKTOP_APP_DISABLE_X11_INTEGRATION + + return std::nullopt; +} + bool WindowExtentsSupported() { #ifdef DESKTOP_APP_QT_PATCHED if (::Platform::IsWayland()) { diff --git a/ui/platform/mac/ui_utility_mac.mm b/ui/platform/mac/ui_utility_mac.mm index fd8b492..73b0c54 100644 --- a/ui/platform/mac/ui_utility_mac.mm +++ b/ui/platform/mac/ui_utility_mac.mm @@ -7,6 +7,7 @@ #include "ui/platform/mac/ui_utility_mac.h" #include "ui/integration.h" +#include "base/qt_adapters.h" #include #include @@ -99,6 +100,12 @@ void DrainMainQueue() { void IgnoreAllActivation(not_null widget) { } +std::optional IsOverlapped( + not_null widget, + const QRect &rect) { + return std::nullopt; +} + TitleControls::Layout TitleControlsLayout() { return TitleControls::Layout{ .left = { diff --git a/ui/platform/ui_platform_utility.h b/ui/platform/ui_platform_utility.h index 3360418..32f1760 100644 --- a/ui/platform/ui_platform_utility.h +++ b/ui/platform/ui_platform_utility.h @@ -30,6 +30,10 @@ void BringToBack(not_null widget); void IgnoreAllActivation(not_null widget); void ClearTransientParent(not_null widget); +[[nodiscard]] std::optional IsOverlapped( + not_null widget, + const QRect &rect); + [[nodiscard]] constexpr bool UseMainQueueGeneric(); void DrainMainQueue(); // Needed only if UseMainQueueGeneric() is false. diff --git a/ui/platform/win/ui_utility_win.cpp b/ui/platform/win/ui_utility_win.cpp index 4c4cfcb..034299f 100644 --- a/ui/platform/win/ui_utility_win.cpp +++ b/ui/platform/win/ui_utility_win.cpp @@ -11,6 +11,11 @@ #include #include +#include +#include + +using namespace Microsoft::WRL; + namespace Ui { namespace Platform { @@ -45,6 +50,55 @@ void IgnoreAllActivation(not_null widget) { ShowWindow(handle, SW_SHOW); } +std::optional IsOverlapped( + not_null widget, + const QRect &rect) { + const auto handle = reinterpret_cast(widget->window()->winId()); + Expects(handle != nullptr); + + ComPtr virtualDesktopManager; + HRESULT hr = CoCreateInstance( + CLSID_VirtualDesktopManager, + nullptr, + CLSCTX_ALL, + IID_PPV_ARGS(&virtualDesktopManager)); + + if (SUCCEEDED(hr)) { + BOOL isCurrent; + hr = virtualDesktopManager->IsWindowOnCurrentVirtualDesktop( + handle, + &isCurrent); + if (SUCCEEDED(hr) && !isCurrent) { + return true; + } + } + + std::vector visited; + const RECT nativeRect{ + rect.left(), + rect.top(), + rect.right(), + rect.bottom(), + }; + + for (auto curHandle = handle; + curHandle != nullptr && !ranges::contains(visited, curHandle); + curHandle = GetWindow(curHandle, GW_HWNDPREV)) { + visited.push_back(curHandle); + if (curHandle == handle) { + continue; + } + RECT testRect, intersection; + if (IsWindowVisible(curHandle) + && GetWindowRect(curHandle, &testRect) + && IntersectRect(&intersection, &nativeRect, &testRect)) { + return true; + } + } + + return false; +} + bool ShowWindowMenu(QWindow *window) { const auto pos = QCursor::pos(); diff --git a/ui/ui_utility.cpp b/ui/ui_utility.cpp index 6fdbafe..097cd74 100644 --- a/ui/ui_utility.cpp +++ b/ui/ui_utility.cpp @@ -6,11 +6,13 @@ // #include "ui/ui_utility.h" +#include "ui/platform/ui_platform_utility.h" #include "ui/style/style_core.h" #include #include #include +#include #include @@ -179,6 +181,36 @@ QPixmap PixmapFromImage(QImage &&image) { return QPixmap::fromImage(std::move(image), Qt::ColorOnly); } +bool IsContentVisible( + not_null widget, + const QRect &rect) { + Expects(widget->window()->windowHandle()); + + const auto activeOrNotOverlapped = [&] { + if (const auto active = widget->isActiveWindow()) { + return active; + } + + const auto mappedRect = QHighDpi::toNativePixels( + rect.isNull() + ? QRect( + widget->mapToGlobal(QPoint()), + widget->mapToGlobal( + QPoint(widget->width(), widget->height()))) + : QRect( + widget->mapToGlobal(rect.topLeft()), + widget->mapToGlobal(rect.bottomRight())), + widget->window()->windowHandle()); + + const auto overlapped = Platform::IsOverlapped(widget, mappedRect); + return overlapped.has_value() && !*overlapped; + }(); + + return activeOrNotOverlapped + && widget->isVisible() + && !widget->window()->isMinimized(); +} + void DisableCustomScaling() { qunsetenv("QT_DEVICE_PIXEL_RATIO"); qunsetenv("QT_SCALE_FACTOR"); diff --git a/ui/ui_utility.h b/ui/ui_utility.h index a174909..ece602d 100644 --- a/ui/ui_utility.h +++ b/ui/ui_utility.h @@ -187,6 +187,10 @@ QPointer MakeWeak(not_null object) { [[nodiscard]] QPixmap PixmapFromImage(QImage &&image); +[[nodiscard]] bool IsContentVisible( + not_null widget, + const QRect &rect = QRect()); + void DisableCustomScaling(); } // namespace Ui