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 #include "waylandintegration.h"
10 
11 #include <QDebug>
12 #include <QExposeEvent>
13 #include <QGuiApplication>
14 #include <QWidget>
15 
16 #include <KWayland/Client/blur.h>
17 #include <KWayland/Client/compositor.h>
18 #include <KWayland/Client/connection_thread.h>
19 #include <KWayland/Client/contrast.h>
20 #include <KWayland/Client/plasmashell.h>
21 #include <KWayland/Client/plasmawindowmanagement.h>
22 #include <KWayland/Client/region.h>
23 #include <KWayland/Client/registry.h>
24 #include <KWayland/Client/slide.h>
25 #include <KWayland/Client/surface.h>
26 
WindowEffects()27 WindowEffects::WindowEffects()
28     : QObject()
29     ,  KWindowEffectsPrivateV2()
30 {
31 }
32 
~WindowEffects()33 WindowEffects::~WindowEffects()
34 {
35 }
36 
windowForId(WId wid)37 QWindow *WindowEffects::windowForId(WId wid)
38 {
39     QWindow *window = nullptr;
40 
41     for (auto win : qApp->allWindows()) {
42         if (win->winId() == wid) {
43             window = win;
44             break;
45         }
46     }
47     return window;
48 }
49 
trackWindow(QWindow * window)50 void WindowEffects::trackWindow(QWindow *window)
51 {
52     if (!m_windowWatchers.contains(window)) {
53         window->installEventFilter(this);
54         auto conn = connect(window, &QObject::destroyed, this, [this, window]() {
55             m_blurRegions.remove(window);
56             m_backgroundConstrastRegions.remove(window);
57             m_windowWatchers.remove(window);
58         });
59         m_windowWatchers[window] = conn;
60     }
61 }
62 
releaseWindow(QWindow * window)63 void WindowEffects::releaseWindow(QWindow *window)
64 {
65     if (!m_blurRegions.contains(window) && !m_backgroundConstrastRegions.contains(window)) {
66         disconnect(m_windowWatchers[window]);
67         window->removeEventFilter(this);
68         m_windowWatchers.remove(window);
69     }
70 }
71 
eventFilter(QObject * watched,QEvent * event)72 bool WindowEffects::eventFilter(QObject *watched, QEvent *event)
73 {
74     if (event->type() == QEvent::Expose) {
75         auto ee = static_cast<QExposeEvent *>(event);
76 
77         if ((ee->region().isNull())) {
78             return false;
79         }
80 
81         auto window = qobject_cast<QWindow *>(watched);
82         if (!window) {
83             return false;
84         }
85 
86         {
87             auto it = m_blurRegions.constFind(window);
88             if (it != m_blurRegions.constEnd()) {
89                 enableBlurBehind(window, true, *it);
90             }
91         }
92         {
93             auto it = m_backgroundConstrastRegions.constFind(window);
94             if (it != m_backgroundConstrastRegions.constEnd()) {
95                 enableBackgroundContrast(window, true, it->contrast, it->intensity, it->saturation, it->region);
96             }
97         }
98     }
99     return false;
100 }
101 
isEffectAvailable(KWindowEffects::Effect effect)102 bool WindowEffects::isEffectAvailable(KWindowEffects::Effect effect)
103 {
104     switch (effect) {
105     case KWindowEffects::BackgroundContrast:
106         return WaylandIntegration::self()->waylandContrastManager() != nullptr;
107     case KWindowEffects::BlurBehind:
108         return WaylandIntegration::self()->waylandBlurManager() != nullptr;
109     case KWindowEffects::Slide:
110         return WaylandIntegration::self()->waylandSlideManager() != nullptr;
111     default:
112         return false;
113     }
114 }
115 
slideWindow(WId id,KWindowEffects::SlideFromLocation location,int offset)116 void WindowEffects::slideWindow(WId id, KWindowEffects::SlideFromLocation location, int offset)
117 {
118     if (!WaylandIntegration::self()->waylandSlideManager()) {
119         return;
120     }
121     KWayland::Client::Surface *surface = KWayland::Client::Surface::fromQtWinId(id);
122     if (surface) {
123         if (location != KWindowEffects::SlideFromLocation::NoEdge) {
124             auto slide = WaylandIntegration::self()->waylandSlideManager()->createSlide(surface, surface);
125 
126             KWayland::Client::Slide::Location convertedLoc;
127             switch (location) {
128             case KWindowEffects::SlideFromLocation::TopEdge:
129                 convertedLoc = KWayland::Client::Slide::Location::Top;
130                 break;
131             case KWindowEffects::SlideFromLocation::LeftEdge:
132                 convertedLoc = KWayland::Client::Slide::Location::Left;
133                 break;
134             case KWindowEffects::SlideFromLocation::RightEdge:
135                 convertedLoc = KWayland::Client::Slide::Location::Right;
136                 break;
137             case KWindowEffects::SlideFromLocation::BottomEdge:
138             default:
139                 convertedLoc = KWayland::Client::Slide::Location::Bottom;
140                 break;
141             }
142 
143             slide->setLocation(convertedLoc);
144             slide->setOffset(offset);
145             slide->commit();
146         } else {
147             WaylandIntegration::self()->waylandSlideManager()->removeSlide(surface);
148         }
149 
150         WaylandIntegration::self()->waylandConnection()->flush();
151     }
152 }
153 
154 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 81)
windowSizes(const QList<WId> & ids)155 QList<QSize> WindowEffects::windowSizes(const QList<WId> &ids)
156 {
157     Q_UNUSED(ids)
158     QList<QSize> sizes;
159     return sizes;
160 }
161 #endif
162 
163 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82)
presentWindows(WId controller,const QList<WId> & ids)164 void WindowEffects::presentWindows(WId controller, const QList<WId> &ids)
165 {
166     Q_UNUSED(controller)
167     Q_UNUSED(ids)
168 }
169 #endif
170 
171 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82)
presentWindows(WId controller,int desktop)172 void WindowEffects::presentWindows(WId controller, int desktop)
173 {
174     Q_UNUSED(controller)
175     Q_UNUSED(desktop)
176 }
177 #endif
178 
179 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82)
highlightWindows(WId controller,const QList<WId> & ids)180 void WindowEffects::highlightWindows(WId controller, const QList<WId> &ids)
181 {
182     Q_UNUSED(controller)
183     Q_UNUSED(ids)
184 }
185 #endif
186 
enableBlurBehind(WId winId,bool enable,const QRegion & region)187 void WindowEffects::enableBlurBehind(WId winId, bool enable, const QRegion &region)
188 {
189     auto window = windowForId(winId);
190     if (!window) {
191         return;
192     }
193     if (enable) {
194         trackWindow(window);
195         m_blurRegions[window] = region;
196     } else {
197         m_blurRegions.remove(window);
198         releaseWindow(window);
199     }
200 
201     enableBlurBehind(window, enable, region);
202 }
203 
enableBlurBehind(QWindow * window,bool enable,const QRegion & region)204 void WindowEffects::enableBlurBehind(QWindow *window, bool enable, const QRegion &region)
205 {
206     if (!WaylandIntegration::self()->waylandBlurManager()) {
207         return;
208     }
209     KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(window);
210 
211     if (surface) {
212         if (enable) {
213             auto blur = WaylandIntegration::self()->waylandBlurManager()->createBlur(surface, surface);
214             blur->setRegion(WaylandIntegration::self()->waylandCompositor()->createRegion(region, nullptr));
215             blur->commit();
216         } else {
217             WaylandIntegration::self()->waylandBlurManager()->removeBlur(surface);
218         }
219 
220         WaylandIntegration::self()->waylandConnection()->flush();
221     }
222 }
223 
enableBackgroundContrast(WId winId,bool enable,qreal contrast,qreal intensity,qreal saturation,const QRegion & region)224 void WindowEffects::enableBackgroundContrast(WId winId, bool enable, qreal contrast, qreal intensity, qreal saturation, const QRegion &region)
225 {
226     auto window = windowForId(winId);
227     if (!window) {
228         return;
229     }
230     if (enable) {
231         trackWindow(window);
232         m_backgroundConstrastRegions[window].contrast = contrast;
233         m_backgroundConstrastRegions[window].intensity = intensity;
234         m_backgroundConstrastRegions[window].saturation = saturation;
235         m_backgroundConstrastRegions[window].region = region;
236     } else {
237         m_backgroundConstrastRegions.remove(window);
238         releaseWindow(window);
239     }
240 
241     enableBackgroundContrast(window, enable, contrast, intensity, saturation, region);
242 }
243 
enableBackgroundContrast(QWindow * window,bool enable,qreal contrast,qreal intensity,qreal saturation,const QRegion & region)244 void WindowEffects::enableBackgroundContrast(QWindow *window, bool enable, qreal contrast, qreal intensity, qreal saturation, const QRegion &region)
245 {
246     if (!WaylandIntegration::self()->waylandContrastManager()) {
247         return;
248     }
249     KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(window);
250     if (surface) {
251         if (enable) {
252             auto backgroundContrast = WaylandIntegration::self()->waylandContrastManager()->createContrast(surface, surface);
253             backgroundContrast->setRegion(WaylandIntegration::self()->waylandCompositor()->createRegion(region, nullptr));
254             backgroundContrast->setContrast(contrast);
255             backgroundContrast->setIntensity(intensity);
256             backgroundContrast->setSaturation(saturation);
257             backgroundContrast->commit();
258         } else {
259             WaylandIntegration::self()->waylandContrastManager()->removeContrast(surface);
260         }
261 
262         WaylandIntegration::self()->waylandConnection()->flush();
263     }
264 }
265 
setBackgroundFrost(QWindow * window,QColor color,const QRegion & region)266 void WindowEffects::setBackgroundFrost(QWindow *window, QColor color, const QRegion &region)
267 {
268     if (!WaylandIntegration::self()->waylandContrastManager()) {
269         return;
270     }
271 
272     KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(window);
273     if (!surface) {
274         return;
275     }
276     if (!color.isValid()) {
277         WaylandIntegration::self()->waylandContrastManager()->removeContrast(surface);
278         return;
279     }
280 
281     auto backgroundContrast = WaylandIntegration::self()->waylandContrastManager()->createContrast(surface, surface);
282     backgroundContrast->setRegion(WaylandIntegration::self()->waylandCompositor()->createRegion(region, nullptr));
283     backgroundContrast->setFrost(color);
284     backgroundContrast->commit();
285 
286     WaylandIntegration::self()->waylandConnection()->flush();
287 }
288 
289 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 67)
markAsDashboard(WId window)290 void WindowEffects::markAsDashboard(WId window)
291 {
292     Q_UNUSED(window)
293 }
294 #endif
295