From fb2d4c113f25754a21d24494fe740f7348a36e92 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Fri, 27 Sep 2019 13:33:42 +0300 Subject: [PATCH] Adjust scene for client-side decorated clients Summary: Currently our Scene is quite naive about geometry. It assumes that the window frame wraps the attached buffer/client. While this is true for X11 clients, such geometry model is not suitable for client-side decorated clients, in our case for xdg-shell clients that set window geometry other than the bounding rectangle of the main surface. In general, the proposed solution doesn't make any concrete assumptions about the order between frame and buffer geometry, however we may still need to reconsider the design of Scene once it starts to generate quads for sub-surfaces. Reviewers: #kwin, davidedmundson Reviewed By: #kwin, davidedmundson Subscribers: davidedmundson, romangg, kwin Tags: #kwin Maniphest Tasks: T10867 Differential Revision: https://phabricator.kde.org/D24462 --- abstract_client.h | 9 +- deleted.cpp | 12 ++ deleted.h | 4 + effects.cpp | 5 +- plugins/scenes/opengl/scene_opengl.cpp | 3 +- plugins/scenes/qpainter/scene_qpainter.cpp | 17 +- plugins/scenes/xrender/scene_xrender.cpp | 4 +- scene.cpp | 177 ++++++++++++--------- scene.h | 10 +- toplevel.cpp | 10 ++ toplevel.h | 19 +++ x11client.cpp | 5 + x11client.h | 1 + xcbutils.h | 8 + 14 files changed, 185 insertions(+), 99 deletions(-) diff --git a/abstract_client.h b/abstract_client.h index f1122a3e08..a80067e560 100644 --- a/abstract_client.h +++ b/abstract_client.h @@ -385,6 +385,7 @@ public: bool wantsTabFocus() const; + QMargins frameMargins() const override; QPoint clientPos() const override { return QPoint(borderLeft(), borderTop()); } @@ -835,14 +836,6 @@ public: */ virtual bool supportsWindowRules() const; - /** - * Returns the extents of the server-side decoration. - * - * Note that the returned margins object will have all margins set to 0 if - * the client doesn't have a server-side decoration. - */ - QMargins frameMargins() const; - public Q_SLOTS: virtual void closeWindow() = 0; diff --git a/deleted.cpp b/deleted.cpp index c03b7b3466..2dbced2739 100644 --- a/deleted.cpp +++ b/deleted.cpp @@ -95,6 +95,8 @@ void Deleted::copyToDeleted(Toplevel* c) Q_ASSERT(dynamic_cast< Deleted* >(c) == nullptr); Toplevel::copyToDeleted(c); m_bufferGeometry = c->bufferGeometry(); + m_bufferMargins = c->bufferMargins(); + m_frameMargins = c->frameMargins(); m_bufferScale = c->bufferScale(); desk = c->desktop(); m_desktops = c->desktops(); @@ -169,6 +171,16 @@ QRect Deleted::bufferGeometry() const return m_bufferGeometry; } +QMargins Deleted::bufferMargins() const +{ + return m_bufferMargins; +} + +QMargins Deleted::frameMargins() const +{ + return m_frameMargins; +} + qreal Deleted::bufferScale() const { return m_bufferScale; diff --git a/deleted.h b/deleted.h index 86ee54f409..4681d61ed8 100644 --- a/deleted.h +++ b/deleted.h @@ -44,6 +44,8 @@ public: void unrefWindow(); void discard(); QRect bufferGeometry() const override; + QMargins bufferMargins() const override; + QMargins frameMargins() const override; qreal bufferScale() const override; int desktop() const override; QStringList activities() const override; @@ -200,6 +202,8 @@ private: void removeTransientFor(Deleted *parent); QRect m_bufferGeometry; + QMargins m_bufferMargins; + QMargins m_frameMargins; int delete_refcount; int desk; diff --git a/effects.cpp b/effects.cpp index a559a5b3e8..b8a3c4312d 100644 --- a/effects.cpp +++ b/effects.cpp @@ -1951,7 +1951,10 @@ void EffectWindowImpl::setSceneWindow(Scene::Window* w) QRegion EffectWindowImpl::shape() const { - return sw ? sw->shape() : geometry(); + if (isX11Client() && sceneWindow()) { + return sceneWindow()->bufferShape(); + } + return geometry(); } QRect EffectWindowImpl::decorationInnerRect() const diff --git a/plugins/scenes/opengl/scene_opengl.cpp b/plugins/scenes/opengl/scene_opengl.cpp index 1b0974d1e5..8693bdc2dc 100644 --- a/plugins/scenes/opengl/scene_opengl.cpp +++ b/plugins/scenes/opengl/scene_opengl.cpp @@ -1512,7 +1512,8 @@ void SceneOpenGL2Window::performPaint(int mask, QRegion region, WindowPaintData // render sub-surfaces auto wp = windowPixmap(); const auto &children = wp ? wp->children() : QVector(); - windowMatrix.translate(toplevel->clientPos().x(), toplevel->clientPos().y()); + const QPoint mainSurfaceOffset = bufferOffset(); + windowMatrix.translate(mainSurfaceOffset.x(), mainSurfaceOffset.y()); for (auto pixmap : children) { if (pixmap->subSurface().isNull() || pixmap->subSurface()->surface().isNull() || !pixmap->subSurface()->surface()->isMapped()) { continue; diff --git a/plugins/scenes/qpainter/scene_qpainter.cpp b/plugins/scenes/qpainter/scene_qpainter.cpp index d0913135ca..3ae749df3a 100644 --- a/plugins/scenes/qpainter/scene_qpainter.cpp +++ b/plugins/scenes/qpainter/scene_qpainter.cpp @@ -287,14 +287,17 @@ void SceneQPainter::Window::performPaint(int mask, QRegion region, WindowPaintDa renderWindowDecorations(painter); // render content - const QRect target = QRect(toplevel->clientPos(), toplevel->clientSize()); - QSize srcSize = pixmap->image().size(); - if (pixmap->surface() && pixmap->surface()->scale() == 1 && srcSize != toplevel->clientSize()) { + QRect source; + QRect target; + if (toplevel->isClient()) { // special case for XWayland windows - srcSize = toplevel->clientSize(); + source = QRect(toplevel->clientPos(), toplevel->clientSize()); + target = source; + } else { + source = pixmap->image().rect(); + target = toplevel->bufferGeometry().translated(-pos()); } - const QRect src = QRect(toplevel->clientPos() + toplevel->clientContentPos(), srcSize); - painter->drawImage(target, pixmap->image(), src); + painter->drawImage(target, pixmap->image(), source); // render subsurfaces const auto &children = pixmap->children(); @@ -302,7 +305,7 @@ void SceneQPainter::Window::performPaint(int mask, QRegion region, WindowPaintDa if (pixmap->subSurface().isNull() || pixmap->subSurface()->surface().isNull() || !pixmap->subSurface()->surface()->isMapped()) { continue; } - paintSubSurface(painter, toplevel->clientPos(), static_cast(pixmap)); + paintSubSurface(painter, bufferOffset(), static_cast(pixmap)); } if (!opaque) { diff --git a/plugins/scenes/xrender/scene_xrender.cpp b/plugins/scenes/xrender/scene_xrender.cpp index 506b8ec675..d9aaa1ecf5 100644 --- a/plugins/scenes/xrender/scene_xrender.cpp +++ b/plugins/scenes/xrender/scene_xrender.cpp @@ -462,10 +462,10 @@ void SceneXrender::Window::performPaint(int mask, QRegion region, WindowPaintDat if (toplevel->shape()) { // "xeyes" + decoration transformed_shape -= cr; - transformed_shape += shape(); + transformed_shape += bufferShape(); } } else { - transformed_shape = shape(); + transformed_shape = bufferShape(); } if (toplevel->shadow()) { transformed_shape |= toplevel->shadow()->shadowRegion(); diff --git a/scene.cpp b/scene.cpp index c8f449031e..2920a4a71d 100644 --- a/scene.cpp +++ b/scene.cpp @@ -278,22 +278,14 @@ void Scene::paintSimpleScreen(int orig_mask, QRegion region) if (client) { opaqueFullscreen = client->isFullScreen(); } - X11Client *cc = dynamic_cast(client); - // the window is fully opaque - if (cc && cc->decorationHasAlpha()) { - // decoration uses alpha channel, so we may not exclude it in clipping - data.clip = window->clientShape().translated(window->x(), window->y()); - } else { - // decoration is fully opaque - if (client && client->isShade()) { - data.clip = QRegion(); - } else { - data.clip = window->shape().translated(window->x(), window->y()); - } + if (!(client && client->decorationHasAlpha())) { + data.clip = window->decorationShape().translated(window->pos()); } + data.clip |= window->clientShape().translated(window->pos() + window->bufferOffset()); } else if (toplevel->hasAlpha() && toplevel->opacity() == 1.0) { - // the window is partially opaque - data.clip = (window->clientShape() & toplevel->opaqueRegion().translated(toplevel->clientPos())).translated(window->x(), window->y()); + const QRegion clientShape = window->clientShape().translated(window->pos() + window->bufferOffset()); + const QRegion opaqueShape = toplevel->opaqueRegion().translated(window->pos() + toplevel->clientPos()); + data.clip = clientShape & opaqueShape; } else { data.clip = QRegion(); } @@ -687,7 +679,6 @@ Scene::Window::Window(Toplevel * c) , m_previousPixmap() , m_referencePixmapCounter(0) , disable_painting(0) - , shape_valid(false) , cached_quad_list(nullptr) { } @@ -731,47 +722,70 @@ void Scene::Window::discardShape() { // it is created on-demand and cached, simply // reset the flag - shape_valid = false; + m_bufferShapeIsValid = false; invalidateQuadsCache(); } -// Find out the shape of the window using the XShape extension -// or if shape is not set then simply it's the window geometry. -const QRegion &Scene::Window::shape() const +QRegion Scene::Window::bufferShape() const { - if (!shape_valid) { - if (toplevel->shape()) { - auto cookie = xcb_shape_get_rectangles_unchecked(connection(), toplevel->frameId(), XCB_SHAPE_SK_BOUNDING); - ScopedCPointer reply(xcb_shape_get_rectangles_reply(connection(), cookie, nullptr)); - if (!reply.isNull()) { - shape_region = QRegion(); - auto *rects = xcb_shape_get_rectangles_rectangles(reply.data()); - for (int i = 0; - i < xcb_shape_get_rectangles_rectangles_length(reply.data()); - ++i) - shape_region += QRegion(rects[ i ].x, rects[ i ].y, - rects[ i ].width, rects[ i ].height); - // make sure the shape is sane (X is async, maybe even XShape is broken) - shape_region &= QRegion(0, 0, width(), height()); - } else - shape_region = QRegion(); - } else - shape_region = QRegion(0, 0, width(), height()); - shape_valid = true; + if (m_bufferShapeIsValid) { + return m_bufferShape; } - return shape_region; + + const QRect bufferGeometry = toplevel->bufferGeometry(); + + if (toplevel->shape()) { + auto cookie = xcb_shape_get_rectangles_unchecked(connection(), toplevel->frameId(), XCB_SHAPE_SK_BOUNDING); + ScopedCPointer reply(xcb_shape_get_rectangles_reply(connection(), cookie, nullptr)); + if (!reply.isNull()) { + m_bufferShape = QRegion(); + const xcb_rectangle_t *rects = xcb_shape_get_rectangles_rectangles(reply.data()); + const int rectCount = xcb_shape_get_rectangles_rectangles_length(reply.data()); + for (int i = 0; i < rectCount; ++i) { + m_bufferShape += QRegion(rects[i].x, rects[i].y, rects[i].width, rects[i].height); + } + // make sure the shape is sane (X is async, maybe even XShape is broken) + m_bufferShape &= QRegion(0, 0, bufferGeometry.width(), bufferGeometry.height()); + } else { + m_bufferShape = QRegion(); + } + } else { + m_bufferShape = QRegion(0, 0, bufferGeometry.width(), bufferGeometry.height()); + } + + m_bufferShapeIsValid = true; + + return m_bufferShape; } QRegion Scene::Window::clientShape() const { - if (AbstractClient *c = dynamic_cast< AbstractClient * > (toplevel)) { - if (c->isShade()) + if (AbstractClient *client = qobject_cast(toplevel)) { + if (client->isShade()) { return QRegion(); + } } - // TODO: cache - const QRegion r = shape() & QRect(toplevel->clientPos(), toplevel->clientSize()); - return r.isEmpty() ? QRegion() : r; + const QRegion shape = bufferShape(); + const QMargins bufferMargins = toplevel->bufferMargins(); + if (bufferMargins.isNull()) { + return shape; + } + + const QRect clippingRect = QRect(QPoint(0, 0), toplevel->bufferGeometry().size()) - toplevel->bufferMargins(); + return shape & clippingRect; +} + +QRegion Scene::Window::decorationShape() const +{ + return QRegion(toplevel->decorationRect()) - toplevel->transparentRect(); +} + +QPoint Scene::Window::bufferOffset() const +{ + const QRect bufferGeometry = toplevel->bufferGeometry(); + const QRect frameGeometry = toplevel->frameGeometry(); + return bufferGeometry.topLeft() - frameGeometry.topLeft(); } bool Scene::Window::isVisible() const @@ -835,19 +849,14 @@ WindowQuadList Scene::Window::buildQuads(bool force) const { if (cached_quad_list != nullptr && !force) return *cached_quad_list; - WindowQuadList ret; - const qreal scale = toplevel->bufferScale(); + WindowQuadList ret = makeContentsQuads(); - if (toplevel->clientPos() == QPoint(0, 0) && toplevel->clientSize() == toplevel->decorationRect().size()) - ret = makeQuads(WindowQuadContents, shape(), QPoint(0,0), scale); // has no decoration - else { + if (!toplevel->frameMargins().isNull()) { AbstractClient *client = dynamic_cast(toplevel); - QRegion contents = clientShape(); QRegion center = toplevel->transparentRect(); - QRegion decoration = (client ? QRegion(client->decorationRect()) : shape()) - center; + const QRegion decoration = decorationShape(); qreal decorationScale = 1.0; - ret = makeQuads(WindowQuadContents, contents, toplevel->clientContentPos(), scale); QRect rects[4]; bool isShadedClient = false; @@ -932,32 +941,48 @@ WindowQuadList Scene::Window::makeDecorationQuads(const QRect *rects, const QReg return list; } +WindowQuadList Scene::Window::makeContentsQuads() const +{ + const QRegion contentsRegion = clientShape(); + if (contentsRegion.isEmpty()) { + return WindowQuadList(); + } + + const QPointF geometryOffset = bufferOffset(); + const qreal textureScale = toplevel->bufferScale(); + + WindowQuadList quads; + quads.reserve(contentsRegion.rectCount()); + + for (const QRectF &rect : contentsRegion) { + WindowQuad quad(WindowQuadContents); + + const qreal x0 = rect.left() + geometryOffset.x(); + const qreal y0 = rect.top() + geometryOffset.y(); + const qreal x1 = rect.right() + geometryOffset.x(); + const qreal y1 = rect.bottom() + geometryOffset.y(); + + const qreal u0 = rect.left() * textureScale; + const qreal v0 = rect.top() * textureScale; + const qreal u1 = rect.right() * textureScale; + const qreal v1 = rect.bottom() * textureScale; + + quad[0] = WindowVertex(QPointF(x0, y0), QPointF(u0, v0)); + quad[1] = WindowVertex(QPointF(x1, y0), QPointF(u1, v0)); + quad[2] = WindowVertex(QPointF(x1, y1), QPointF(u1, v1)); + quad[3] = WindowVertex(QPointF(x0, y1), QPointF(u0, v1)); + + quads << quad; + } + + return quads; +} + void Scene::Window::invalidateQuadsCache() { cached_quad_list.reset(); } -WindowQuadList Scene::Window::makeQuads(WindowQuadType type, const QRegion& reg, const QPoint &textureOffset, qreal scale) const -{ - WindowQuadList ret; - ret.reserve(reg.rectCount()); - for (const QRect &r : reg) { - WindowQuad quad(type); - // TODO asi mam spatne pravy dolni roh - bud tady, nebo v jinych castech - quad[ 0 ] = WindowVertex(QPointF(r.x(), r.y()), - QPointF(r.x() + textureOffset.x(), r.y() + textureOffset.y()) * scale); - quad[ 1 ] = WindowVertex(QPointF(r.x() + r.width(), r.y()), - QPointF(r.x() + r.width() + textureOffset.x(), r.y() + textureOffset.y()) * scale); - quad[ 2 ] = WindowVertex(QPointF(r.x() + r.width(), r.y() + r.height()), - QPointF(r.x() + r.width() + textureOffset.x(), r.y() + r.height() + textureOffset.y()) * scale); - quad[ 3 ] = WindowVertex(QPointF(r.x(), r.y() + r.height()), - QPointF(r.x() + textureOffset.x(), r.y() + r.height() + textureOffset.y()) * scale); - - ret.append(quad); - } - return ret; -} - void Scene::Window::updateShadow(Shadow* shadow) { if (m_shadow == shadow) { @@ -1029,14 +1054,14 @@ void WindowPixmap::create() xcb_free_pixmap(connection(), pix); return; } - if (!windowGeometry || - windowGeometry->width != toplevel()->width() || windowGeometry->height != toplevel()->height()) { + const QRect bufferGeometry = toplevel()->bufferGeometry(); + if (windowGeometry.size() != bufferGeometry.size()) { qCDebug(KWIN_CORE) << "Creating window pixmap failed: " << this; xcb_free_pixmap(connection(), pix); return; } m_pixmap = pix; - m_pixmapSize = QSize(toplevel()->width(), toplevel()->height()); + m_pixmapSize = bufferGeometry.size(); m_contentsRect = QRect(toplevel()->clientPos(), toplevel()->clientSize()); m_window->unreferencePreviousPixmap(); } diff --git a/scene.h b/scene.h index 7e539193bc..2430970ac6 100644 --- a/scene.h +++ b/scene.h @@ -330,8 +330,10 @@ public: // is the window fully opaque bool isOpaque() const; // shape of the window - const QRegion &shape() const; + QRegion bufferShape() const; QRegion clientShape() const; + QRegion decorationShape() const; + QPoint bufferOffset() const; void discardShape(); void updateToplevel(Toplevel* c); // creates initial quad list for the window @@ -343,8 +345,8 @@ public: void unreferencePreviousPixmap(); void invalidateQuadsCache(); protected: - WindowQuadList makeQuads(WindowQuadType type, const QRegion& reg, const QPoint &textureOffset = QPoint(0, 0), qreal textureScale = 1.0) const; WindowQuadList makeDecorationQuads(const QRect *rects, const QRegion ®ion, qreal textureScale = 1.0) const; + WindowQuadList makeContentsQuads() const; /** * @brief Returns the WindowPixmap for this Window. * @@ -377,8 +379,8 @@ private: QScopedPointer m_previousPixmap; int m_referencePixmapCounter; int disable_painting; - mutable QRegion shape_region; - mutable bool shape_valid; + mutable QRegion m_bufferShape; + mutable bool m_bufferShapeIsValid = false; mutable QScopedPointer cached_quad_list; Q_DISABLE_COPY(Window) }; diff --git a/toplevel.cpp b/toplevel.cpp index 18000445d6..811b6b14ca 100644 --- a/toplevel.cpp +++ b/toplevel.cpp @@ -804,5 +804,15 @@ bool Toplevel::isLocalhost() const return m_clientMachine->isLocal(); } +QMargins Toplevel::bufferMargins() const +{ + return QMargins(); +} + +QMargins Toplevel::frameMargins() const +{ + return QMargins(); +} + } // namespace diff --git a/toplevel.h b/toplevel.h index 190b6c8cb7..772f5e30f1 100644 --- a/toplevel.h +++ b/toplevel.h @@ -314,11 +314,30 @@ public: * occupies on the screen, in global screen coordinates. */ virtual QRect bufferGeometry() const = 0; + /** + * Returns the extents of invisible portions in the pixmap. + * + * An X11 pixmap may contain invisible space around the actual contents of the + * client. That space is reserved for server-side decoration, which we usually + * want to skip when building contents window quads. + * + * Default implementation returns a margins object with all margins set to 0. + */ + virtual QMargins bufferMargins() const; /** * Returns the geometry of the Toplevel, excluding invisible portions, e.g. * server-side and client-side drop shadows, etc. */ QRect frameGeometry() const; + /** + * Returns the extents of the server-side decoration. + * + * Note that the returned margins object will have all margins set to 0 if + * the client doesn't have a server-side decoration. + * + * Default implementation returns a margins object with all margins set to 0. + */ + virtual QMargins frameMargins() const; /** * The geometry of the Toplevel which accepts input events. This might be larger * than the actual geometry, e.g. to support resizing outside the window. diff --git a/x11client.cpp b/x11client.cpp index dff94c6d29..f4ce2278b8 100644 --- a/x11client.cpp +++ b/x11client.cpp @@ -1976,6 +1976,11 @@ QRect X11Client::bufferGeometry() const return geom; } +QMargins X11Client::bufferMargins() const +{ + return QMargins(borderLeft(), borderTop(), borderRight(), borderBottom()); +} + Xcb::Property X11Client::fetchShowOnScreenEdge() const { return Xcb::Property(false, window(), atoms->kde_screen_edge_show, XCB_ATOM_CARDINAL, 0, 1); diff --git a/x11client.h b/x11client.h index c2d7a6f8e6..3107e2d986 100644 --- a/x11client.h +++ b/x11client.h @@ -90,6 +90,7 @@ public: xcb_window_t frameId() const override; QRect bufferGeometry() const override; + QMargins bufferMargins() const override; bool isTransient() const override; bool groupTransient() const override; diff --git a/xcbutils.h b/xcbutils.h index 827d92498a..6c9c0879ae 100644 --- a/xcbutils.h +++ b/xcbutils.h @@ -566,6 +566,14 @@ public: } return QRect(geometry->x, geometry->y, geometry->width, geometry->height); } + + inline QSize size() { + const xcb_get_geometry_reply_t *geometry = data(); + if (!geometry) { + return QSize(); + } + return QSize(geometry->width, geometry->height); + } }; XCB_WRAPPER_DATA(TreeData, xcb_query_tree, xcb_window_t)