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 "logging.h"
10#include "shm.h"
11#include "surfacehelper.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 (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 ~Shadow()
57 {
58 destroy();
59 }
60};
61
62WindowShadowTile::WindowShadowTile()
63{
64 connect(sender: Shm::instance(), signal: &Shm::activeChanged, context: this, slot: [this] {
65 if (Shm::instance()->isActive()) {
66 buffer.reset();
67 }
68 });
69}
70WindowShadowTile::~WindowShadowTile()
71{
72}
73
74bool WindowShadowTile::create()
75{
76 if (!Shm::instance()->isActive()) {
77 return false;
78 }
79
80 buffer = Shm::instance()->createBuffer(image);
81 return true;
82}
83
84void WindowShadowTile::destroy()
85{
86 buffer.reset();
87}
88
89WindowShadowTile *WindowShadowTile::get(const KWindowShadowTile *tile)
90{
91 KWindowShadowTilePrivate *d = KWindowShadowTilePrivate::get(tile);
92 return static_cast<WindowShadowTile *>(d);
93}
94
95static wl_buffer *bufferForTile(const KWindowShadowTile::Ptr &tile)
96{
97 if (!tile) {
98 return nullptr;
99 }
100 WindowShadowTile *d = WindowShadowTile::get(tile: tile.data());
101 // Our buffer has been deleted in the meantime, try to create it again
102 if (!d->buffer && d->isCreated) {
103 d->buffer = Shm::instance()->createBuffer(image: d->image);
104 }
105 return d->buffer ? d->buffer.get()->object() : nullptr;
106}
107
108WindowShadow::WindowShadow()
109{
110}
111WindowShadow::~WindowShadow()
112{
113}
114
115bool WindowShadow::eventFilter(QObject *watched, QEvent *event)
116{
117 Q_UNUSED(watched)
118 if (event->type() == QEvent::Expose) {
119 if (auto window = qobject_cast<QWindow *>(o: watched); window && window->isExposed()) {
120 if (!internalCreate()) {
121 qCWarning(KWAYLAND_KWS) << "Failed to recreate shadow for" << window;
122 }
123 }
124 }
125 return false;
126}
127
128bool WindowShadow::internalCreate()
129{
130 if (shadow) {
131 return true;
132 }
133 if (!ShadowManager::instance()->isActive()) {
134 return false;
135 }
136 auto surface = surfaceForWindow(window);
137 if (!surface) {
138 return false;
139 }
140
141 shadow = std::make_unique<Shadow>(args: ShadowManager::instance()->create(surface));
142 auto waylandWindow = window->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
143 if (waylandWindow) {
144 connect(sender: waylandWindow, signal: &QNativeInterface::Private::QWaylandWindow::surfaceDestroyed, context: this, slot: &WindowShadow::internalDestroy, type: Qt::UniqueConnection);
145 }
146
147 auto attach = [](const std::unique_ptr<Shadow> &shadow, auto attach_func, const KWindowShadowTile::Ptr &tile) {
148 if (auto buffer = bufferForTile(tile)) {
149 (*shadow.*attach_func)(buffer);
150 }
151 };
152 attach(shadow, &Shadow::attach_left, leftTile);
153 attach(shadow, &Shadow::attach_top_left, topLeftTile);
154 attach(shadow, &Shadow::attach_top, topTile);
155 attach(shadow, &Shadow::attach_top_right, topRightTile);
156 attach(shadow, &Shadow::attach_right, rightTile);
157 attach(shadow, &Shadow::attach_bottom_right, bottomRightTile);
158 attach(shadow, &Shadow::attach_bottom, bottomTile);
159 attach(shadow, &Shadow::attach_bottom_left, bottomLeftTile);
160
161 shadow->set_left_offset(wl_fixed_from_double(d: padding.left()));
162 shadow->set_top_offset(wl_fixed_from_double(d: padding.top()));
163 shadow->set_right_offset(wl_fixed_from_double(d: padding.right()));
164 shadow->set_bottom_offset(wl_fixed_from_double(d: padding.bottom()));
165
166 shadow->commit();
167
168 // Commit wl_surface at the next available time.
169 window->requestUpdate();
170
171 return true;
172}
173
174bool WindowShadow::create()
175{
176 if (!ShadowManager::instance()->isActive()) {
177 return false;
178 }
179
180 internalCreate();
181 window->installEventFilter(filterObj: this);
182 return true;
183}
184
185void WindowShadow::internalDestroy()
186{
187 if (!shadow) {
188 return;
189 }
190
191 // Only call surfaceForWindow and unset the surface if the native window is alive.
192 // Otherwise window->create() might be called when the window is being destroyed, leading to a crash.
193 if (window && window->nativeInterface<QNativeInterface::Private::QWaylandWindow>() && ShadowManager::instance()->isActive()) {
194 if (auto surface = surfaceForWindow(window)) {
195 ShadowManager::instance()->unset(surface);
196 }
197 }
198
199 shadow.reset();
200
201 if (window && window->isVisible()) {
202 window->requestUpdate();
203 }
204}
205
206void WindowShadow::destroy()
207{
208 if (window) {
209 window->removeEventFilter(obj: this);
210 }
211 internalDestroy();
212}
213
214#include "windowshadow.moc"
215

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