File indexing completed on 2024-05-19 16:34:45

0001 /*
0002     SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
0003     SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "scene/decorationitem.h"
0009 #include "composite.h"
0010 #include "core/output.h"
0011 #include "decorations/decoratedclient.h"
0012 #include "deleted.h"
0013 #include "scene/workspacescene.h"
0014 #include "utils/common.h"
0015 #include "window.h"
0016 
0017 #include <cmath>
0018 
0019 #include <KDecoration2/DecoratedClient>
0020 #include <KDecoration2/Decoration>
0021 
0022 #include <QPainter>
0023 
0024 namespace KWin
0025 {
0026 
0027 DecorationRenderer::DecorationRenderer(Decoration::DecoratedClientImpl *client)
0028     : m_client(client)
0029     , m_imageSizesDirty(true)
0030 {
0031     connect(client->decoration(), &KDecoration2::Decoration::damaged,
0032             this, &DecorationRenderer::addDamage);
0033 
0034     connect(client->decoration(), &KDecoration2::Decoration::bordersChanged,
0035             this, &DecorationRenderer::invalidate);
0036     connect(client->decoratedClient(), &KDecoration2::DecoratedClient::sizeChanged,
0037             this, &DecorationRenderer::invalidate);
0038 
0039     invalidate();
0040 }
0041 
0042 Decoration::DecoratedClientImpl *DecorationRenderer::client() const
0043 {
0044     return m_client;
0045 }
0046 
0047 void DecorationRenderer::invalidate()
0048 {
0049     if (m_client) {
0050         addDamage(m_client->window()->rect().toAlignedRect());
0051     }
0052     m_imageSizesDirty = true;
0053 }
0054 
0055 QRegion DecorationRenderer::damage() const
0056 {
0057     return m_damage;
0058 }
0059 
0060 void DecorationRenderer::addDamage(const QRegion &region)
0061 {
0062     m_damage += region;
0063     Q_EMIT damaged(region);
0064 }
0065 
0066 void DecorationRenderer::resetDamage()
0067 {
0068     m_damage = QRegion();
0069 }
0070 
0071 qreal DecorationRenderer::effectiveDevicePixelRatio() const
0072 {
0073     // QPainter won't let paint with a device pixel ratio less than 1.
0074     return std::max(qreal(1.0), devicePixelRatio());
0075 }
0076 
0077 qreal DecorationRenderer::devicePixelRatio() const
0078 {
0079     return m_devicePixelRatio;
0080 }
0081 
0082 void DecorationRenderer::setDevicePixelRatio(qreal dpr)
0083 {
0084     if (m_devicePixelRatio != dpr) {
0085         m_devicePixelRatio = dpr;
0086         invalidate();
0087     }
0088 }
0089 
0090 QImage DecorationRenderer::renderToImage(const QRect &geo)
0091 {
0092     Q_ASSERT(m_client);
0093 
0094     // Guess the pixel format of the X pixmap into which the QImage will be copied.
0095     QImage::Format format;
0096     const int depth = client()->window()->depth();
0097     switch (depth) {
0098     case 30:
0099         format = QImage::Format_A2RGB30_Premultiplied;
0100         break;
0101     case 24:
0102     case 32:
0103         format = QImage::Format_ARGB32_Premultiplied;
0104         break;
0105     default:
0106         qCCritical(KWIN_CORE) << "Unsupported client depth" << depth;
0107         format = QImage::Format_ARGB32_Premultiplied;
0108         break;
0109     };
0110 
0111     QImage image(geo.width() * m_devicePixelRatio, geo.height() * m_devicePixelRatio, format);
0112     image.setDevicePixelRatio(m_devicePixelRatio);
0113     image.fill(Qt::transparent);
0114     QPainter p(&image);
0115     p.setRenderHint(QPainter::Antialiasing);
0116     p.setWindow(QRect(geo.topLeft(), geo.size() * effectiveDevicePixelRatio()));
0117     p.setClipRect(geo);
0118     renderToPainter(&p, geo);
0119     return image;
0120 }
0121 
0122 void DecorationRenderer::renderToPainter(QPainter *painter, const QRect &rect)
0123 {
0124     client()->decoration()->paint(painter, rect);
0125 }
0126 
0127 DecorationItem::DecorationItem(KDecoration2::Decoration *decoration, Window *window, Scene *scene, Item *parent)
0128     : Item(scene, parent)
0129     , m_window(window)
0130 {
0131     m_renderer.reset(Compositor::self()->scene()->createDecorationRenderer(window->decoratedClient()));
0132 
0133     connect(window, &Window::frameGeometryChanged,
0134             this, &DecorationItem::handleFrameGeometryChanged);
0135     connect(window, &Window::windowClosed,
0136             this, &DecorationItem::handleWindowClosed);
0137     connect(window, &Window::screenChanged,
0138             this, &DecorationItem::handleOutputChanged);
0139 
0140     connect(decoration, &KDecoration2::Decoration::bordersChanged,
0141             this, &DecorationItem::discardQuads);
0142 
0143     connect(renderer(), &DecorationRenderer::damaged,
0144             this, qOverload<const QRegion &>(&Item::scheduleRepaint));
0145 
0146     // this toSize is to match that DecoratedWindow also rounds
0147     setSize(window->size().toSize());
0148     handleOutputChanged();
0149 }
0150 
0151 QVector<QRectF> DecorationItem::shape() const
0152 {
0153     QRectF left, top, right, bottom;
0154     m_window->layoutDecorationRects(left, top, right, bottom);
0155     return {left, top, right, bottom};
0156 }
0157 
0158 QRegion DecorationItem::opaque() const
0159 {
0160     if (m_window->decorationHasAlpha()) {
0161         return QRegion();
0162     }
0163     QRectF left, top, right, bottom;
0164     m_window->layoutDecorationRects(left, top, right, bottom);
0165     return QRegion(left.toRect()).united(top.toRect()).united(right.toRect()).united(bottom.toRect());
0166 }
0167 
0168 void DecorationItem::preprocess()
0169 {
0170     const QRegion damage = m_renderer->damage();
0171     if (!damage.isEmpty()) {
0172         m_renderer->render(damage);
0173         m_renderer->resetDamage();
0174     }
0175 }
0176 
0177 void DecorationItem::handleOutputChanged()
0178 {
0179     if (m_output) {
0180         disconnect(m_output, &Output::scaleChanged, this, &DecorationItem::handleOutputScaleChanged);
0181     }
0182 
0183     m_output = m_window->output();
0184 
0185     if (m_output) {
0186         handleOutputScaleChanged();
0187         connect(m_output, &Output::scaleChanged, this, &DecorationItem::handleOutputScaleChanged);
0188     }
0189 }
0190 
0191 void DecorationItem::handleOutputScaleChanged()
0192 {
0193     const qreal dpr = m_output->scale();
0194     if (m_renderer->devicePixelRatio() != dpr) {
0195         m_renderer->setDevicePixelRatio(dpr);
0196         discardQuads();
0197     }
0198 }
0199 
0200 void DecorationItem::handleFrameGeometryChanged()
0201 {
0202     setSize(m_window->size().toSize());
0203 }
0204 
0205 void DecorationItem::handleWindowClosed(Window *original, Deleted *deleted)
0206 {
0207     m_window = deleted;
0208 
0209     // If the decoration is about to be destroyed, render the decoration for the last time.
0210     effects->makeOpenGLContextCurrent();
0211     preprocess();
0212 }
0213 
0214 DecorationRenderer *DecorationItem::renderer() const
0215 {
0216     return m_renderer.get();
0217 }
0218 
0219 Window *DecorationItem::window() const
0220 {
0221     return m_window;
0222 }
0223 
0224 WindowQuad buildQuad(const QRectF &partRect, const QPoint &textureOffset,
0225                      const qreal devicePixelRatio, bool rotated)
0226 {
0227     const QRectF &r = partRect;
0228     const int p = DecorationRenderer::TexturePad;
0229 
0230     const int x0 = r.x();
0231     const int y0 = r.y();
0232     const int x1 = r.x() + r.width();
0233     const int y1 = r.y() + r.height();
0234 
0235     WindowQuad quad;
0236     if (rotated) {
0237         const int u0 = textureOffset.y() + p;
0238         const int v0 = textureOffset.x() + p;
0239         const int u1 = textureOffset.y() + p + (r.width() * devicePixelRatio);
0240         const int v1 = textureOffset.x() + p + (r.height() * devicePixelRatio);
0241 
0242         quad[0] = WindowVertex(x0, y0, v0, u1); // Top-left
0243         quad[1] = WindowVertex(x1, y0, v0, u0); // Top-right
0244         quad[2] = WindowVertex(x1, y1, v1, u0); // Bottom-right
0245         quad[3] = WindowVertex(x0, y1, v1, u1); // Bottom-left
0246     } else {
0247         const int u0 = textureOffset.x() + p;
0248         const int v0 = textureOffset.y() + p;
0249         const int u1 = textureOffset.x() + p + (r.width() * devicePixelRatio);
0250         const int v1 = textureOffset.y() + p + (r.height() * devicePixelRatio);
0251 
0252         quad[0] = WindowVertex(x0, y0, u0, v0); // Top-left
0253         quad[1] = WindowVertex(x1, y0, u1, v0); // Top-right
0254         quad[2] = WindowVertex(x1, y1, u1, v1); // Bottom-right
0255         quad[3] = WindowVertex(x0, y1, u0, v1); // Bottom-left
0256     }
0257     return quad;
0258 }
0259 
0260 WindowQuadList DecorationItem::buildQuads() const
0261 {
0262     if (m_window->frameMargins().isNull()) {
0263         return WindowQuadList();
0264     }
0265 
0266     QRectF left, top, right, bottom;
0267     const qreal devicePixelRatio = m_renderer->effectiveDevicePixelRatio();
0268     const int texturePad = DecorationRenderer::TexturePad;
0269 
0270     m_window->layoutDecorationRects(left, top, right, bottom);
0271 
0272     const int topHeight = std::ceil(top.height() * devicePixelRatio);
0273     const int bottomHeight = std::ceil(bottom.height() * devicePixelRatio);
0274     const int leftWidth = std::ceil(left.width() * devicePixelRatio);
0275 
0276     const QPoint topPosition(0, 0);
0277     const QPoint bottomPosition(0, topPosition.y() + topHeight + (2 * texturePad));
0278     const QPoint leftPosition(0, bottomPosition.y() + bottomHeight + (2 * texturePad));
0279     const QPoint rightPosition(0, leftPosition.y() + leftWidth + (2 * texturePad));
0280 
0281     WindowQuadList list;
0282     if (left.isValid()) {
0283         list.append(buildQuad(left, leftPosition, devicePixelRatio, true));
0284     }
0285     if (top.isValid()) {
0286         list.append(buildQuad(top, topPosition, devicePixelRatio, false));
0287     }
0288     if (right.isValid()) {
0289         list.append(buildQuad(right, rightPosition, devicePixelRatio, true));
0290     }
0291     if (bottom.isValid()) {
0292         list.append(buildQuad(bottom, bottomPosition, devicePixelRatio, false));
0293     }
0294     return list;
0295 }
0296 
0297 } // namespace KWin