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
This commit is contained in:
Vlad Zahorodnii 2019-09-27 13:33:42 +03:00
parent 14dc76f624
commit fb2d4c113f
14 changed files with 185 additions and 99 deletions

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -1512,7 +1512,8 @@ void SceneOpenGL2Window::performPaint(int mask, QRegion region, WindowPaintData
// render sub-surfaces
auto wp = windowPixmap<OpenGLWindowPixmap>();
const auto &children = wp ? wp->children() : QVector<WindowPixmap*>();
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;

View file

@ -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<QPainterWindowPixmap*>(pixmap));
paintSubSurface(painter, bufferOffset(), static_cast<QPainterWindowPixmap*>(pixmap));
}
if (!opaque) {

View file

@ -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();

177
scene.cpp
View file

@ -278,22 +278,14 @@ void Scene::paintSimpleScreen(int orig_mask, QRegion region)
if (client) {
opaqueFullscreen = client->isFullScreen();
}
X11Client *cc = dynamic_cast<X11Client *>(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<xcb_shape_get_rectangles_reply_t> 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<xcb_shape_get_rectangles_reply_t> 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<AbstractClient *>(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<AbstractClient*>(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();
}

10
scene.h
View file

@ -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 &region, qreal textureScale = 1.0) const;
WindowQuadList makeContentsQuads() const;
/**
* @brief Returns the WindowPixmap for this Window.
*
@ -377,8 +379,8 @@ private:
QScopedPointer<WindowPixmap> 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<WindowQuadList> cached_quad_list;
Q_DISABLE_COPY(Window)
};

View file

@ -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

View file

@ -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.

View file

@ -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);

View file

@ -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;

View file

@ -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)