File indexing completed on 2024-05-12 17:00:19

0001 /*
0002     SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
0003     SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006 */
0007 
0008 #include "windoweffects.h"
0009 #include "waylandintegration.h"
0010 
0011 #include <QDebug>
0012 #include <QExposeEvent>
0013 #include <QGuiApplication>
0014 #include <QWidget>
0015 
0016 #include <KWayland/Client/blur.h>
0017 #include <KWayland/Client/compositor.h>
0018 #include <KWayland/Client/connection_thread.h>
0019 #include <KWayland/Client/contrast.h>
0020 #include <KWayland/Client/plasmashell.h>
0021 #include <KWayland/Client/plasmawindowmanagement.h>
0022 #include <KWayland/Client/region.h>
0023 #include <KWayland/Client/registry.h>
0024 #include <KWayland/Client/slide.h>
0025 #include <KWayland/Client/surface.h>
0026 #include <private/qwaylandwindow_p.h>
0027 
0028 WindowEffects::WindowEffects()
0029     : QObject()
0030     ,  KWindowEffectsPrivateV2()
0031 {
0032     auto registry = WaylandIntegration::self()->registry();
0033 
0034     // The KWindowEffects API doesn't provide any signals to notify that the particular
0035     // effect has become unavailable. So we re-install effects when the corresponding globals
0036     // are added.
0037     connect(registry, &KWayland::Client::Registry::blurAnnounced, this, [this]() {
0038         for (auto it = m_blurRegions.constBegin(); it != m_blurRegions.constEnd(); ++it) {
0039             installBlur(it.key(), true, *it);
0040         }
0041     });
0042     connect(registry, &KWayland::Client::Registry::blurRemoved, this, [this]() {
0043         for (auto it = m_blurRegions.constBegin(); it != m_blurRegions.constEnd(); ++it) {
0044             installBlur(it.key(), false, *it);
0045         }
0046     });
0047 
0048     connect(registry, &KWayland::Client::Registry::contrastAnnounced, this, [this]() {
0049         for (auto it = m_backgroundConstrastRegions.constBegin(); it != m_backgroundConstrastRegions.constEnd(); ++it) {
0050             installContrast(it.key(), true, it->contrast, it->intensity, it->saturation, it->region);
0051         }
0052     });
0053     connect(registry, &KWayland::Client::Registry::contrastRemoved, this, [this]() {
0054         for (auto it = m_backgroundConstrastRegions.constBegin(); it != m_backgroundConstrastRegions.constEnd(); ++it) {
0055             installContrast(it.key(), false);
0056         }
0057     });
0058 
0059     connect(registry, &KWayland::Client::Registry::slideAnnounced, this, [this]() {
0060         for (auto it = m_slideMap.constBegin(); it != m_slideMap.constEnd(); ++it) {
0061             installSlide(it.key(), it->location, it->offset);
0062         }
0063     });
0064     connect(registry, &KWayland::Client::Registry::slideRemoved, this, [this]() {
0065         for (auto it = m_slideMap.constBegin(); it != m_slideMap.constEnd(); ++it) {
0066             installSlide(it.key(), KWindowEffects::SlideFromLocation::NoEdge, 0);
0067         }
0068     });
0069 }
0070 
0071 WindowEffects::~WindowEffects()
0072 {
0073 }
0074 
0075 QWindow *WindowEffects::windowForId(WId wid)
0076 {
0077     QWindow *window = nullptr;
0078 
0079     for (auto win : qApp->allWindows()) {
0080         if (win->winId() == wid) {
0081             window = win;
0082             break;
0083         }
0084     }
0085     return window;
0086 }
0087 
0088 void WindowEffects::trackWindow(QWindow *window)
0089 {
0090     if (!m_windowWatchers.contains(window)) {
0091         window->installEventFilter(this);
0092         auto conn = connect(window, &QObject::destroyed, this, [this, window]() {
0093             resetBlur(window);
0094             m_blurRegions.remove(window);
0095             resetContrast(window);
0096             m_backgroundConstrastRegions.remove(window);
0097             m_slideMap.remove(window);
0098             m_windowWatchers.remove(window);
0099         });
0100         m_windowWatchers[window] << conn;
0101         auto waylandWindow = dynamic_cast<QtWaylandClient::QWaylandWindow *>(window->handle());
0102         if (waylandWindow) {
0103             auto conn = connect(waylandWindow, &QtWaylandClient::QWaylandWindow::wlSurfaceDestroyed, this, [this, window]() {
0104                 resetBlur(window);
0105                 resetContrast(window);
0106             });
0107             m_windowWatchers[window] << conn;
0108         }
0109     }
0110 }
0111 
0112 void WindowEffects::releaseWindow(QWindow *window)
0113 {
0114     if (!m_blurRegions.contains(window) && !m_backgroundConstrastRegions.contains(window) && !m_slideMap.contains(window)) {
0115         for (const auto &conn : m_windowWatchers[window]) {
0116             disconnect(conn);
0117         }
0118         window->removeEventFilter(this);
0119         m_windowWatchers.remove(window);
0120     }
0121 }
0122 
0123 // Helper function to replace a QObject value in the map and delete the old one.
0124 template<typename MapType>
0125 void replaceValue(MapType &map, typename MapType::key_type key, typename MapType::mapped_type value)
0126 {
0127     if (auto oldValue = map.take(key)) {
0128         oldValue->deleteLater();
0129     }
0130     if (value) {
0131         map[key] = value;
0132     }
0133 }
0134 
0135 void WindowEffects::resetBlur(QWindow *window, KWayland::Client::Blur *blur)
0136 {
0137     replaceValue(m_blurs, window, blur);
0138 }
0139 
0140 void WindowEffects::resetContrast(QWindow *window, KWayland::Client::Contrast *contrast)
0141 {
0142     replaceValue(m_contrasts, window, contrast);
0143 }
0144 
0145 bool WindowEffects::eventFilter(QObject *watched, QEvent *event)
0146 {
0147     if (event->type() == QEvent::Expose) {
0148         auto ee = static_cast<QExposeEvent *>(event);
0149 
0150         if ((ee->region().isNull())) {
0151             return false;
0152         }
0153 
0154         auto window = qobject_cast<QWindow *>(watched);
0155         if (!window) {
0156             return false;
0157         }
0158 
0159         {
0160             auto it = m_blurRegions.constFind(window);
0161             if (it != m_blurRegions.constEnd()) {
0162                 installBlur(window, true, *it);
0163             }
0164         }
0165         {
0166             auto it = m_backgroundConstrastRegions.constFind(window);
0167             if (it != m_backgroundConstrastRegions.constEnd()) {
0168                 installContrast(window, true, it->contrast, it->intensity, it->saturation, it->region);
0169             }
0170         }
0171     }
0172     return false;
0173 }
0174 
0175 bool WindowEffects::isEffectAvailable(KWindowEffects::Effect effect)
0176 {
0177     switch (effect) {
0178     case KWindowEffects::BackgroundContrast:
0179         return WaylandIntegration::self()->waylandContrastManager() != nullptr;
0180     case KWindowEffects::BlurBehind:
0181         return WaylandIntegration::self()->waylandBlurManager() != nullptr;
0182     case KWindowEffects::Slide:
0183         return WaylandIntegration::self()->waylandSlideManager() != nullptr;
0184     default:
0185         return false;
0186     }
0187 }
0188 
0189 void WindowEffects::slideWindow(WId id, KWindowEffects::SlideFromLocation location, int offset)
0190 {
0191     auto window = windowForId(id);
0192     if (!window) {
0193         return;
0194     }
0195     if (location != KWindowEffects::SlideFromLocation::NoEdge) {
0196         m_slideMap[window] = SlideData{
0197             .location = location,
0198             .offset = offset,
0199         };
0200         trackWindow(window);
0201     } else {
0202         m_slideMap.remove(window);
0203         releaseWindow(window);
0204     }
0205 
0206     installSlide(window, location, offset);
0207 }
0208 
0209 void WindowEffects::installSlide(QWindow *window, KWindowEffects::SlideFromLocation location, int offset)
0210 {
0211     if (!WaylandIntegration::self()->waylandSlideManager()) {
0212         return;
0213     }
0214     KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(window);
0215     if (surface) {
0216         if (location != KWindowEffects::SlideFromLocation::NoEdge) {
0217             auto slide = WaylandIntegration::self()->waylandSlideManager()->createSlide(surface, surface);
0218 
0219             KWayland::Client::Slide::Location convertedLoc;
0220             switch (location) {
0221             case KWindowEffects::SlideFromLocation::TopEdge:
0222                 convertedLoc = KWayland::Client::Slide::Location::Top;
0223                 break;
0224             case KWindowEffects::SlideFromLocation::LeftEdge:
0225                 convertedLoc = KWayland::Client::Slide::Location::Left;
0226                 break;
0227             case KWindowEffects::SlideFromLocation::RightEdge:
0228                 convertedLoc = KWayland::Client::Slide::Location::Right;
0229                 break;
0230             case KWindowEffects::SlideFromLocation::BottomEdge:
0231             default:
0232                 convertedLoc = KWayland::Client::Slide::Location::Bottom;
0233                 break;
0234             }
0235 
0236             slide->setLocation(convertedLoc);
0237             slide->setOffset(offset);
0238             slide->commit();
0239         } else {
0240             WaylandIntegration::self()->waylandSlideManager()->removeSlide(surface);
0241         }
0242 
0243         WaylandIntegration::self()->waylandConnection()->flush();
0244     }
0245 }
0246 
0247 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 81)
0248 QList<QSize> WindowEffects::windowSizes(const QList<WId> &ids)
0249 {
0250     Q_UNUSED(ids)
0251     QList<QSize> sizes;
0252     return sizes;
0253 }
0254 #endif
0255 
0256 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82)
0257 void WindowEffects::presentWindows(WId controller, const QList<WId> &ids)
0258 {
0259     Q_UNUSED(controller)
0260     Q_UNUSED(ids)
0261 }
0262 #endif
0263 
0264 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82)
0265 void WindowEffects::presentWindows(WId controller, int desktop)
0266 {
0267     Q_UNUSED(controller)
0268     Q_UNUSED(desktop)
0269 }
0270 #endif
0271 
0272 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 82)
0273 void WindowEffects::highlightWindows(WId controller, const QList<WId> &ids)
0274 {
0275     Q_UNUSED(controller)
0276     Q_UNUSED(ids)
0277 }
0278 #endif
0279 
0280 void WindowEffects::enableBlurBehind(WId winId, bool enable, const QRegion &region)
0281 {
0282     auto window = windowForId(winId);
0283     if (!window) {
0284         return;
0285     }
0286     if (enable) {
0287         trackWindow(window);
0288         m_blurRegions[window] = region;
0289     } else {
0290         resetBlur(window);
0291         m_blurRegions.remove(window);
0292         releaseWindow(window);
0293     }
0294 
0295     installBlur(window, enable, region);
0296 }
0297 
0298 void WindowEffects::installBlur(QWindow *window, bool enable, const QRegion &region)
0299 {
0300     if (!WaylandIntegration::self()->waylandBlurManager()) {
0301         return;
0302     }
0303     KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(window);
0304 
0305     if (surface) {
0306         if (enable) {
0307             auto blur = WaylandIntegration::self()->waylandBlurManager()->createBlur(surface, surface);
0308             std::unique_ptr<KWayland::Client::Region> wlRegion(WaylandIntegration::self()->waylandCompositor()->createRegion(region, nullptr));
0309             blur->setRegion(wlRegion.get());
0310             blur->commit();
0311             resetBlur(window, blur);
0312         } else {
0313             resetBlur(window);
0314             WaylandIntegration::self()->waylandBlurManager()->removeBlur(surface);
0315         }
0316 
0317         WaylandIntegration::self()->waylandConnection()->flush();
0318     }
0319 }
0320 
0321 void WindowEffects::enableBackgroundContrast(WId winId, bool enable, qreal contrast, qreal intensity, qreal saturation, const QRegion &region)
0322 {
0323     auto window = windowForId(winId);
0324     if (!window) {
0325         return;
0326     }
0327     if (enable) {
0328         trackWindow(window);
0329         m_backgroundConstrastRegions[window].contrast = contrast;
0330         m_backgroundConstrastRegions[window].intensity = intensity;
0331         m_backgroundConstrastRegions[window].saturation = saturation;
0332         m_backgroundConstrastRegions[window].region = region;
0333     } else {
0334         resetContrast(window);
0335         m_backgroundConstrastRegions.remove(window);
0336         releaseWindow(window);
0337     }
0338 
0339     installContrast(window, enable, contrast, intensity, saturation, region);
0340 }
0341 
0342 void WindowEffects::installContrast(QWindow *window, bool enable, qreal contrast, qreal intensity, qreal saturation, const QRegion &region)
0343 {
0344     if (!WaylandIntegration::self()->waylandContrastManager()) {
0345         return;
0346     }
0347     KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(window);
0348     if (surface) {
0349         if (enable) {
0350             auto backgroundContrast = WaylandIntegration::self()->waylandContrastManager()->createContrast(surface, surface);
0351             std::unique_ptr<KWayland::Client::Region> wlRegion(WaylandIntegration::self()->waylandCompositor()->createRegion(region, nullptr));
0352             backgroundContrast->setRegion(wlRegion.get());
0353             backgroundContrast->setContrast(contrast);
0354             backgroundContrast->setIntensity(intensity);
0355             backgroundContrast->setSaturation(saturation);
0356             backgroundContrast->commit();
0357             resetContrast(window, backgroundContrast);
0358         } else {
0359             resetContrast(window);
0360             WaylandIntegration::self()->waylandContrastManager()->removeContrast(surface);
0361         }
0362 
0363         WaylandIntegration::self()->waylandConnection()->flush();
0364     }
0365 }
0366 
0367 void WindowEffects::setBackgroundFrost(QWindow *window, QColor color, const QRegion &region)
0368 {
0369     if (!WaylandIntegration::self()->waylandContrastManager()) {
0370         return;
0371     }
0372 
0373     KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(window);
0374     if (!surface) {
0375         return;
0376     }
0377     if (!color.isValid()) {
0378         resetContrast(window);
0379         WaylandIntegration::self()->waylandContrastManager()->removeContrast(surface);
0380         return;
0381     }
0382 
0383     auto backgroundContrast = WaylandIntegration::self()->waylandContrastManager()->createContrast(surface, surface);
0384     std::unique_ptr<KWayland::Client::Region> wlRegion(WaylandIntegration::self()->waylandCompositor()->createRegion(region, nullptr));
0385     backgroundContrast->setRegion(wlRegion.get());
0386     backgroundContrast->setFrost(color);
0387     backgroundContrast->commit();
0388     resetContrast(window, backgroundContrast);
0389 
0390     WaylandIntegration::self()->waylandConnection()->flush();
0391 }
0392 
0393 #if KWINDOWSYSTEM_BUILD_DEPRECATED_SINCE(5, 67)
0394 void WindowEffects::markAsDashboard(WId window)
0395 {
0396     Q_UNUSED(window)
0397 }
0398 #endif