1 /*
2     SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
3     SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
4 
5     SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 
8 #include "decorationitem.h"
9 #include "abstract_client.h"
10 #include "composite.h"
11 #include "decorations/decoratedclient.h"
12 #include "deleted.h"
13 #include "scene.h"
14 #include "utils.h"
15 
16 #include <KDecoration2/Decoration>
17 #include <KDecoration2/DecoratedClient>
18 
19 #include <QPainter>
20 
21 namespace KWin
22 {
23 
DecorationRenderer(Decoration::DecoratedClientImpl * client)24 DecorationRenderer::DecorationRenderer(Decoration::DecoratedClientImpl *client)
25     : m_client(client)
26     , m_imageSizesDirty(true)
27 {
28     connect(client->decoration(), &KDecoration2::Decoration::damaged,
29             this, &DecorationRenderer::addDamage);
30 
31     connect(client->client(), &AbstractClient::screenScaleChanged,
32             this, &DecorationRenderer::invalidate);
33     connect(client->decoration(), &KDecoration2::Decoration::bordersChanged,
34             this, &DecorationRenderer::invalidate);
35     connect(client->decoratedClient(), &KDecoration2::DecoratedClient::sizeChanged,
36             this, &DecorationRenderer::invalidate);
37 
38     invalidate();
39 }
40 
client() const41 Decoration::DecoratedClientImpl *DecorationRenderer::client() const
42 {
43     return m_client;
44 }
45 
invalidate()46 void DecorationRenderer::invalidate()
47 {
48     if (m_client) {
49         addDamage(m_client->client()->rect());
50     }
51     m_imageSizesDirty = true;
52 }
53 
damage() const54 QRegion DecorationRenderer::damage() const
55 {
56     return m_damage;
57 }
58 
addDamage(const QRegion & region)59 void DecorationRenderer::addDamage(const QRegion &region)
60 {
61     m_damage += region;
62     Q_EMIT damaged(region);
63 }
64 
resetDamage()65 void DecorationRenderer::resetDamage()
66 {
67     m_damage = QRegion();
68 }
69 
renderToImage(const QRect & geo)70 QImage DecorationRenderer::renderToImage(const QRect &geo)
71 {
72     Q_ASSERT(m_client);
73     auto dpr = client()->client()->screenScale();
74 
75     // Guess the pixel format of the X pixmap into which the QImage will be copied.
76     QImage::Format format;
77     const int depth = client()->client()->depth();
78     switch (depth) {
79     case 30:
80         format = QImage::Format_A2RGB30_Premultiplied;
81         break;
82     case 24:
83     case 32:
84         format = QImage::Format_ARGB32_Premultiplied;
85         break;
86     default:
87         qCCritical(KWIN_CORE) << "Unsupported client depth" << depth;
88         format = QImage::Format_ARGB32_Premultiplied;
89         break;
90     };
91 
92     QImage image(geo.width() * dpr, geo.height() * dpr, format);
93     image.setDevicePixelRatio(dpr);
94     image.fill(Qt::transparent);
95     QPainter p(&image);
96     p.setRenderHint(QPainter::Antialiasing);
97     p.setWindow(QRect(geo.topLeft(), geo.size() * qPainterEffectiveDevicePixelRatio(&p)));
98     p.setClipRect(geo);
99     renderToPainter(&p, geo);
100     return image;
101 }
102 
renderToPainter(QPainter * painter,const QRect & rect)103 void DecorationRenderer::renderToPainter(QPainter *painter, const QRect &rect)
104 {
105     client()->decoration()->paint(painter, rect);
106 }
107 
DecorationItem(KDecoration2::Decoration * decoration,AbstractClient * window,Item * parent)108 DecorationItem::DecorationItem(KDecoration2::Decoration *decoration, AbstractClient *window, Item *parent)
109     : Item(parent)
110     , m_window(window)
111 {
112     m_renderer.reset(Compositor::self()->scene()->createDecorationRenderer(window->decoratedClient()));
113 
114     connect(window, &Toplevel::frameGeometryChanged,
115             this, &DecorationItem::handleFrameGeometryChanged);
116     connect(window, &Toplevel::windowClosed,
117             this, &DecorationItem::handleWindowClosed);
118 
119     connect(window, &Toplevel::screenScaleChanged,
120             this, &DecorationItem::discardQuads);
121     connect(decoration, &KDecoration2::Decoration::bordersChanged,
122             this, &DecorationItem::discardQuads);
123 
124     connect(renderer(), &DecorationRenderer::damaged,
125             this, &DecorationItem::scheduleRepaint);
126 
127     setSize(window->size());
128 }
129 
preprocess()130 void DecorationItem::preprocess()
131 {
132     const QRegion damage = m_renderer->damage();
133     if (!damage.isEmpty()) {
134         m_renderer->render(damage);
135         m_renderer->resetDamage();
136     }
137 }
138 
handleFrameGeometryChanged()139 void DecorationItem::handleFrameGeometryChanged()
140 {
141     setSize(m_window->size());
142 }
143 
handleWindowClosed(Toplevel * original,Deleted * deleted)144 void DecorationItem::handleWindowClosed(Toplevel *original, Deleted *deleted)
145 {
146     Q_UNUSED(original)
147     m_window = deleted;
148 
149     // If the decoration is about to be destroyed, render the decoration for the last time.
150     preprocess();
151 }
152 
renderer() const153 DecorationRenderer *DecorationItem::renderer() const
154 {
155     return m_renderer.data();
156 }
157 
buildQuads() const158 WindowQuadList DecorationItem::buildQuads() const
159 {
160     if (m_window->frameMargins().isNull()) {
161         return WindowQuadList();
162     }
163 
164     QRect rects[4];
165 
166     if (const AbstractClient *client = qobject_cast<const AbstractClient *>(m_window)) {
167         client->layoutDecorationRects(rects[0], rects[1], rects[2], rects[3]);
168     } else if (const Deleted *deleted = qobject_cast<const Deleted *>(m_window)) {
169         deleted->layoutDecorationRects(rects[0], rects[1], rects[2], rects[3]);
170     }
171 
172     const qreal textureScale = m_window->screenScale();
173     const int padding = 1;
174 
175     const QPoint topSpritePosition(padding, padding);
176     const QPoint bottomSpritePosition(padding, topSpritePosition.y() + rects[1].height() + 2 * padding);
177     const QPoint leftSpritePosition(bottomSpritePosition.y() + rects[3].height() + 2 * padding, padding);
178     const QPoint rightSpritePosition(leftSpritePosition.x() + rects[0].width() + 2 * padding, padding);
179 
180     const QPoint offsets[4] = {
181         QPoint(-rects[0].x(), -rects[0].y()) + leftSpritePosition,
182         QPoint(-rects[1].x(), -rects[1].y()) + topSpritePosition,
183         QPoint(-rects[2].x(), -rects[2].y()) + rightSpritePosition,
184         QPoint(-rects[3].x(), -rects[3].y()) + bottomSpritePosition,
185     };
186 
187     const Qt::Orientation orientations[4] = {
188         Qt::Vertical,   // Left
189         Qt::Horizontal, // Top
190         Qt::Vertical,   // Right
191         Qt::Horizontal, // Bottom
192     };
193 
194     WindowQuadList list;
195     list.reserve(4);
196 
197     for (int i = 0; i < 4; ++i) {
198         const QRect &r = rects[i];
199         if (!r.isValid()) {
200             continue;
201         }
202 
203         const int x0 = r.x();
204         const int y0 = r.y();
205         const int x1 = r.x() + r.width();
206         const int y1 = r.y() + r.height();
207 
208         const int u0 = (x0 + offsets[i].x()) * textureScale;
209         const int v0 = (y0 + offsets[i].y()) * textureScale;
210         const int u1 = (x1 + offsets[i].x()) * textureScale;
211         const int v1 = (y1 + offsets[i].y()) * textureScale;
212 
213         WindowQuad quad;
214 
215         if (orientations[i] == Qt::Vertical) {
216             quad[0] = WindowVertex(x0, y0, v0, u0); // Top-left
217             quad[1] = WindowVertex(x1, y0, v0, u1); // Top-right
218             quad[2] = WindowVertex(x1, y1, v1, u1); // Bottom-right
219             quad[3] = WindowVertex(x0, y1, v1, u0); // Bottom-left
220         } else {
221             quad[0] = WindowVertex(x0, y0, u0, v0); // Top-left
222             quad[1] = WindowVertex(x1, y0, u1, v0); // Top-right
223             quad[2] = WindowVertex(x1, y1, u1, v1); // Bottom-right
224             quad[3] = WindowVertex(x0, y1, u0, v1); // Bottom-left
225         }
226 
227         list.append(quad);
228     }
229 
230     return list;
231 }
232 
233 } // namespace KWin
234