Commit fb2d4c11 authored by Vlad Zahorodnii's avatar Vlad Zahorodnii
Browse files

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
parent 14dc76f6
......@@ -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;
......
......@@ -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;
......
......@@ -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;
......
......@@ -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
......
......@@ -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;
......
......@@ -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) {
......
......@@ -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();
......
......@@ -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
{
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;
QRegion Scene::Window::bufferShape() const
{
if (m_bufferShapeIsValid) {
return m_bufferShape;
}
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());
}
return shape_region;
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,30 +941,46 @@ WindowQuadList Scene::Window::makeDecorationQuads(const QRect *rects, const QReg
return list;
}
void Scene::Window::invalidateQuadsCache()
WindowQuadList Scene::Window::makeContentsQuads() const
{
cached_quad_list.reset();
}
const QRegion contentsRegion = clientShape();
if (contentsRegion.isEmpty()) {
return WindowQuadList();
}
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);
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();
ret.append(quad);
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 ret;
return quads;
}
void Scene::Window::invalidateQuadsCache()
{
cached_quad_list.reset();
}
void Scene::Window::updateShadow(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();
}
......
......@@ -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)
};
......
......@@ -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
......@@ -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.
......
......@@ -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);
......
......@@ -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;
......
......@@ -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)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment