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 | |
27 | static wl_region *createRegion(const QRegion ®ion) |
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 | |
44 | class BlurManager : public QWaylandClientExtensionTemplate<BlurManager>, public QtWayland::org_kde_kwin_blur_manager |
45 | { |
46 | public: |
47 | BlurManager() |
48 | : QWaylandClientExtensionTemplate<BlurManager>(1) |
49 | { |
50 | } |
51 | }; |
52 | |
53 | class Blur : public QObject, public QtWayland::org_kde_kwin_blur |
54 | { |
55 | public: |
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 | |
68 | class ContrastManager : public QWaylandClientExtensionTemplate<ContrastManager>, public QtWayland::org_kde_kwin_contrast_manager |
69 | { |
70 | public: |
71 | ContrastManager() |
72 | : QWaylandClientExtensionTemplate<ContrastManager>(2) |
73 | { |
74 | } |
75 | }; |
76 | |
77 | class Contrast : public QObject, public QtWayland::org_kde_kwin_contrast |
78 | { |
79 | public: |
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 | |
92 | class SlideManager : public QWaylandClientExtensionTemplate<SlideManager>, public QtWayland::org_kde_kwin_slide_manager |
93 | { |
94 | public: |
95 | SlideManager() |
96 | : QWaylandClientExtensionTemplate<SlideManager>(1) |
97 | { |
98 | } |
99 | }; |
100 | |
101 | class Slide : public QObject, public QtWayland::org_kde_kwin_slide |
102 | { |
103 | public: |
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 | |
116 | WindowEffects::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 | |
155 | WindowEffects::~WindowEffects() |
156 | { |
157 | delete m_blurManager; |
158 | delete m_contrastManager; |
159 | delete m_slideManager; |
160 | } |
161 | |
162 | void 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 | |
186 | void 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. |
198 | template<typename MapType> |
199 | void 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 | |
209 | void WindowEffects::resetBlur(QWindow *window, Blur *blur) |
210 | { |
211 | replaceValue(map&: m_blurs, key: window, value: blur); |
212 | } |
213 | |
214 | void WindowEffects::resetContrast(QWindow *window, Contrast *contrast) |
215 | { |
216 | replaceValue(map&: m_contrasts, key: window, value: contrast); |
217 | } |
218 | |
219 | bool 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 | |
249 | bool 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 | |
263 | void 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 | |
279 | void 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 | |
315 | void WindowEffects::enableBlurBehind(QWindow *window, bool enable, const QRegion ®ion) |
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 | |
329 | void WindowEffects::installBlur(QWindow *window, bool enable, const QRegion ®ion) |
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 | |
355 | void WindowEffects::enableBackgroundContrast(QWindow *window, bool enable, qreal contrast, qreal intensity, qreal saturation, const QRegion ®ion) |
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 | |
372 | void WindowEffects::installContrast(QWindow *window, bool enable, qreal contrast, qreal intensity, qreal saturation, const QRegion ®ion) |
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 | |