1/*
2 SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
3 SPDX-FileCopyrightText: 2023 David Redondo <kde@david-redondo.de>
4
5 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6*/
7
8#include "windowshadow.h"
9#include "helpers.h"
10#include "logging.h"
11#include "shm.h"
12
13#include <qwayland-shadow.h>
14
15#include <QDebug>
16#include <QExposeEvent>
17#include <QWaylandClientExtension>
18
19#include <qpa/qplatformwindow_p.h>
20
21class ShadowManager : public QWaylandClientExtensionTemplate<ShadowManager>, public QtWayland::org_kde_kwin_shadow_manager
22{
23 Q_OBJECT
24 static constexpr int version = 2;
25 explicit ShadowManager(QObject *parent = nullptr)
26 : QWaylandClientExtensionTemplate(version)
27 {
28 setParent(parent);
29 initialize();
30
31 connect(sender: this, signal: &QWaylandClientExtension::activeChanged, context: this, slot: [this] {
32 if (!isActive()) {
33 destroy();
34 }
35 });
36 }
37
38public:
39 ~ShadowManager()
40 {
41 if (isQpaAlive() && isActive()) {
42 destroy();
43 }
44 }
45 static ShadowManager *instance()
46 {
47 static ShadowManager *instance = new ShadowManager(qGuiApp);
48 return instance;
49 }
50};
51
52class Shadow : public QtWayland::org_kde_kwin_shadow
53{
54public:
55 using QtWayland::org_kde_kwin_shadow::org_kde_kwin_shadow;
56
57 ~Shadow()
58 {
59 if (isQpaAlive()) {
60 destroy();
61 }
62 }
63};
64
65WindowShadowTile::WindowShadowTile()
66{
67 connect(sender: Shm::instance(), signal: &Shm::activeChanged, context: this, slot: [this] {
68 if (!Shm::instance()->isActive()) {
69 buffer.reset();
70 }
71 });
72}
73WindowShadowTile::~WindowShadowTile()
74{
75}
76
77bool WindowShadowTile::create()
78{
79 if (!Shm::instance()->isActive()) {
80 return false;
81 }
82
83 buffer = Shm::instance()->createBuffer(image);
84 return true;
85}
86
87void WindowShadowTile::destroy()
88{
89 buffer.reset();
90}
91
92WindowShadowTile *WindowShadowTile::get(const KWindowShadowTile *tile)
93{
94 KWindowShadowTilePrivate *d = KWindowShadowTilePrivate::get(tile);
95 return static_cast<WindowShadowTile *>(d);
96}
97
98static wl_buffer *bufferForTile(const KWindowShadowTile::Ptr &tile)
99{
100 if (!tile) {
101 return nullptr;
102 }
103 WindowShadowTile *d = WindowShadowTile::get(tile: tile.data());
104 // Our buffer has been deleted in the meantime, try to create it again
105 if (!d->buffer && d->isCreated) {
106 d->buffer = Shm::instance()->createBuffer(image: d->image);
107 }
108 return d->buffer ? d->buffer.get()->object() : nullptr;
109}
110
111WindowShadow::WindowShadow()
112{
113}
114WindowShadow::~WindowShadow()
115{
116}
117
118bool WindowShadow::eventFilter(QObject *watched, QEvent *event)
119{
120 Q_UNUSED(watched)
121 if (event->type() == QEvent::Expose) {
122 if (auto window = qobject_cast<QWindow *>(o: watched); window && window->isExposed()) {
123 if (!internalCreate()) {
124 qCWarning(KWAYLAND_KWS) << "Failed to recreate shadow for" << window;
125 }
126 }
127 }
128 return false;
129}
130
131bool WindowShadow::internalCreate()
132{
133 if (shadow) {
134 return true;
135 }
136 if (!ShadowManager::instance()->isActive()) {
137 return false;
138 }
139 auto surface = surfaceForWindow(window);
140 if (!surface) {
141 return false;
142 }
143
144 shadow = std::make_unique<Shadow>(args: ShadowManager::instance()->create(surface));
145 auto waylandWindow = window->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
146 if (waylandWindow) {
147 connect(sender: waylandWindow, signal: &QNativeInterface::Private::QWaylandWindow::surfaceDestroyed, context: this, slot: &WindowShadow::internalDestroy, type: Qt::UniqueConnection);
148 }
149
150 auto attach = [](const std::unique_ptr<Shadow> &shadow, auto attach_func, const KWindowShadowTile::Ptr &tile) {
151 if (auto buffer = bufferForTile(tile)) {
152 (*shadow.*attach_func)(buffer);
153 }
154 };
155 attach(shadow, &Shadow::attach_left, leftTile);
156 attach(shadow, &Shadow::attach_top_left, topLeftTile);
157 attach(shadow, &Shadow::attach_top, topTile);
158 attach(shadow, &Shadow::attach_top_right, topRightTile);
159 attach(shadow, &Shadow::attach_right, rightTile);
160 attach(shadow, &Shadow::attach_bottom_right, bottomRightTile);
161 attach(shadow, &Shadow::attach_bottom, bottomTile);
162 attach(shadow, &Shadow::attach_bottom_left, bottomLeftTile);
163
164 shadow->set_left_offset(wl_fixed_from_double(d: padding.left()));
165 shadow->set_top_offset(wl_fixed_from_double(d: padding.top()));
166 shadow->set_right_offset(wl_fixed_from_double(d: padding.right()));
167 shadow->set_bottom_offset(wl_fixed_from_double(d: padding.bottom()));
168
169 shadow->commit();
170
171 // Commit wl_surface at the next available time.
172 window->requestUpdate();
173
174 return true;
175}
176
177bool WindowShadow::create()
178{
179 if (!ShadowManager::instance()->isActive()) {
180 return false;
181 }
182
183 internalCreate();
184 window->installEventFilter(filterObj: this);
185 return true;
186}
187
188void WindowShadow::internalDestroy()
189{
190 if (!shadow) {
191 return;
192 }
193
194 if (!isQpaAlive()) {
195 return;
196 }
197
198 // Only call surfaceForWindow and unset the surface if the native window is alive.
199 // Otherwise window->create() might be called when the window is being destroyed, leading to a crash.
200 if (window && window->nativeInterface<QNativeInterface::Private::QWaylandWindow>() && ShadowManager::instance()->isActive()) {
201 if (auto surface = surfaceForWindow(window)) {
202 ShadowManager::instance()->unset(surface);
203 }
204 }
205
206 shadow.reset();
207
208 if (window && window->isVisible()) {
209 window->requestUpdate();
210 }
211}
212
213void WindowShadow::destroy()
214{
215 if (window) {
216 window->removeEventFilter(obj: this);
217 }
218 internalDestroy();
219}
220
221#include "windowshadow.moc"
222

source code of kwindowsystem/src/platforms/wayland/windowshadow.cpp