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 <QWaylandClientExtension>
17#include <QWaylandClientExtensionTemplate>
18
19#include "qwayland-blur.h"
20#include "qwayland-contrast.h"
21#include "qwayland-slide.h"
22
23#include "helpers.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 if (isQpaAlive()) {
65 release();
66 }
67 }
68};
69
70class ContrastManager : public QWaylandClientExtensionTemplate<ContrastManager>, public QtWayland::org_kde_kwin_contrast_manager
71{
72public:
73 ContrastManager()
74 : QWaylandClientExtensionTemplate<ContrastManager>(2)
75 {
76 }
77};
78
79class Contrast : public QObject, public QtWayland::org_kde_kwin_contrast
80{
81public:
82 Contrast(struct ::org_kde_kwin_contrast *object, QObject *parent)
83 : QObject(parent)
84 , QtWayland::org_kde_kwin_contrast(object)
85 {
86 }
87
88 ~Contrast() override
89 {
90 if (isQpaAlive()) {
91 release();
92 }
93 }
94};
95
96class SlideManager : public QWaylandClientExtensionTemplate<SlideManager>, public QtWayland::org_kde_kwin_slide_manager
97{
98public:
99 SlideManager()
100 : QWaylandClientExtensionTemplate<SlideManager>(1)
101 {
102 }
103};
104
105class Slide : public QObject, public QtWayland::org_kde_kwin_slide
106{
107public:
108 Slide(struct ::org_kde_kwin_slide *object, QObject *parent)
109 : QObject(parent)
110 , QtWayland::org_kde_kwin_slide(object)
111 {
112 }
113
114 ~Slide() override
115 {
116 if (isQpaAlive()) {
117 release();
118 }
119 }
120};
121
122WindowEffects::WindowEffects()
123 : QObject()
124 , KWindowEffectsPrivate()
125{
126 m_blurManager = new BlurManager();
127 m_contrastManager = new ContrastManager();
128 m_slideManager = new SlideManager();
129
130 // The KWindowEffects API doesn't provide any signals to notify that the particular
131 // effect has become unavailable. So we re-install effects when the corresponding globals
132 // are added.
133
134 connect(sender: m_blurManager, signal: &BlurManager::activeChanged, context: this, slot: [this] {
135 for (auto it = m_blurRegions.constBegin(); it != m_blurRegions.constEnd(); ++it) {
136 installBlur(window: it.key(), enable: m_blurManager->isActive(), region: *it);
137 }
138 });
139
140 connect(sender: m_contrastManager, signal: &ContrastManager::activeChanged, context: this, slot: [this] {
141 for (auto it = m_backgroundConstrastRegions.constBegin(); it != m_backgroundConstrastRegions.constEnd(); ++it) {
142 if (m_contrastManager->isActive()) {
143 installContrast(window: it.key(), enable: true, contrast: it->contrast, intensity: it->intensity, saturation: it->saturation, region: it->region);
144 } else {
145 installContrast(window: it.key(), enable: false);
146 }
147 }
148 });
149
150 connect(sender: m_slideManager, signal: &SlideManager::activeChanged, context: this, slot: [this] {
151 for (auto it = m_slideMap.constBegin(); it != m_slideMap.constEnd(); ++it) {
152 if (m_slideManager->isActive()) {
153 installSlide(window: it.key(), location: it->location, offset: it->offset);
154 } else {
155 installSlide(window: it.key(), location: KWindowEffects::SlideFromLocation::NoEdge, offset: 0);
156 }
157 }
158 });
159}
160
161WindowEffects::~WindowEffects()
162{
163 delete m_blurManager;
164 delete m_contrastManager;
165 delete m_slideManager;
166}
167
168void WindowEffects::trackWindow(QWindow *window)
169{
170 if (!m_windowWatchers.contains(key: window)) {
171 window->installEventFilter(filterObj: this);
172 auto conn = connect(sender: window, signal: &QObject::destroyed, context: this, slot: [this, window]() {
173 resetBlur(window);
174 m_blurRegions.remove(key: window);
175 resetContrast(window);
176 m_backgroundConstrastRegions.remove(key: window);
177 m_slideMap.remove(key: window);
178 m_windowWatchers.remove(key: window);
179 });
180 m_windowWatchers[window] << conn;
181 auto waylandWindow = window->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
182 if (waylandWindow) {
183 auto conn = connect(sender: waylandWindow, signal: &QNativeInterface::Private::QWaylandWindow::surfaceDestroyed, context: this, slot: [this, window]() {
184 resetBlur(window);
185 resetContrast(window);
186 });
187 m_windowWatchers[window] << conn;
188 }
189 }
190}
191
192void WindowEffects::releaseWindow(QWindow *window)
193{
194 if (!m_blurRegions.contains(key: window) && !m_backgroundConstrastRegions.contains(key: window) && !m_slideMap.contains(key: window)) {
195 for (const auto &conn : m_windowWatchers[window]) {
196 disconnect(conn);
197 }
198 window->removeEventFilter(obj: this);
199 m_windowWatchers.remove(key: window);
200 }
201}
202
203// Helper function to replace a QObject value in the map and delete the old one.
204template<typename MapType>
205void replaceValue(MapType &map, typename MapType::key_type key, typename MapType::mapped_type value)
206{
207 if (auto oldValue = map.take(key)) {
208 oldValue->deleteLater();
209 }
210 if (value) {
211 map[key] = value;
212 }
213}
214
215void WindowEffects::resetBlur(QWindow *window, Blur *blur)
216{
217 replaceValue(map&: m_blurs, key: window, value: blur);
218}
219
220void WindowEffects::resetContrast(QWindow *window, Contrast *contrast)
221{
222 replaceValue(map&: m_contrasts, key: window, value: contrast);
223}
224
225bool WindowEffects::eventFilter(QObject *watched, QEvent *event)
226{
227 if (event->type() == QEvent::Expose) {
228 auto window = qobject_cast<QWindow *>(o: watched);
229 if (!window || !window->isExposed()) {
230 return false;
231 }
232
233 {
234 auto it = m_blurRegions.constFind(key: window);
235 if (it != m_blurRegions.constEnd()) {
236 installBlur(window, enable: true, region: *it);
237 }
238 }
239 {
240 auto it = m_backgroundConstrastRegions.constFind(key: window);
241 if (it != m_backgroundConstrastRegions.constEnd()) {
242 installContrast(window, enable: true, contrast: it->contrast, intensity: it->intensity, saturation: it->saturation, region: it->region);
243 }
244 }
245 {
246 auto it = m_slideMap.constFind(key: window);
247 if (it != m_slideMap.constEnd()) {
248 installSlide(window, location: it->location, offset: it->offset);
249 }
250 }
251 }
252 return false;
253}
254
255bool WindowEffects::isEffectAvailable(KWindowEffects::Effect effect)
256{
257 switch (effect) {
258 case KWindowEffects::BackgroundContrast:
259 return m_contrastManager->isActive();
260 case KWindowEffects::BlurBehind:
261 return m_blurManager->isActive();
262 case KWindowEffects::Slide:
263 return m_slideManager->isActive();
264 default:
265 return false;
266 }
267}
268
269void WindowEffects::slideWindow(QWindow *window, KWindowEffects::SlideFromLocation location, int offset)
270{
271 if (location != KWindowEffects::SlideFromLocation::NoEdge) {
272 trackWindow(window);
273
274 const SlideData data{
275 .location = location,
276 .offset = offset,
277 };
278
279 if (auto it = m_slideMap.find(key: window); it != m_slideMap.end()) {
280 if (*it == data) {
281 return;
282 } else {
283 *it = data;
284 }
285 } else {
286 m_slideMap.insert(key: window, value: data);
287 }
288 } else {
289 if (!m_slideMap.remove(key: window)) {
290 return;
291 }
292 releaseWindow(window);
293 }
294
295 installSlide(window, location, offset);
296}
297
298void WindowEffects::installSlide(QWindow *window, KWindowEffects::SlideFromLocation location, int offset)
299{
300 if (!m_slideManager->isActive()) {
301 return;
302 }
303 wl_surface *surface = surfaceForWindow(window);
304 if (surface) {
305 if (location != KWindowEffects::SlideFromLocation::NoEdge) {
306 auto slide = new Slide(m_slideManager->create(surface), window);
307
308 Slide::location convertedLoc;
309 switch (location) {
310 case KWindowEffects::SlideFromLocation::TopEdge:
311 convertedLoc = Slide::location::location_top;
312 break;
313 case KWindowEffects::SlideFromLocation::LeftEdge:
314 convertedLoc = Slide::location::location_left;
315 break;
316 case KWindowEffects::SlideFromLocation::RightEdge:
317 convertedLoc = Slide::location::location_right;
318 break;
319 case KWindowEffects::SlideFromLocation::BottomEdge:
320 default:
321 convertedLoc = Slide::location::location_bottom;
322 break;
323 }
324
325 slide->set_location(convertedLoc);
326 slide->set_offset(offset);
327 slide->commit();
328 } else {
329 m_slideManager->unset(surface);
330 }
331 }
332}
333
334void WindowEffects::enableBlurBehind(QWindow *window, bool enable, const QRegion &region)
335{
336 if (enable) {
337 trackWindow(window);
338
339 if (auto it = m_blurRegions.find(key: window); it != m_blurRegions.end()) {
340 if (*it == region) {
341 return;
342 } else {
343 *it = region;
344 }
345 } else {
346 m_blurRegions.insert(key: window, value: region);
347 }
348 } else {
349 if (!m_blurRegions.remove(key: window)) {
350 return;
351 }
352 resetBlur(window);
353 releaseWindow(window);
354 }
355
356 installBlur(window, enable, region);
357}
358
359void WindowEffects::installBlur(QWindow *window, bool enable, const QRegion &region)
360{
361 if (!m_blurManager->isActive()) {
362 return;
363 }
364
365 wl_surface *surface = surfaceForWindow(window);
366
367 if (surface) {
368 if (enable) {
369 auto wl_region = createRegion(region);
370 if (!wl_region) {
371 return;
372 }
373 auto blur = new Blur(m_blurManager->create(surface), window);
374 blur->set_region(wl_region);
375 blur->commit();
376 wl_region_destroy(wl_region);
377 resetBlur(window, blur);
378 } else {
379 resetBlur(window);
380 m_blurManager->unset(surface);
381 }
382 }
383}
384
385void WindowEffects::enableBackgroundContrast(QWindow *window, bool enable, qreal contrast, qreal intensity, qreal saturation, const QRegion &region)
386{
387 if (enable) {
388 trackWindow(window);
389
390 const BackgroundContrastData data{
391 .contrast = contrast,
392 .intensity = intensity,
393 .saturation = saturation,
394 .region = region,
395 };
396
397 if (auto it = m_backgroundConstrastRegions.find(key: window); it != m_backgroundConstrastRegions.end()) {
398 if (*it == data) {
399 return;
400 } else {
401 *it = data;
402 }
403 } else {
404 m_backgroundConstrastRegions.insert(key: window, value: data);
405 }
406 } else {
407 if (!m_backgroundConstrastRegions.remove(key: window)) {
408 return;
409 }
410 resetContrast(window);
411 releaseWindow(window);
412 }
413
414 installContrast(window, enable, contrast, intensity, saturation, region);
415}
416
417void WindowEffects::installContrast(QWindow *window, bool enable, qreal contrast, qreal intensity, qreal saturation, const QRegion &region)
418{
419 if (!m_contrastManager->isActive()) {
420 return;
421 }
422
423 wl_surface *surface = surfaceForWindow(window);
424 if (surface) {
425 if (enable) {
426 auto wl_region = createRegion(region);
427 if (!wl_region) {
428 return;
429 }
430 auto backgroundContrast = new Contrast(m_contrastManager->create(surface), window);
431 backgroundContrast->set_region(wl_region);
432 backgroundContrast->set_contrast(wl_fixed_from_double(d: contrast));
433 backgroundContrast->set_intensity(wl_fixed_from_double(d: intensity));
434 backgroundContrast->set_saturation(wl_fixed_from_double(d: saturation));
435 backgroundContrast->commit();
436 wl_region_destroy(wl_region);
437 resetContrast(window, contrast: backgroundContrast);
438 } else {
439 resetContrast(window);
440 m_contrastManager->unset(surface);
441 }
442 }
443}
444
445#include "moc_windoweffects.cpp"
446

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