1 /*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2011 Martin Gräßlin <mgraesslin@kde.org>
6 SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
7
8 SPDX-License-Identifier: GPL-2.0-or-later
9 */
10 #include "shadow.h"
11 // kwin
12 #include "atoms.h"
13 #include "abstract_client.h"
14 #include "composite.h"
15 #include "internal_client.h"
16 #include "scene.h"
17 #include "toplevel.h"
18 #include "wayland_server.h"
19
20 #include <KDecoration2/Decoration>
21 #include <KDecoration2/DecorationShadow>
22
23 #include <KWaylandServer/shmclientbuffer.h>
24 #include <KWaylandServer/shadow_interface.h>
25 #include <KWaylandServer/surface_interface.h>
26
27 #include <QWindow>
28
29 Q_DECLARE_METATYPE(QMargins)
30
31 namespace KWin
32 {
33
Shadow(Toplevel * toplevel)34 Shadow::Shadow(Toplevel *toplevel)
35 : m_topLevel(toplevel)
36 , m_cachedSize(toplevel->size())
37 , m_decorationShadow(nullptr)
38 {
39 connect(m_topLevel, &Toplevel::frameGeometryChanged, this, &Shadow::geometryChanged);
40 }
41
~Shadow()42 Shadow::~Shadow()
43 {
44 }
45
createShadow(Toplevel * toplevel)46 Shadow *Shadow::createShadow(Toplevel *toplevel)
47 {
48 Shadow *shadow = createShadowFromDecoration(toplevel);
49 if (!shadow && waylandServer()) {
50 shadow = createShadowFromWayland(toplevel);
51 }
52 if (!shadow && kwinApp()->x11Connection()) {
53 shadow = createShadowFromX11(toplevel);
54 }
55 if (!shadow) {
56 shadow = createShadowFromInternalWindow(toplevel);
57 }
58 return shadow;
59 }
60
createShadowFromX11(Toplevel * toplevel)61 Shadow *Shadow::createShadowFromX11(Toplevel *toplevel)
62 {
63 auto data = Shadow::readX11ShadowProperty(toplevel->window());
64 if (!data.isEmpty()) {
65 Shadow *shadow = Compositor::self()->scene()->createShadow(toplevel);
66
67 if (!shadow->init(data)) {
68 delete shadow;
69 return nullptr;
70 }
71 return shadow;
72 } else {
73 return nullptr;
74 }
75 }
76
createShadowFromDecoration(Toplevel * toplevel)77 Shadow *Shadow::createShadowFromDecoration(Toplevel *toplevel)
78 {
79 AbstractClient *c = qobject_cast<AbstractClient*>(toplevel);
80 if (!c) {
81 return nullptr;
82 }
83 if (!c->decoration()) {
84 return nullptr;
85 }
86 Shadow *shadow = Compositor::self()->scene()->createShadow(toplevel);
87 if (!shadow->init(c->decoration())) {
88 delete shadow;
89 return nullptr;
90 }
91 return shadow;
92 }
93
createShadowFromWayland(Toplevel * toplevel)94 Shadow *Shadow::createShadowFromWayland(Toplevel *toplevel)
95 {
96 auto surface = toplevel->surface();
97 if (!surface) {
98 return nullptr;
99 }
100 const auto s = surface->shadow();
101 if (!s) {
102 return nullptr;
103 }
104 Shadow *shadow = Compositor::self()->scene()->createShadow(toplevel);
105 if (!shadow->init(s)) {
106 delete shadow;
107 return nullptr;
108 }
109 return shadow;
110 }
111
createShadowFromInternalWindow(Toplevel * toplevel)112 Shadow *Shadow::createShadowFromInternalWindow(Toplevel *toplevel)
113 {
114 const InternalClient *client = qobject_cast<InternalClient *>(toplevel);
115 if (!client) {
116 return nullptr;
117 }
118 const QWindow *window = client->internalWindow();
119 if (!window) {
120 return nullptr;
121 }
122 Shadow *shadow = Compositor::self()->scene()->createShadow(toplevel);
123 if (!shadow->init(window)) {
124 delete shadow;
125 return nullptr;
126 }
127 return shadow;
128 }
129
readX11ShadowProperty(xcb_window_t id)130 QVector< uint32_t > Shadow::readX11ShadowProperty(xcb_window_t id)
131 {
132 QVector<uint32_t> ret;
133 if (id != XCB_WINDOW_NONE) {
134 Xcb::Property property(false, id, atoms->kde_net_wm_shadow, XCB_ATOM_CARDINAL, 0, 12);
135 uint32_t *shadow = property.value<uint32_t*>();
136 if (shadow) {
137 ret.reserve(12);
138 for (int i=0; i<12; ++i) {
139 ret << shadow[i];
140 }
141 }
142 }
143 return ret;
144 }
145
init(const QVector<uint32_t> & data)146 bool Shadow::init(const QVector< uint32_t > &data)
147 {
148 QVector<Xcb::WindowGeometry> pixmapGeometries(ShadowElementsCount);
149 QVector<xcb_get_image_cookie_t> getImageCookies(ShadowElementsCount);
150 auto *c = connection();
151 for (int i = 0; i < ShadowElementsCount; ++i) {
152 pixmapGeometries[i] = Xcb::WindowGeometry(data[i]);
153 }
154 auto discardReplies = [&getImageCookies](int start) {
155 for (int i = start; i < getImageCookies.size(); ++i) {
156 xcb_discard_reply(connection(), getImageCookies.at(i).sequence);
157 }
158 };
159 for (int i = 0; i < ShadowElementsCount; ++i) {
160 auto &geo = pixmapGeometries[i];
161 if (geo.isNull()) {
162 discardReplies(0);
163 return false;
164 }
165 getImageCookies[i] = xcb_get_image_unchecked(c, XCB_IMAGE_FORMAT_Z_PIXMAP, data[i],
166 0, 0, geo->width, geo->height, ~0);
167 }
168 for (int i = 0; i < ShadowElementsCount; ++i) {
169 auto *reply = xcb_get_image_reply(c, getImageCookies.at(i), nullptr);
170 if (!reply) {
171 discardReplies(i+1);
172 return false;
173 }
174 auto &geo = pixmapGeometries[i];
175 QImage image(xcb_get_image_data(reply), geo->width, geo->height, QImage::Format_ARGB32);
176 m_shadowElements[i] = QPixmap::fromImage(image);
177 free(reply);
178 }
179 m_offset = QMargins(data[ShadowElementsCount + 3],
180 data[ShadowElementsCount],
181 data[ShadowElementsCount + 1],
182 data[ShadowElementsCount + 2]);
183 Q_EMIT offsetChanged();
184 if (!prepareBackend()) {
185 return false;
186 }
187 Q_EMIT textureChanged();
188 return true;
189 }
190
init(KDecoration2::Decoration * decoration)191 bool Shadow::init(KDecoration2::Decoration *decoration)
192 {
193 if (m_decorationShadow) {
194 // disconnect previous connections
195 disconnect(m_decorationShadow.data(), &KDecoration2::DecorationShadow::innerShadowRectChanged, m_topLevel, &Toplevel::updateShadow);
196 disconnect(m_decorationShadow.data(), &KDecoration2::DecorationShadow::shadowChanged, m_topLevel, &Toplevel::updateShadow);
197 disconnect(m_decorationShadow.data(), &KDecoration2::DecorationShadow::paddingChanged, m_topLevel, &Toplevel::updateShadow);
198 }
199 m_decorationShadow = decoration->shadow();
200 if (!m_decorationShadow) {
201 return false;
202 }
203 // setup connections - all just mapped to recreate
204 connect(m_decorationShadow.data(), &KDecoration2::DecorationShadow::innerShadowRectChanged, m_topLevel, &Toplevel::updateShadow);
205 connect(m_decorationShadow.data(), &KDecoration2::DecorationShadow::shadowChanged, m_topLevel, &Toplevel::updateShadow);
206 connect(m_decorationShadow.data(), &KDecoration2::DecorationShadow::paddingChanged, m_topLevel, &Toplevel::updateShadow);
207
208 m_offset = m_decorationShadow->padding();
209 Q_EMIT offsetChanged();
210 if (!prepareBackend()) {
211 return false;
212 }
213 Q_EMIT textureChanged();
214 return true;
215 }
216
shadowTileForBuffer(KWaylandServer::ClientBuffer * buffer)217 static QPixmap shadowTileForBuffer(KWaylandServer::ClientBuffer *buffer)
218 {
219 auto shmBuffer = qobject_cast<KWaylandServer::ShmClientBuffer *>(buffer);
220 if (shmBuffer) {
221 return QPixmap::fromImage(shmBuffer->data().copy());
222 }
223 return QPixmap();
224 }
225
init(const QPointer<KWaylandServer::ShadowInterface> & shadow)226 bool Shadow::init(const QPointer< KWaylandServer::ShadowInterface > &shadow)
227 {
228 if (!shadow) {
229 return false;
230 }
231
232 m_shadowElements[ShadowElementTop] = shadowTileForBuffer(shadow->top());
233 m_shadowElements[ShadowElementTopRight] = shadowTileForBuffer(shadow->topRight());
234 m_shadowElements[ShadowElementRight] = shadowTileForBuffer(shadow->right());
235 m_shadowElements[ShadowElementBottomRight] = shadowTileForBuffer(shadow->bottomRight());
236 m_shadowElements[ShadowElementBottom] = shadowTileForBuffer(shadow->bottom());
237 m_shadowElements[ShadowElementBottomLeft] = shadowTileForBuffer(shadow->bottomLeft());
238 m_shadowElements[ShadowElementLeft] = shadowTileForBuffer(shadow->left());
239 m_shadowElements[ShadowElementTopLeft] = shadowTileForBuffer(shadow->topLeft());
240
241 m_offset = shadow->offset().toMargins();
242 Q_EMIT offsetChanged();
243 if (!prepareBackend()) {
244 return false;
245 }
246 Q_EMIT textureChanged();
247 return true;
248 }
249
init(const QWindow * window)250 bool Shadow::init(const QWindow *window)
251 {
252 const bool isEnabled = window->property("kwin_shadow_enabled").toBool();
253 if (!isEnabled) {
254 return false;
255 }
256
257 const QImage leftTile = window->property("kwin_shadow_left_tile").value<QImage>();
258 const QImage topLeftTile = window->property("kwin_shadow_top_left_tile").value<QImage>();
259 const QImage topTile = window->property("kwin_shadow_top_tile").value<QImage>();
260 const QImage topRightTile = window->property("kwin_shadow_top_right_tile").value<QImage>();
261 const QImage rightTile = window->property("kwin_shadow_right_tile").value<QImage>();
262 const QImage bottomRightTile = window->property("kwin_shadow_bottom_right_tile").value<QImage>();
263 const QImage bottomTile = window->property("kwin_shadow_bottom_tile").value<QImage>();
264 const QImage bottomLeftTile = window->property("kwin_shadow_bottom_left_tile").value<QImage>();
265
266 m_shadowElements[ShadowElementLeft] = QPixmap::fromImage(leftTile);
267 m_shadowElements[ShadowElementTopLeft] = QPixmap::fromImage(topLeftTile);
268 m_shadowElements[ShadowElementTop] = QPixmap::fromImage(topTile);
269 m_shadowElements[ShadowElementTopRight] = QPixmap::fromImage(topRightTile);
270 m_shadowElements[ShadowElementRight] = QPixmap::fromImage(rightTile);
271 m_shadowElements[ShadowElementBottomRight] = QPixmap::fromImage(bottomRightTile);
272 m_shadowElements[ShadowElementBottom] = QPixmap::fromImage(bottomTile);
273 m_shadowElements[ShadowElementBottomLeft] = QPixmap::fromImage(bottomLeftTile);
274
275 m_offset = window->property("kwin_shadow_padding").value<QMargins>();
276 Q_EMIT offsetChanged();
277
278 if (!prepareBackend()) {
279 return false;
280 }
281 Q_EMIT textureChanged();
282 return true;
283 }
284
updateShadow()285 bool Shadow::updateShadow()
286 {
287 if (!m_topLevel) {
288 return false;
289 }
290
291 if (m_decorationShadow) {
292 if (AbstractClient *c = qobject_cast<AbstractClient*>(m_topLevel)) {
293 if (c->decoration()) {
294 if (init(c->decoration())) {
295 return true;
296 }
297 }
298 }
299 return false;
300 }
301
302 if (waylandServer()) {
303 if (m_topLevel && m_topLevel->surface()) {
304 if (const auto &s = m_topLevel->surface()->shadow()) {
305 if (init(s)) {
306 return true;
307 }
308 }
309 }
310 }
311
312 if (InternalClient *client = qobject_cast<InternalClient *>(m_topLevel)) {
313 if (init(client->internalWindow())) {
314 return true;
315 }
316 }
317
318 auto data = Shadow::readX11ShadowProperty(m_topLevel->window());
319 if (data.isEmpty()) {
320 return false;
321 }
322
323 init(data);
324
325 return true;
326 }
327
toplevel() const328 Toplevel *Shadow::toplevel() const
329 {
330 return m_topLevel;
331 }
332
setToplevel(Toplevel * topLevel)333 void Shadow::setToplevel(Toplevel *topLevel)
334 {
335 m_topLevel = topLevel;
336 connect(m_topLevel, &Toplevel::frameGeometryChanged, this, &Shadow::geometryChanged);
337 }
geometryChanged()338 void Shadow::geometryChanged()
339 {
340 if (m_cachedSize == m_topLevel->size()) {
341 return;
342 }
343 m_cachedSize = m_topLevel->size();
344 Q_EMIT rectChanged();
345 }
346
decorationShadowImage() const347 QImage Shadow::decorationShadowImage() const
348 {
349 if (!m_decorationShadow) {
350 return QImage();
351 }
352 return m_decorationShadow->shadow();
353 }
354
elementSize(Shadow::ShadowElements element) const355 QSize Shadow::elementSize(Shadow::ShadowElements element) const
356 {
357 if (m_decorationShadow) {
358 switch (element) {
359 case ShadowElementTop:
360 return m_decorationShadow->topGeometry().size();
361 case ShadowElementTopRight:
362 return m_decorationShadow->topRightGeometry().size();
363 case ShadowElementRight:
364 return m_decorationShadow->rightGeometry().size();
365 case ShadowElementBottomRight:
366 return m_decorationShadow->bottomRightGeometry().size();
367 case ShadowElementBottom:
368 return m_decorationShadow->bottomGeometry().size();
369 case ShadowElementBottomLeft:
370 return m_decorationShadow->bottomLeftGeometry().size();
371 case ShadowElementLeft:
372 return m_decorationShadow->leftGeometry().size();
373 case ShadowElementTopLeft:
374 return m_decorationShadow->topLeftGeometry().size();
375 default:
376 return QSize();
377 }
378 } else {
379 return m_shadowElements[element].size();
380 }
381 }
382
setShadowElement(const QPixmap & shadow,Shadow::ShadowElements element)383 void Shadow::setShadowElement(const QPixmap &shadow, Shadow::ShadowElements element)
384 {
385 m_shadowElements[element] = shadow;
386 }
387
388 } // namespace
389