1/*
2 SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
3 SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6*/
7
8#include "windoweffects.h"
9
10#include <QDebug>
11#include <QExposeEvent>
12#include <QGuiApplication>
13
14#include <qpa/qplatformwindow_p.h>
15
16#include <QWaylandClientExtensionTemplate>
17#include <qwaylandclientextension.h>
18
19#include "qwayland-blur.h"
20#include "qwayland-contrast.h"
21#include "qwayland-slide.h"
22
23#include "surfacehelper.h"
24
25#include <wayland-client-protocol.h>
26
27static wl_region *createRegion(const QRegion &region)
28{
29 QPlatformNativeInterface *native = qGuiApp->platformNativeInterface();
30 if (!native) {
31 return nullptr;
32 }
33 auto compositor = reinterpret_cast<wl_compositor *>(native->nativeResourceForIntegration(QByteArrayLiteral("compositor")));
34 if (!compositor) {
35 return nullptr;
36 }
37 auto wl_region = wl_compositor_create_region(wl_compositor: compositor);
38 for (const auto &rect : region) {
39 wl_region_add(wl_region, x: rect.x(), y: rect.y(), width: rect.width(), height: rect.height());
40 }
41 return wl_region;
42}
43
44class BlurManager : public QWaylandClientExtensionTemplate<BlurManager>, public QtWayland::org_kde_kwin_blur_manager
45{
46public:
47 BlurManager()
48 : QWaylandClientExtensionTemplate<BlurManager>(1)
49 {
50 }
51};
52
53class Blur : public QObject, public QtWayland::org_kde_kwin_blur
54{
55public:
56 Blur(struct ::org_kde_kwin_blur *object, QObject *parent)
57 : QObject(parent)
58 , QtWayland::org_kde_kwin_blur(object)
59 {
60 }
61
62 ~Blur() override
63 {
64 release();
65 }
66};
67
68class ContrastManager : public QWaylandClientExtensionTemplate<ContrastManager>, public QtWayland::org_kde_kwin_contrast_manager
69{
70public:
71 ContrastManager()
72 : QWaylandClientExtensionTemplate<ContrastManager>(2)
73 {
74 }
75};
76
77class Contrast : public QObject, public QtWayland::org_kde_kwin_contrast
78{
79public:
80 Contrast(struct ::org_kde_kwin_contrast *object, QObject *parent)
81 : QObject(parent)
82 , QtWayland::org_kde_kwin_contrast(object)
83 {
84 }
85
86 ~Contrast() override
87 {
88 release();
89 }
90};
91
92class SlideManager : public QWaylandClientExtensionTemplate<SlideManager>, public QtWayland::org_kde_kwin_slide_manager
93{
94public:
95 SlideManager()
96 : QWaylandClientExtensionTemplate<SlideManager>(1)
97 {
98 }
99};
100
101class Slide : public QObject, public QtWayland::org_kde_kwin_slide
102{
103public:
104 Slide(struct ::org_kde_kwin_slide *object, QObject *parent)
105 : QObject(parent)
106 , QtWayland::org_kde_kwin_slide(object)
107 {
108 }
109
110 ~Slide() override
111 {
112 release();
113 }
114};
115
116WindowEffects::WindowEffects()
117 : QObject()
118 , KWindowEffectsPrivate()
119{
120 m_blurManager = new BlurManager();
121 m_contrastManager = new ContrastManager();
122 m_slideManager = new SlideManager();
123
124 // The KWindowEffects API doesn't provide any signals to notify that the particular
125 // effect has become unavailable. So we re-install effects when the corresponding globals
126 // are added.
127
128 connect(sender: m_blurManager, signal: &BlurManager::activeChanged, context: this, slot: [this] {
129 for (auto it = m_blurRegions.constBegin(); it != m_blurRegions.constEnd(); ++it) {
130 installBlur(window: it.key(), enable: m_blurManager->isActive(), region: *it);
131 }
132 });
133
134 connect(sender: m_contrastManager, signal: &ContrastManager::activeChanged, context: this, slot: [this] {
135 for (auto it = m_backgroundConstrastRegions.constBegin(); it != m_backgroundConstrastRegions.constEnd(); ++it) {
136 if (m_contrastManager->isActive()) {
137 installContrast(window: it.key(), enable: true, contrast: it->contrast, intensity: it->intensity, saturation: it->saturation, region: it->region);
138 } else {
139 installContrast(window: it.key(), enable: false);
140 }
141 }
142 });
143
144 connect(sender: m_slideManager, signal: &SlideManager::activeChanged, context: this, slot: [this] {
145 for (auto it = m_slideMap.constBegin(); it != m_slideMap.constEnd(); ++it) {
146 if (m_slideManager->isActive()) {
147 installSlide(window: it.key(), location: it->location, offset: it->offset);
148 } else {
149 installSlide(window: it.key(), location: KWindowEffects::SlideFromLocation::NoEdge, offset: 0);
150 }
151 }
152 });
153}
154
155WindowEffects::~WindowEffects()
156{
157 delete m_blurManager;
158 delete m_contrastManager;
159 delete m_slideManager;
160}
161
162void WindowEffects::trackWindow(QWindow *window)
163{
164 if (!m_windowWatchers.contains(key: window)) {
165 window->installEventFilter(filterObj: this);
166 auto conn = connect(sender: window, signal: &QObject::destroyed, context: this, slot: [this, window]() {
167 resetBlur(window);
168 m_blurRegions.remove(key: window);
169 resetContrast(window);
170 m_backgroundConstrastRegions.remove(key: window);
171 m_slideMap.remove(key: window);
172 m_windowWatchers.remove(key: window);
173 });
174 m_windowWatchers[window] << conn;
175 auto waylandWindow = window->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
176 if (waylandWindow) {
177 auto conn = connect(sender: waylandWindow, signal: &QNativeInterface::Private::QWaylandWindow::surfaceDestroyed, context: this, slot: [this, window]() {
178 resetBlur(window);
179 resetContrast(window);
180 });
181 m_windowWatchers[window] << conn;
182 }
183 }
184}
185
186void WindowEffects::releaseWindow(QWindow *window)
187{
188 if (!m_blurRegions.contains(key: window) && !m_backgroundConstrastRegions.contains(key: window) && !m_slideMap.contains(key: window)) {
189 for (const auto &conn : m_windowWatchers[window]) {
190 disconnect(conn);
191 }
192 window->removeEventFilter(obj: this);
193 m_windowWatchers.remove(key: window);
194 }
195}
196
197// Helper function to replace a QObject value in the map and delete the old one.
198template<typename MapType>
199void replaceValue(MapType &map, typename MapType::key_type key, typename MapType::mapped_type value)
200{
201 if (auto oldValue = map.take(key)) {
202 oldValue->deleteLater();
203 }
204 if (value) {
205 map[key] = value;
206 }
207}
208
209void WindowEffects::resetBlur(QWindow *window, Blur *blur)
210{
211 replaceValue(map&: m_blurs, key: window, value: blur);
212}
213
214void WindowEffects::resetContrast(QWindow *window, Contrast *contrast)
215{
216 replaceValue(map&: m_contrasts, key: window, value: contrast);
217}
218
219bool WindowEffects::eventFilter(QObject *watched, QEvent *event)
220{
221 if (event->type() == QEvent::Expose) {
222 auto window = qobject_cast<QWindow *>(o: watched);
223 if (!window || !window->isExposed()) {
224 return false;
225 }
226
227 {
228 auto it = m_blurRegions.constFind(key: window);
229 if (it != m_blurRegions.constEnd()) {
230 installBlur(window, enable: true, region: *it);
231 }
232 }
233 {
234 auto it = m_backgroundConstrastRegions.constFind(key: window);
235 if (it != m_backgroundConstrastRegions.constEnd()) {
236 installContrast(window, enable: true, contrast: it->contrast, intensity: it->intensity, saturation: it->saturation, region: it->region);
237 }
238 }
239 {
240 auto it = m_slideMap.constFind(key: window);
241 if (it != m_slideMap.constEnd()) {
242 installSlide(window, location: it->location, offset: it->offset);
243 }
244 }
245 }
246 return false;
247}
248
249bool WindowEffects::isEffectAvailable(KWindowEffects::Effect effect)
250{
251 switch (effect) {
252 case KWindowEffects::BackgroundContrast:
253 return m_contrastManager->isActive();
254 case KWindowEffects::BlurBehind:
255 return m_blurManager->isActive();
256 case KWindowEffects::Slide:
257 return m_slideManager->isActive();
258 default:
259 return false;
260 }
261}
262
263void WindowEffects::slideWindow(QWindow *window, KWindowEffects::SlideFromLocation location, int offset)
264{
265 if (location != KWindowEffects::SlideFromLocation::NoEdge) {
266 m_slideMap[window] = SlideData{
267 .location = location,
268 .offset = offset,
269 };
270 trackWindow(window);
271 } else {
272 m_slideMap.remove(key: window);
273 releaseWindow(window);
274 }
275
276 installSlide(window, location, offset);
277}
278
279void WindowEffects::installSlide(QWindow *window, KWindowEffects::SlideFromLocation location, int offset)
280{
281 if (!m_slideManager->isActive()) {
282 return;
283 }
284 wl_surface *surface = surfaceForWindow(window);
285 if (surface) {
286 if (location != KWindowEffects::SlideFromLocation::NoEdge) {
287 auto slide = new Slide(m_slideManager->create(surface), window);
288
289 Slide::location convertedLoc;
290 switch (location) {
291 case KWindowEffects::SlideFromLocation::TopEdge:
292 convertedLoc = Slide::location::location_top;
293 break;
294 case KWindowEffects::SlideFromLocation::LeftEdge:
295 convertedLoc = Slide::location::location_left;
296 break;
297 case KWindowEffects::SlideFromLocation::RightEdge:
298 convertedLoc = Slide::location::location_right;
299 break;
300 case KWindowEffects::SlideFromLocation::BottomEdge:
301 default:
302 convertedLoc = Slide::location::location_bottom;
303 break;
304 }
305
306 slide->set_location(convertedLoc);
307 slide->set_offset(offset);
308 slide->commit();
309 } else {
310 m_slideManager->unset(surface);
311 }
312 }
313}
314
315void WindowEffects::enableBlurBehind(QWindow *window, bool enable, const QRegion &region)
316{
317 if (enable) {
318 trackWindow(window);
319 m_blurRegions[window] = region;
320 } else {
321 resetBlur(window);
322 m_blurRegions.remove(key: window);
323 releaseWindow(window);
324 }
325
326 installBlur(window, enable, region);
327}
328
329void WindowEffects::installBlur(QWindow *window, bool enable, const QRegion &region)
330{
331 if (!m_blurManager->isActive()) {
332 return;
333 }
334
335 wl_surface *surface = surfaceForWindow(window);
336
337 if (surface) {
338 if (enable) {
339 auto wl_region = createRegion(region);
340 if (!wl_region) {
341 return;
342 }
343 auto blur = new Blur(m_blurManager->create(surface), window);
344 blur->set_region(wl_region);
345 blur->commit();
346 wl_region_destroy(wl_region);
347 resetBlur(window, blur);
348 } else {
349 resetBlur(window);
350 m_blurManager->unset(surface);
351 }
352 }
353}
354
355void WindowEffects::enableBackgroundContrast(QWindow *window, bool enable, qreal contrast, qreal intensity, qreal saturation, const QRegion &region)
356{
357 if (enable) {
358 trackWindow(window);
359 m_backgroundConstrastRegions[window].contrast = contrast;
360 m_backgroundConstrastRegions[window].intensity = intensity;
361 m_backgroundConstrastRegions[window].saturation = saturation;
362 m_backgroundConstrastRegions[window].region = region;
363 } else {
364 resetContrast(window);
365 m_backgroundConstrastRegions.remove(key: window);
366 releaseWindow(window);
367 }
368
369 installContrast(window, enable, contrast, intensity, saturation, region);
370}
371
372void WindowEffects::installContrast(QWindow *window, bool enable, qreal contrast, qreal intensity, qreal saturation, const QRegion &region)
373{
374 if (!m_contrastManager->isActive()) {
375 return;
376 }
377
378 wl_surface *surface = surfaceForWindow(window);
379 if (surface) {
380 if (enable) {
381 auto wl_region = createRegion(region);
382 if (!wl_region) {
383 return;
384 }
385 auto backgroundContrast = new Contrast(m_contrastManager->create(surface), window);
386 backgroundContrast->set_region(wl_region);
387 backgroundContrast->set_contrast(wl_fixed_from_double(d: contrast));
388 backgroundContrast->set_intensity(wl_fixed_from_double(d: intensity));
389 backgroundContrast->set_saturation(wl_fixed_from_double(d: saturation));
390 backgroundContrast->commit();
391 wl_region_destroy(wl_region);
392 resetContrast(window, contrast: backgroundContrast);
393 } else {
394 resetContrast(window);
395 m_contrastManager->unset(surface);
396 }
397 }
398}
399
400#include "moc_windoweffects.cpp"
401

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